Merge "Shrink by 0.5 for YUV TextViews"
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringBuilderPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringBuilderPerfTest.java
index 34fb88b..10fa8b9 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringBuilderPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringBuilderPerfTest.java
@@ -20,28 +20,19 @@
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.util.Arrays;
-import java.util.Collection;
/** Tests the performance of various StringBuilder methods. */
-@RunWith(Parameterized.class)
+@RunWith(AndroidJUnit4.class)
@LargeTest
public class StringBuilderPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mLength={0}")
- public static Collection<Object[]> data() {
- return Arrays.asList(new Object[][] {{1}, {10}, {100}});
- }
-
- @Parameterized.Parameter(0)
- public int mLength;
+ public int mLength = 100;
@Test
public void timeAppendBoolean() {
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 0de0a1c..d6b246a 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -112,6 +112,7 @@
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.LongArrayQueue;
@@ -2299,7 +2300,11 @@
+ " reached for uid: " + UserHandle.formatUid(callingUid)
+ ", callingPackage: " + callingPackage;
Slog.w(TAG, errorMsg);
- throw new IllegalStateException(errorMsg);
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new IllegalStateException(errorMsg);
+ } else {
+ EventLog.writeEvent(0x534e4554, "234441463", -1, errorMsg);
+ }
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation,
directReceiver, listenerTag, flags, workSource, alarmClock, callingUid,
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 836801d..5af02f4 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -52,11 +52,7 @@
#define SKIPPED_DUMPSTATE_SECTIONS { \
1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, /* Logs */ \
1200, 1201, 1202, /* Native, hal, java traces */ \
- 3000, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3010, 3011, 3012, 3013, \
- 3014, 3015, 3016, 3017, 3018, 3019, 3020, 3021, 3022, 3023, 3024, 3027, 3028, 3029, \
- 3030, 3031, 3032, 3033, 3034, 3035, 3036, 3037, 3038, 3039, 3040, 3041, 3042, 3043, \
- 3044, 3045, 3046, 3047, 3048, 3049, 3050, 3051, 3052, 3053, 3054, 3055, 3056, 4000, \
- 4001, /* Dumpsys */ }
+ 3018, /* dumpsys meminfo*/ }
namespace android {
namespace os {
diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java
index e6cdcc0..0d6a079 100644
--- a/core/java/android/accounts/Account.java
+++ b/core/java/android/accounts/Account.java
@@ -31,7 +31,6 @@
import com.android.internal.annotations.GuardedBy;
-import java.util.Objects;
import java.util.Set;
/**
@@ -87,12 +86,6 @@
if (TextUtils.isEmpty(type)) {
throw new IllegalArgumentException("the type must not be empty: " + type);
}
- if (name.length() > 200) {
- throw new IllegalArgumentException("account name is longer than 200 characters");
- }
- if (type.length() > 200) {
- throw new IllegalArgumentException("account type is longer than 200 characters");
- }
this.name = name;
this.type = type;
this.accessId = accessId;
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 9c0b443..07fbde5 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1454,6 +1454,11 @@
}
/** @hide */
+ public void setRemoteTransition(@Nullable RemoteTransition remoteTransition) {
+ mRemoteTransition = remoteTransition;
+ }
+
+ /** @hide */
public static ActivityOptions fromBundle(Bundle bOptions) {
return bOptions != null ? new ActivityOptions(bOptions) : null;
}
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 0f1b39c..17dcc80 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -826,7 +826,9 @@
* <p> Here, SC Map, refers to the {@link StreamConfigurationMap}, the target stream sizes must
* be chosen from. {@code DEFAULT} refers to the default sensor pixel mode {@link
* StreamConfigurationMap} and {@code MAX_RES} refers to the maximum resolution {@link
- * StreamConfigurationMap}. The same capture request must not mix targets from
+ * StreamConfigurationMap}. For {@code MAX_RES} streams, {@code MAX} in the {@code Max size} column refers to the maximum size from
+ * {@link StreamConfigurationMap#getOutputSizes} and {@link StreamConfigurationMap#getHighResolutionOutputSizes}.
+ * Note: The same capture request must not mix targets from
* {@link StreamConfigurationMap}s corresponding to different sensor pixel modes. </p>
*
* <p> 10-bit output capable
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index e5b9cdb..956a474 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -1723,7 +1723,17 @@
}
if (isUltraHighResolution) {
- sizes.add(getMaxSize(sm.getOutputSizes(formatChosen)));
+ Size [] outputSizes = sm.getOutputSizes(formatChosen);
+ Size [] highResolutionOutputSizes =
+ sm.getHighResolutionOutputSizes(formatChosen);
+ Size maxBurstSize = getMaxSizeOrNull(outputSizes);
+ Size maxHighResolutionSize = getMaxSizeOrNull(highResolutionOutputSizes);
+ Size chosenMaxSize =
+ maxBurstSize != null ? maxBurstSize : maxHighResolutionSize;
+ if (maxBurstSize != null && maxHighResolutionSize != null) {
+ chosenMaxSize = getMaxSize(maxBurstSize, maxHighResolutionSize);
+ }
+ sizes.add(chosenMaxSize);
} else {
if (formatChosen == ImageFormat.RAW_SENSOR) {
// RAW_SENSOR always has MAXIMUM threshold.
@@ -2126,6 +2136,21 @@
}
/**
+ * Get the largest size by area.
+ *
+ * @param sizes an array of sizes
+ *
+ * @return Largest Size or null if sizes was null or had 0 elements
+ */
+ public static @Nullable Size getMaxSizeOrNull(Size... sizes) {
+ if (sizes == null || sizes.length == 0) {
+ return null;
+ }
+
+ return getMaxSize(sizes);
+ }
+
+ /**
* Whether or not the hardware level reported by android.info.supportedHardwareLevel is
* at least the desired one (but could be higher)
*/
diff --git a/core/java/android/service/resumeonreboot/OWNERS b/core/java/android/service/resumeonreboot/OWNERS
index 721fbaf..e098053 100644
--- a/core/java/android/service/resumeonreboot/OWNERS
+++ b/core/java/android/service/resumeonreboot/OWNERS
@@ -1 +1,2 @@
-ejyzhang@google.com
\ No newline at end of file
+aveena@google.com
+ejyzhang@google.com
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 2879cd8..bc7e31f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1598,7 +1598,13 @@
public BitmapCache(Parcel source) {
mBitmaps = source.createTypedArrayList(Bitmap.CREATOR);
- mBitmapHashes = source.readSparseIntArray();
+ mBitmapHashes = new SparseIntArray();
+ for (int i = 0; i < mBitmaps.size(); i++) {
+ Bitmap b = mBitmaps.get(i);
+ if (b != null) {
+ mBitmapHashes.put(b.hashCode(), i);
+ }
+ }
}
public int getBitmapId(Bitmap b) {
@@ -1614,7 +1620,7 @@
b = b.asShared();
}
mBitmaps.add(b);
- mBitmapHashes.put(mBitmaps.size() - 1, hash);
+ mBitmapHashes.put(hash, mBitmaps.size() - 1);
mBitmapMemory = -1;
return (mBitmaps.size() - 1);
}
@@ -1631,7 +1637,6 @@
public void writeBitmapsToParcel(Parcel dest, int flags) {
dest.writeTypedList(mBitmaps, flags);
- dest.writeSparseIntArray(mBitmapHashes);
}
public int getBitmapMemory() {
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 627631a..62c7966 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -115,7 +115,7 @@
Slog.i(TAG, "Setting initial brightness to default value of: " + defaultBrightness);
}
- mBrightnessSyncObserver.startObserving();
+ mBrightnessSyncObserver.startObserving(mHandler);
mHandler.sendEmptyMessageAtTime(MSG_RUN_UPDATE, mClock.uptimeMillis());
}
@@ -482,27 +482,29 @@
}
};
- private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (selfChange) {
- return;
+ private ContentObserver createBrightnessContentObserver(Handler handler) {
+ return new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (selfChange) {
+ return;
+ }
+ if (BRIGHTNESS_URI.equals(uri)) {
+ handleBrightnessChangeInt(getScreenBrightnessInt());
+ }
}
- if (BRIGHTNESS_URI.equals(uri)) {
- handleBrightnessChangeInt(getScreenBrightnessInt());
- }
- }
- };
+ };
+ }
boolean isObserving() {
return mIsObserving;
}
- void startObserving() {
+ void startObserving(Handler handler) {
final ContentResolver cr = mContext.getContentResolver();
- cr.registerContentObserver(BRIGHTNESS_URI, false, mContentObserver,
- UserHandle.USER_ALL);
- mDisplayManager.registerDisplayListener(mListener, mHandler,
+ cr.registerContentObserver(BRIGHTNESS_URI, false,
+ createBrightnessContentObserver(handler), UserHandle.USER_ALL);
+ mDisplayManager.registerDisplayListener(mListener, handler,
DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
mIsObserving = true;
}
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index b866723..b11ea29 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -25,6 +25,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Size;
@@ -108,6 +109,12 @@
}
break;
case Icon.TYPE_RESOURCE:
+ if (!(TextUtils.isEmpty(icon.getResPackage())
+ || context.getPackageName().equals(icon.getResPackage()))) {
+ // We can't properly resolve icons from other packages here, so fall back.
+ return icon.loadDrawable(context);
+ }
+
Drawable result = resolveImage(icon.getResId(), context, maxWidth, maxHeight);
if (result != null) {
return tintDrawable(icon, result);
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 88aa4de..77317d1 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2179,7 +2179,7 @@
break;
}
- nAudioMix->mCriteria.add(nCriterion);
+ nAudioMix->mCriteria.push_back(nCriterion);
env->DeleteLocalRef(jCriterion);
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d689aab..6f83ab1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1995,6 +1995,9 @@
on grouped devices. -->
<bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
+ <!-- Flag indicating current media Output Switcher version. -->
+ <integer name="config_mediaOutputSwitchDialogVersion">1</integer>
+
<!-- Flag indicating that an outbound call must have a call capable phone account
that has declared it can process the call's handle. -->
<bool name="config_requireCallCapableAccountForHandle">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 234c435..1f71bf9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4703,6 +4703,8 @@
<java-symbol type="bool" name="config_volumeAdjustmentForRemoteGroupSessions" />
+ <java-symbol type="integer" name="config_mediaOutputSwitchDialogVersion" />
+
<!-- List of shared library packages that should be loaded by the classloader after the
code and resources provided by applications. -->
<java-symbol type="array" name="config_sharedLibrariesLoadedAfterApp" />
diff --git a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
index c63d18b..0cee526 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
@@ -270,4 +270,13 @@
assertThat(bd.getBitmap().getHeight()).isEqualTo(originalHeight);
}
+
+ @Test
+ public void resolveImage_iconWithOtherPackageResource_usesPackageContextDefinition()
+ throws IOException {
+ Icon icon = Icon.createWithResource("this_is_invalid", R.drawable.test32x24);
+ Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+ // This drawable must not be loaded - if it was, the code ignored the package specification.
+ assertThat(d).isNull();
+ }
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 392d89e..8ca1607 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -483,6 +483,8 @@
<permission name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE" />
<!-- Permission required for GTS test - GtsAssistIntentTestCases -->
<permission name="android.permission.MANAGE_VOICE_KEYPHRASES" />
+ <!-- Permission required for test - CellBroadcastComplianceTest -->
+ <permission name="com.android.cellbroadcastservice.FULL_ACCESS_CELL_BROADCAST_HISTORY" />
<!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
<permission name="android.permission.LOCK_DEVICE" />
<!-- Permissions required for CTS test - CtsSafetyCenterTestCases -->
diff --git a/media/Android.bp b/media/Android.bp
index e97f077..97970da 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -90,8 +90,13 @@
"aidl/android/media/audio/common/AudioStreamType.aidl",
"aidl/android/media/audio/common/AudioUsage.aidl",
"aidl/android/media/audio/common/AudioUuid.aidl",
+ "aidl/android/media/audio/common/Boolean.aidl",
+ "aidl/android/media/audio/common/Byte.aidl",
"aidl/android/media/audio/common/ExtraAudioDescriptor.aidl",
+ "aidl/android/media/audio/common/Float.aidl",
+ "aidl/android/media/audio/common/Double.aidl",
"aidl/android/media/audio/common/Int.aidl",
+ "aidl/android/media/audio/common/Long.aidl",
"aidl/android/media/audio/common/PcmType.aidl",
],
stability: "vintf",
diff --git a/media/aidl/android/media/audio/common/Boolean.aidl b/media/aidl/android/media/audio/common/Boolean.aidl
new file mode 100644
index 0000000..fddd5324
--- /dev/null
+++ b/media/aidl/android/media/audio/common/Boolean.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * This is a simple wrapper around a 'boolean', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Boolean {
+ boolean value;
+}
diff --git a/media/aidl/android/media/audio/common/Byte.aidl b/media/aidl/android/media/audio/common/Byte.aidl
new file mode 100644
index 0000000..f0a31a2
--- /dev/null
+++ b/media/aidl/android/media/audio/common/Byte.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * This is a simple wrapper around a 'byte', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Byte {
+ byte value;
+}
diff --git a/media/aidl/android/media/audio/common/Double.aidl b/media/aidl/android/media/audio/common/Double.aidl
new file mode 100644
index 0000000..d7ab7b8
--- /dev/null
+++ b/media/aidl/android/media/audio/common/Double.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * This is a simple wrapper around a 'double', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Double {
+ double value;
+}
diff --git a/media/aidl/android/media/audio/common/Float.aidl b/media/aidl/android/media/audio/common/Float.aidl
new file mode 100644
index 0000000..4c5257e
--- /dev/null
+++ b/media/aidl/android/media/audio/common/Float.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * This is a simple wrapper around a 'float', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Float {
+ float value;
+}
diff --git a/media/aidl/android/media/audio/common/Long.aidl b/media/aidl/android/media/audio/common/Long.aidl
new file mode 100644
index 0000000..a4aeb53
--- /dev/null
+++ b/media/aidl/android/media/audio/common/Long.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * This is a simple wrapper around a 'long', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Long {
+ long value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Boolean.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Boolean.aidl
new file mode 100644
index 0000000..bc996e4
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Boolean.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Boolean {
+ boolean value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Byte.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Byte.aidl
new file mode 100644
index 0000000..604e74d
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Byte.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Byte {
+ byte value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Double.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Double.aidl
new file mode 100644
index 0000000..a525629
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Double.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Double {
+ double value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Float.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Float.aidl
new file mode 100644
index 0000000..af98eab
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Float.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Float {
+ float value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Long.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Long.aidl
new file mode 100644
index 0000000..e403dd3
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Long.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Long {
+ long value;
+}
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index b8e7930e..ad4feec 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -335,13 +335,13 @@
}
/**
- * Constructor.
* Creates a media muxer that writes to the specified path.
+ * <p>The caller must not use the file {@code path} before calling {@link #stop}.
* @param path The path of the output media file.
* @param format The format of the output media file.
* @see android.media.MediaMuxer.OutputFormat
* @throws IllegalArgumentException if path is invalid or format is not supported.
- * @throws IOException if failed to open the file for write.
+ * @throws IOException if an error occurs while opening or creating the output file.
*/
public MediaMuxer(@NonNull String path, @Format int format) throws IOException {
if (path == null) {
@@ -363,16 +363,19 @@
}
/**
- * Constructor.
- * Creates a media muxer that writes to the specified FileDescriptor. File descriptor
- * must be seekable and writable. Application should not use the file referenced
- * by this file descriptor until {@link #stop}. It is the application's responsibility
- * to close the file descriptor. It is safe to do so as soon as this call returns.
- * @param fd The FileDescriptor of the output media file.
+ * Creates a media muxer that writes to the specified FileDescriptor.
+ * <p>The caller must not use the file referenced by the specified {@code fd} before calling
+ * {@link #stop}.
+ * <p>It is the caller's responsibility to close the file descriptor, which is safe to do so
+ * as soon as this call returns.
+ * @param fd The FileDescriptor of the output media file. If {@code format} is
+ * {@link OutputFormat#MUXER_OUTPUT_WEBM}, {@code fd} must be open in read-write mode.
+ * Otherwise, write mode is sufficient, but read-write is also accepted.
* @param format The format of the output media file.
* @see android.media.MediaMuxer.OutputFormat
- * @throws IllegalArgumentException if fd is invalid or format is not supported.
- * @throws IOException if failed to open the file for write.
+ * @throws IllegalArgumentException if {@code format} is not supported, or if {@code fd} is
+ * not open in the expected mode.
+ * @throws IOException if an error occurs while performing an IO operation.
*/
public MediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException {
setUpMediaMuxer(fd, format);
diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp
index 2679176..b664325 100644
--- a/media/jni/android_media_MediaMuxer.cpp
+++ b/media/jni/android_media_MediaMuxer.cpp
@@ -165,7 +165,11 @@
MediaMuxer::OutputFormat fileFormat =
static_cast<MediaMuxer::OutputFormat>(format);
- sp<MediaMuxer> muxer = new MediaMuxer(fd, fileFormat);
+ sp<MediaMuxer> muxer = MediaMuxer::create(fd, fileFormat);
+ if (muxer == nullptr) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Muxer creation failed");
+ return 0;
+ }
muxer->incStrong(clazz);
return reinterpret_cast<jlong>(muxer.get());
}
diff --git a/media/native/midi/include/amidi/AMidi.h b/media/native/midi/include/amidi/AMidi.h
index fbb7fb3..2b31703 100644
--- a/media/native/midi/include/amidi/AMidi.h
+++ b/media/native/midi/include/amidi/AMidi.h
@@ -66,7 +66,7 @@
*
* Introduced in API 33.
*/
-enum AMidiDevice_Protocol : int32_t {
+typedef enum AMidiDevice_Protocol : int32_t {
/**
* Constant representing a default protocol with Universal MIDI Packets (UMP).
* UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
@@ -131,7 +131,7 @@
* MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
*/
AMIDI_DEVICE_PROTOCOL_UNKNOWN = -1
-};
+} AMidiDevice_Protocol;
/*
* Device API
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b971f7a..979e9ea 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -680,6 +680,9 @@
<!-- Permission required for CTS test - Notification test suite -->
<uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" />
+ <!-- Permission required for test - CellBroadcastComplianceTest -->
+ <uses-permission android:name="com.android.cellbroadcastservice.FULL_ACCESS_CELL_BROADCAST_HISTORY" />
+
<!-- Permission required for CTS test - CaptioningManagerTest -->
<uses-permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" />
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 33453a4..1597845 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -631,9 +631,22 @@
Drawable artwork;
boolean isArtworkBound;
Icon artworkIcon = data.getArtwork();
+ WallpaperColors wallpaperColors = null;
if (artworkIcon != null) {
- WallpaperColors wallpaperColors = WallpaperColors
- .fromBitmap(artworkIcon.getBitmap());
+ if (artworkIcon.getType() == Icon.TYPE_BITMAP
+ || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+ // Avoids extra processing if this is already a valid bitmap
+ wallpaperColors = WallpaperColors
+ .fromBitmap(artworkIcon.getBitmap());
+ } else {
+ Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
+ if (artworkDrawable != null) {
+ wallpaperColors = WallpaperColors
+ .fromDrawable(artworkIcon.loadDrawable(mContext));
+ }
+ }
+ }
+ if (wallpaperColors != null) {
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
artwork = getScaledBackground(artworkIcon, width, height);
isArtworkBound = true;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index f2f2753..1ab0b5e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -269,6 +269,7 @@
}
private void onGroupActionTriggered(boolean isChecked, MediaDevice device) {
+ disableSeekBar();
if (isChecked && isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
mController.addDeviceToPlayMedia(device);
} else if (!isChecked && isDeviceIncluded(mController.getDeselectableMediaDevice(),
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index bec6739..3b4ca48 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -273,6 +273,8 @@
void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
if (!mController.isVolumeControlEnabled(device)) {
disableSeekBar();
+ } else {
+ enableSeekBar();
}
mSeekBar.setMaxVolume(device.getMaxVolume());
final int currentVolume = device.getCurrentVolume();
@@ -417,11 +419,16 @@
return drawable;
}
- private void disableSeekBar() {
+ protected void disableSeekBar() {
mSeekBar.setEnabled(false);
mSeekBar.setOnTouchListener((v, event) -> true);
}
+ private void enableSeekBar() {
+ mSeekBar.setEnabled(true);
+ mSeekBar.setOnTouchListener((v, event) -> false);
+ }
+
protected void setUpDeviceIcon(MediaDevice device) {
ThreadUtils.postOnBackgroundThread(() -> {
Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 5d7af52..6fe06e0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -194,6 +194,11 @@
}
private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) {
+ if (device == null) {
+ return isSourceDevice
+ ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
+ : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE;
+ }
switch (device.getDeviceType()) {
case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
return isSourceDevice
@@ -229,6 +234,9 @@
}
private int getInteractionDeviceType(MediaDevice device) {
+ if (device == null) {
+ return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE;
+ }
switch (device.getDeviceType()) {
case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__BUILTIN_SPEAKER;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index fbdabc7..fcd9e10 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -19,8 +19,6 @@
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Secure;
@@ -28,6 +26,7 @@
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
@@ -35,9 +34,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.PluginListener;
@@ -68,12 +65,20 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Predicate;
import javax.inject.Inject;
import javax.inject.Provider;
-/** Platform implementation of the quick settings tile host **/
+/** Platform implementation of the quick settings tile host
+ *
+ * This class keeps track of the set of current tiles and is the in memory source of truth
+ * (ground truth is kept in {@link Secure#QS_TILES}). When the ground truth changes,
+ * {@link #onTuningChanged} will be called and the tiles will be re-created as needed.
+ *
+ * This class also provides the interface for adding/removing/changing tiles.
+ */
@SysUISingleton
public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, Dumpable {
private static final String TAG = "QSTileHost";
@@ -89,11 +94,11 @@
private final TunerService mTunerService;
private final PluginManager mPluginManager;
private final DumpManager mDumpManager;
- private final BroadcastDispatcher mBroadcastDispatcher;
private final QSLogger mQSLogger;
private final UiEventLogger mUiEventLogger;
private final InstanceIdSequence mInstanceIdSequence;
private final CustomTileStatePersister mCustomTileStatePersister;
+ private final Executor mMainExecutor;
private final List<Callback> mCallbacks = new ArrayList<>();
@Nullable
@@ -113,13 +118,11 @@
public QSTileHost(Context context,
StatusBarIconController iconController,
QSFactory defaultFactory,
- @Main Handler mainHandler,
- @Background Looper bgLooper,
+ @Main Executor mainExecutor,
PluginManager pluginManager,
TunerService tunerService,
Provider<AutoTileManager> autoTiles,
DumpManager dumpManager,
- BroadcastDispatcher broadcastDispatcher,
Optional<CentralSurfaces> centralSurfacesOptional,
QSLogger qsLogger,
UiEventLogger uiEventLogger,
@@ -137,7 +140,7 @@
mDumpManager = dumpManager;
mQSLogger = qsLogger;
mUiEventLogger = uiEventLogger;
- mBroadcastDispatcher = broadcastDispatcher;
+ mMainExecutor = mainExecutor;
mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
@@ -151,7 +154,7 @@
mSecureSettings = secureSettings;
mCustomTileStatePersister = customTileStatePersister;
- mainHandler.post(() -> {
+ mainExecutor.execute(() -> {
// This is technically a hack to avoid circular dependency of
// QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation
// finishes before creating any tiles.
@@ -258,6 +261,33 @@
return mTileSpecs.indexOf(spec);
}
+ /**
+ * Whenever the Secure Setting keeping track of the current tiles changes (or upon start) this
+ * will be called with the new value of the setting.
+ *
+ * This method will do the following:
+ * <ol>
+ * <li>Destroy any existing tile that's not one of the current tiles (in the setting)</li>
+ * <li>Create new tiles for those that don't already exist. If this tiles end up being
+ * not available, they'll also be destroyed.</li>
+ * <li>Save the resolved list of tiles (current tiles that are available) into the setting.
+ * This means that after this call ends, the tiles in the Setting, {@link #mTileSpecs},
+ * and visible tiles ({@link #mTiles}) must match.
+ * </li>
+ * </ol>
+ *
+ * Additionally, if the user has changed, it'll do the following:
+ * <ul>
+ * <li>Change the user for SystemUI tiles: {@link QSTile#userSwitch}.</li>
+ * <li>Destroy any {@link CustomTile} and recreate it for the new user.</li>
+ * </ul>
+ *
+ * This happens in main thread as {@link com.android.systemui.tuner.TunerServiceImpl} dispatches
+ * in main thread.
+ *
+ * @see QSTile#isAvailable
+ */
+ @MainThread
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
@@ -330,34 +360,44 @@
mCurrentUser = currentUser;
List<String> currentSpecs = new ArrayList<>(mTileSpecs);
mTileSpecs.clear();
- mTileSpecs.addAll(tileSpecs);
+ mTileSpecs.addAll(newTiles.keySet()); // Only add the valid (available) tiles.
mTiles.clear();
mTiles.putAll(newTiles);
if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
// If we didn't manage to create any tiles, set it to empty (default)
Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
- changeTiles(currentSpecs, loadTileSpecs(mContext, ""));
+ changeTilesByUser(currentSpecs, loadTileSpecs(mContext, ""));
} else {
+ String resolvedTiles = TextUtils.join(",", mTileSpecs);
+ if (!resolvedTiles.equals(newValue)) {
+ // If the resolved tiles (those we actually ended up with) are different than
+ // the ones that are in the setting, update the Setting.
+ saveTilesToSettings(mTileSpecs);
+ }
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onTilesChanged();
}
}
}
+ /**
+ * Only use with [CustomTile] if the tile doesn't exist anymore (and therefore doesn't need
+ * its lifecycle terminated).
+ */
@Override
public void removeTile(String spec) {
- changeTileSpecs(tileSpecs-> tileSpecs.remove(spec));
+ mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));
}
/**
* Remove many tiles at once.
*
- * It will only save to settings once (as opposed to {@link QSTileHost#removeTile} called
+ * It will only save to settings once (as opposed to {@link QSTileHost#removeTileByUser} called
* multiple times).
*/
@Override
public void removeTiles(Collection<String> specs) {
- changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs));
+ mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs)));
}
@Override
@@ -381,27 +421,30 @@
* @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
*/
public void addTile(String spec, int requestPosition) {
- if (spec.equals("work")) Log.wtfStack(TAG, "Adding work tile");
- changeTileSpecs(tileSpecs -> {
- if (tileSpecs.contains(spec)) return false;
+ mMainExecutor.execute(() ->
+ changeTileSpecs(tileSpecs -> {
+ if (tileSpecs.contains(spec)) return false;
- int size = tileSpecs.size();
- if (requestPosition == POSITION_AT_END || requestPosition >= size) {
- tileSpecs.add(spec);
- } else {
- tileSpecs.add(requestPosition, spec);
- }
- return true;
- });
+ int size = tileSpecs.size();
+ if (requestPosition == POSITION_AT_END || requestPosition >= size) {
+ tileSpecs.add(spec);
+ } else {
+ tileSpecs.add(requestPosition, spec);
+ }
+ return true;
+ })
+ );
}
- void saveTilesToSettings(List<String> tileSpecs) {
- if (tileSpecs.contains("work")) Log.wtfStack(TAG, "Saving work tile");
+
+ @MainThread
+ private void saveTilesToSettings(List<String> tileSpecs) {
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
null /* tag */, false /* default */, mCurrentUser,
true /* overrideable by restore */);
}
+ @MainThread
private void changeTileSpecs(Predicate<List<String>> changeFunction) {
final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser);
final List<String> tileSpecs = loadTileSpecs(mContext, setting);
@@ -421,29 +464,32 @@
*/
public void addTile(ComponentName tile, boolean end) {
String spec = CustomTile.toSpec(tile);
- if (!mTileSpecs.contains(spec)) {
- List<String> newSpecs = new ArrayList<>(mTileSpecs);
- if (end) {
- newSpecs.add(spec);
- } else {
- newSpecs.add(0, spec);
- }
- changeTiles(mTileSpecs, newSpecs);
- }
+ addTile(spec, end ? POSITION_AT_END : 0);
}
- public void removeTile(ComponentName tile) {
- List<String> newSpecs = new ArrayList<>(mTileSpecs);
- newSpecs.remove(CustomTile.toSpec(tile));
- changeTiles(mTileSpecs, newSpecs);
+ /**
+ * This will call through {@link #changeTilesByUser}. It should only be used when a tile is
+ * removed by a <b>user action</b> like {@code adb}.
+ */
+ public void removeTileByUser(ComponentName tile) {
+ mMainExecutor.execute(() -> {
+ List<String> newSpecs = new ArrayList<>(mTileSpecs);
+ if (newSpecs.remove(CustomTile.toSpec(tile))) {
+ changeTilesByUser(mTileSpecs, newSpecs);
+ }
+ });
}
/**
* Change the tiles triggered by the user editing.
* <p>
* This is not called on device start, or on user change.
+ *
+ * {@link android.service.quicksettings.TileService#onTileRemoved} will be called for tiles
+ * that are removed.
*/
- public void changeTiles(List<String> previousTiles, List<String> newTiles) {
+ @MainThread
+ public void changeTilesByUser(List<String> previousTiles, List<String> newTiles) {
final List<String> copy = new ArrayList<>(previousTiles);
final int NP = copy.size();
for (int i = 0; i < NP; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index e52bfbd..d84b12c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -182,7 +182,7 @@
for (int i = 1; i < mTiles.size() && mTiles.get(i) != null; i++) {
newSpecs.add(mTiles.get(i).spec);
}
- host.changeTiles(mCurrentSpecs, newSpecs);
+ host.changeTilesByUser(mCurrentSpecs, newSpecs);
mCurrentSpecs = newSpecs;
}
@@ -200,7 +200,7 @@
/** */
public void resetTileSpecs(List<String> specs) {
// Notify the host so the tiles get removed callbacks.
- mHost.changeTiles(mCurrentSpecs, specs);
+ mHost.changeTilesByUser(mCurrentSpecs, specs);
setTileSpecs(specs);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index bf565a8..cfc57db 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -289,7 +289,7 @@
}
}
- mServices.getHost().removeTile(component);
+ mServices.getHost().removeTile(CustomTile.toSpec(component));
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 38a208b..f4ca7ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -642,6 +642,8 @@
// - device keyguard is shown in secure mode;
// - profile is locked with a work challenge.
SparseArray<UserInfo> currentProfiles = getCurrentProfiles();
+ SparseBooleanArray oldPublicModes = mLockscreenPublicMode.clone();
+ SparseBooleanArray oldWorkChallenges = mUsersWithSeparateWorkChallenge.clone();
mUsersWithSeparateWorkChallenge.clear();
for (int i = currentProfiles.size() - 1; i >= 0; i--) {
final int userId = currentProfiles.valueAt(i).id;
@@ -660,7 +662,10 @@
}
getEntryManager().updateNotifications("NotificationLockscreenUserManager.updatePublicMode");
// TODO(b/234738798): Migrate KeyguardNotificationVisibilityProvider to use this listener
- // notifyNotificationStateChanged();
+ if (!mLockscreenPublicMode.equals(oldPublicModes)
+ || !mUsersWithSeparateWorkChallenge.equals(oldWorkChallenges)) {
+ notifyNotificationStateChanged();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 6c99e3a..bef3d50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -20,8 +20,10 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationHeadsUpLog
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import javax.inject.Inject
class NotificationInterruptLogger @Inject constructor(
@@ -212,6 +214,33 @@
})
}
+ fun logNoFullscreen(entry: NotificationEntry, reason: String) {
+ hunBuffer.log(TAG, DEBUG, {
+ str1 = entry.key
+ str2 = reason
+ }, {
+ "No FullScreenIntent: $str2: $str1"
+ })
+ }
+
+ fun logNoFullscreenWarning(entry: NotificationEntry, reason: String) {
+ hunBuffer.log(TAG, WARNING, {
+ str1 = entry.key
+ str2 = reason
+ }, {
+ "No FullScreenIntent: WARNING: $str2: $str1"
+ })
+ }
+
+ fun logFullscreen(entry: NotificationEntry, reason: String) {
+ hunBuffer.log(TAG, DEBUG, {
+ str1 = entry.key
+ str2 = reason
+ }, {
+ "FullScreenIntent: $str2: $str1"
+ })
+ }
+
fun keyguardHideNotification(key: String) {
hunBuffer.log(TAG, DEBUG, {
str1 = key
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index e210f19..e1ddbd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -177,9 +177,69 @@
*/
@Override
public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) {
- return entry.getSbn().getNotification().fullScreenIntent != null
- && (!shouldHeadsUp(entry)
- || mStatusBarStateController.getState() == StatusBarState.KEYGUARD);
+ if (entry.getSbn().getNotification().fullScreenIntent == null) {
+ return false;
+ }
+
+ // Never show FSI when suppressed by DND
+ if (entry.shouldSuppressFullScreenIntent()) {
+ mLogger.logNoFullscreen(entry, "Suppressed by DND");
+ return false;
+ }
+
+ // Never show FSI if importance is not HIGH
+ if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
+ mLogger.logNoFullscreen(entry, "Not important enough");
+ return false;
+ }
+
+ // If the notification has suppressive GroupAlertBehavior, block FSI and warn.
+ StatusBarNotification sbn = entry.getSbn();
+ if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
+ // b/231322873: Detect and report an event when a notification has both an FSI and a
+ // suppressive groupAlertBehavior, and now correctly block the FSI from firing.
+ final int uid = entry.getSbn().getUid();
+ android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior");
+ mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
+ return false;
+ }
+
+ // If the screen is off, then launch the FullScreenIntent
+ if (!mPowerManager.isInteractive()) {
+ mLogger.logFullscreen(entry, "Device is not interactive");
+ return true;
+ }
+
+ // If the device is currently dreaming, then launch the FullScreenIntent
+ if (isDreaming()) {
+ mLogger.logFullscreen(entry, "Device is dreaming");
+ return true;
+ }
+
+ // If the keyguard is showing, then launch the FullScreenIntent
+ if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mLogger.logFullscreen(entry, "Keyguard is showing");
+ return true;
+ }
+
+ // If the notification should HUN, then we don't need FSI
+ if (shouldHeadsUp(entry)) {
+ mLogger.logNoFullscreen(entry, "Expected to HUN");
+ return false;
+ }
+
+ // If the notification won't HUN for some other reason (DND/snooze/etc), launch FSI.
+ mLogger.logFullscreen(entry, "Expected not to HUN");
+ return true;
+ }
+
+ private boolean isDreaming() {
+ try {
+ return mDreamManager.isDreaming();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query dream manager.", e);
+ return false;
+ }
}
private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
@@ -219,13 +279,7 @@
return false;
}
- boolean isDreaming = false;
- try {
- isDreaming = mDreamManager.isDreaming();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to query dream manager.", e);
- }
- boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
+ boolean inUse = mPowerManager.isScreenOn() && !isDreaming();
if (!inUse) {
mLogger.logNoHeadsUpNotInUse(sbn);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 010e6cf..e5f8424 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -339,6 +339,13 @@
};
/**
+ * Recalculate sensitiveness without animation; called when waking up while keyguard occluded.
+ */
+ public void updateSensitivenessForOccludedWakeup() {
+ mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode());
+ }
+
+ /**
* Set the overexpansion of the panel to be applied to the view.
*/
public void setOverExpansion(float overExpansion) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 8782be5..9070ead 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -428,7 +428,7 @@
if (isSafetyCenterEnabled && !mAutoTracker.isAdded(mSafetySpec)) {
initSafetyTile();
} else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) {
- mHost.removeTile(CustomTile.getComponentFromSpec(mSafetySpec));
+ mHost.removeTile(mSafetySpec);
mHost.unmarkTileAsAutoAdded(mSafetySpec);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 9060d5f..ffd50ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -187,7 +187,7 @@
public void remQsTile(ComponentName tile) {
QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
if (qsPanelController != null && qsPanelController.getHost() != null) {
- qsPanelController.getHost().removeTile(tile);
+ qsPanelController.getHost().removeTileByUser(tile);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 2966538..e9a837b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -3688,6 +3688,18 @@
public void onFinishedWakingUp() {
mWakeUpCoordinator.setFullyAwake(true);
mWakeUpCoordinator.setWakingUp(false);
+ if (mKeyguardStateController.isOccluded()
+ && !mDozeParameters.canControlUnlockedScreenOff()) {
+ // When the keyguard is occluded we don't use the KEYGUARD state which would
+ // normally cause these redaction updates. If AOD is on, the KEYGUARD state is used
+ // to show the doze, AND UnlockedScreenOffAnimationController.onFinishedWakingUp()
+ // would force a KEYGUARD state that would take care of recalculating redaction.
+ // So if AOD is off or unsupported we need to trigger these updates at screen on
+ // when the keyguard is occluded.
+ mLockscreenUserManager.updatePublicMode();
+ mNotificationPanelViewController.getNotificationStackScrollLayoutController()
+ .updateSensitivenessForOccludedWakeup();
+ }
if (mLaunchCameraWhenFinishedWaking) {
mNotificationPanelViewController.launchCamera(
false /* animate */, mLastCameraLaunchSource);
diff --git a/packages/SystemUI/tests/Android.bp b/packages/SystemUI/tests/Android.bp
new file mode 100644
index 0000000..3c418ed
--- /dev/null
+++ b/packages/SystemUI/tests/Android.bp
@@ -0,0 +1,50 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+ name: "SystemUITests",
+
+ dxflags: ["--multi-dex"],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ static_libs: ["SystemUI-tests"],
+ compile_multilib: "both",
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
+ libs: [
+ "android.test.runner",
+ "telephony-common",
+ "android.test.base",
+ ],
+ aaptflags: [
+ "--extra-packages com.android.systemui",
+ ],
+
+ // sign this with platform cert, so this test is allowed to inject key events into
+ // UI it doesn't own. This is necessary to allow screenshots to be taken
+ certificate: "platform",
+
+ additional_manifests: ["AndroidManifest.xml"],
+ manifest: "AndroidManifest-base.xml",
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
deleted file mode 100644
index ff5165d..0000000
--- a/packages/SystemUI/tests/Android.mk
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2011 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_JACK_FLAGS := --multi-dex native
-LOCAL_DX_FLAGS := --multi-dex
-
-LOCAL_PACKAGE_NAME := SystemUITests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- SystemUI-tests
-
-LOCAL_MULTILIB := both
-
-LOCAL_JNI_SHARED_LIBRARIES := \
- libdexmakerjvmtiagent \
- libmultiplejvmtiagentsinterferenceagent \
- libstaticjvmtiagent
-
-LOCAL_JAVA_LIBRARIES := \
- android.test.runner \
- telephony-common \
- android.test.base \
-
-LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui
-
-# sign this with platform cert, so this test is allowed to inject key events into
-# UI it doesn't own. This is necessary to allow screenshots to be taken
-LOCAL_CERTIFICATE := platform
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest.xml
-LOCAL_MANIFEST_FILE := AndroidManifest-base.xml
-
-# Provide jack a list of classes to exclude from code coverage.
-# This is needed because the SystemUITests compile SystemUI source directly, rather than using
-# LOCAL_INSTRUMENTATION_FOR := SystemUI.
-#
-# We want to exclude the test classes from code coverage measurements, but they share the same
-# package as the rest of SystemUI so they can't be easily filtered by package name.
-#
-# Generate a comma separated list of patterns based on the test source files under src/
-# SystemUI classes are in ../src/ so they won't be excluded.
-# Example:
-# Input files: src/com/android/systemui/Test.java src/com/android/systemui/AnotherTest.java
-# Generated exclude list: com.android.systemui.Test*,com.android.systemui.AnotherTest*
-
-# Filter all src files under src/ to just java files
-local_java_files := $(filter %.java,$(call all-java-files-under, src))
-# Transform java file names into full class names.
-# This only works if the class name matches the file name and the directory structure
-# matches the package.
-local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
-local_comma := ,
-local_empty :=
-local_space := $(local_empty) $(local_empty)
-# Convert class name list to jacoco exclude list
-# This appends a * to all classes and replace the space separators with commas.
-jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*,com.android.keyguard.*
-LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
-
-ifeq ($(EXCLUDE_SYSTEMUI_TESTS),)
- include $(BUILD_PACKAGE)
-endif
-
-# Reset variables
-local_java_files :=
-local_classes :=
-local_comma :=
-local_space :=
-jacoco_exclude :=
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 241ed24..6f4579b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -547,6 +547,19 @@
}
@Test
+ fun bindAlbumView_artUsesResource() {
+ val albumArt = Icon.createWithResource(context, R.drawable.ic_android)
+ val state = mediaData.copy(artwork = albumArt)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(state, PACKAGE)
+ bgExecutor.runAllReady()
+ mainExecutor.runAllReady()
+
+ verify(albumView).setImageDrawable(any(Drawable::class.java))
+ }
+
+ @Test
fun bindAlbumView_setAfterExecutors() {
val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 59475cf..c4cb833 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -274,4 +274,30 @@
verify(mMediaOutputController).connectDevice(mMediaDevice2);
}
+
+ @Test
+ public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
+ when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
+ List<MediaDevice> selectableDevices = new ArrayList<>();
+ selectableDevices.add(mMediaDevice1);
+ when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(true);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ mViewHolder.mContainerLayout.performClick();
+
+ assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void onBindViewHolder_volumeControlChangeToEnabled_enableSeekbarAgain() {
+ when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+ assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
+
+ when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mSeekBar.isEnabled()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 664af75..32c66d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -32,8 +32,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
@@ -42,36 +40,23 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
-import com.android.systemui.qs.external.CustomTileStatePersister;
-import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceRequestController;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSFactoryImpl;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.animation.UniqueObjectHostView;
-import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -79,8 +64,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -125,34 +108,11 @@
mFragments.dispatchResume();
processAllMessages();
- QSTileHost host =
- new QSTileHost(
- mContext,
- mock(StatusBarIconController.class),
- mock(QSFactoryImpl.class),
- new Handler(),
- Looper.myLooper(),
- mock(PluginManager.class),
- mock(TunerService.class),
- () -> mock(AutoTileManager.class),
- mock(DumpManager.class),
- mock(BroadcastDispatcher.class),
- Optional.of(mock(CentralSurfaces.class)),
- mock(QSLogger.class),
- mock(UiEventLogger.class),
- mock(UserTracker.class),
- mock(SecureSettings.class),
- mock(CustomTileStatePersister.class),
- mTileServiceRequestControllerBuilder,
- mock(TileLifecycleManager.Factory.class));
-
qs.setListening(true);
processAllMessages();
qs.setListening(false);
processAllMessages();
- host.destroy();
- processAllMessages();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 8cf3fe2..7dbc561 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -32,12 +32,11 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
import androidx.annotation.Nullable;
@@ -48,7 +47,6 @@
import com.android.internal.util.CollectionUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.ActivityStarter;
@@ -68,8 +66,10 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -81,18 +81,19 @@
import java.io.StringWriter;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Provider;
@RunWith(AndroidTestingRunner.class)
@SmallTest
-@RunWithLooper(setAsMainLooper = true)
public class QSTileHostTest extends SysuiTestCase {
private static String MOCK_STATE_STRING = "MockState";
private static ComponentName CUSTOM_TILE =
ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS");
private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE);
+ private static final String SETTING = QSTileHost.TILES_SETTING;
@Mock
private StatusBarIconController mIconController;
@@ -107,8 +108,6 @@
@Mock
private DumpManager mDumpManager;
@Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
private QSTile.State mMockState;
@Mock
private CentralSurfaces mCentralSurfaces;
@@ -132,31 +131,47 @@
@Mock
private TileLifecycleManager mTileLifecycleManager;
- private Handler mHandler;
- private TestableLooper mLooper;
+ private FakeExecutor mMainExecutor;
+
private QSTileHost mQSTileHost;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mLooper = TestableLooper.get(this);
- mHandler = new Handler(mLooper.getLooper());
+ mMainExecutor = new FakeExecutor(new FakeSystemClock());
+
when(mTileServiceRequestControllerBuilder.create(any()))
.thenReturn(mTileServiceRequestController);
when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
.thenReturn(mTileLifecycleManager);
mSecureSettings = new FakeSettings();
- mSecureSettings.putStringForUser(
- QSTileHost.TILES_SETTING, "", "", false, mUserTracker.getUserId(), false);
- mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
- mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
- mBroadcastDispatcher, mCentralSurfaces, mQSLogger, mUiEventLogger, mUserTracker,
- mSecureSettings, mCustomTileStatePersister, mTileServiceRequestControllerBuilder,
- mTileLifecycleManagerFactory);
+ saveSetting("");
+ mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mMainExecutor,
+ mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
+ mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
+ mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory);
+
+ mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ mMainExecutor.execute(() -> mQSTileHost.onTuningChanged(SETTING, getSetting()));
+ mMainExecutor.runAllReady();
+ }
+ }, mUserTracker.getUserId());
setUpTileFactory();
}
+ private void saveSetting(String value) {
+ mSecureSettings.putStringForUser(
+ SETTING, value, "", false, mUserTracker.getUserId(), false);
+ }
+
+ private String getSetting() {
+ return mSecureSettings.getStringForUser(SETTING, mUserTracker.getUserId());
+ }
+
private void setUpTileFactory() {
when(mMockState.toString()).thenReturn(MOCK_STATE_STRING);
// Only create this kind of tiles
@@ -173,6 +188,10 @@
return new NotAvailableTile(mQSTileHost);
} else if (CUSTOM_TILE_SPEC.equals(spec)) {
return mCustomTile;
+ } else if ("internet".equals(spec)
+ || "wifi".equals(spec)
+ || "cell".equals(spec)) {
+ return new TestTile1(mQSTileHost);
} else {
return null;
}
@@ -196,14 +215,14 @@
public void testInvalidSpecUsesDefault() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "not-valid");
+ saveSetting("not-valid");
assertEquals(2, mQSTileHost.getTiles().size());
}
@Test
public void testRemoveWifiAndCellularWithoutInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2");
+ saveSetting("wifi, spec1, cell, spec2");
assertEquals("internet", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec1", mQSTileHost.mTileSpecs.get(1));
@@ -212,7 +231,7 @@
@Test
public void testRemoveWifiAndCellularWithInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2, internet");
+ saveSetting("wifi, spec1, cell, spec2, internet");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
@@ -221,7 +240,7 @@
@Test
public void testRemoveWifiWithoutInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, wifi, spec2");
+ saveSetting("spec1, wifi, spec2");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("internet", mQSTileHost.mTileSpecs.get(1));
@@ -230,7 +249,7 @@
@Test
public void testRemoveCellWithInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, spec2, cell, internet");
+ saveSetting("spec1, spec2, cell, internet");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
@@ -239,7 +258,7 @@
@Test
public void testNoWifiNoCellularNoInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+ saveSetting("spec1,spec2");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
@@ -249,7 +268,7 @@
public void testSpecWithInvalidDoesNotUseDefault() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec2,not-valid");
+ saveSetting("spec2,not-valid");
assertEquals(1, mQSTileHost.getTiles().size());
QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
@@ -258,7 +277,7 @@
@Test
public void testDump() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+ saveSetting("spec1,spec2");
StringWriter w = new StringWriter();
PrintWriter pw = new PrintWriter(w);
mQSTileHost.dump(pw, new String[]{});
@@ -274,7 +293,7 @@
public void testDefault() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles_default, "spec1");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "default");
+ saveSetting("default");
assertEquals(1, mQSTileHost.getTiles().size());
QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
assertTrue(element instanceof TestTile1);
@@ -285,7 +304,7 @@
public void testNoRepeatedSpecs_addTile() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+ saveSetting("spec1,spec2");
mQSTileHost.addTile("spec1");
@@ -298,9 +317,10 @@
public void testAddTileAtValidPosition() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
+ saveSetting("spec1,spec3");
mQSTileHost.addTile("spec2", 1);
+ mMainExecutor.runAllReady();
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
@@ -312,9 +332,10 @@
public void testAddTileAtInvalidPositionAddsToEnd() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
+ saveSetting("spec1,spec3");
mQSTileHost.addTile("spec2", 100);
+ mMainExecutor.runAllReady();
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
@@ -326,9 +347,10 @@
public void testAddTileAtEnd() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
+ saveSetting("spec1,spec3");
mQSTileHost.addTile("spec2", QSTileHost.POSITION_AT_END);
+ mMainExecutor.runAllReady();
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
@@ -338,9 +360,10 @@
@Test
public void testNoRepeatedSpecs_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, CUSTOM_TILE_SPEC);
+ saveSetting(CUSTOM_TILE_SPEC);
mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
+ mMainExecutor.runAllReady();
assertEquals(1, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
@@ -348,9 +371,10 @@
@Test
public void testAddedAtBeginningOnDefault_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+ saveSetting("spec1"); // seed
mQSTileHost.addTile(CUSTOM_TILE);
+ mMainExecutor.runAllReady();
assertEquals(2, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
@@ -358,9 +382,10 @@
@Test
public void testAddedAtBeginning_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+ saveSetting("spec1"); // seed
mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
+ mMainExecutor.runAllReady();
assertEquals(2, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
@@ -368,9 +393,10 @@
@Test
public void testAddedAtEnd_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+ saveSetting("spec1"); // seed
mQSTileHost.addTile(CUSTOM_TILE, /* end */ true);
+ mMainExecutor.runAllReady();
assertEquals(2, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(1));
@@ -409,13 +435,13 @@
@Test
public void testNotAvailableTile_specNotNull() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "na");
+ saveSetting("na");
verify(mQSLogger, never()).logTileDestroyed(isNull(), anyString());
}
@Test
public void testCustomTileRemoved_stateDeleted() {
- mQSTileHost.changeTiles(List.of(CUSTOM_TILE_SPEC), List.of());
+ mQSTileHost.changeTilesByUser(List.of(CUSTOM_TILE_SPEC), List.of());
verify(mCustomTileStatePersister)
.removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId()));
@@ -423,29 +449,99 @@
@Test
public void testRemoveTiles() {
- List<String> tiles = List.of("spec1", "spec2", "spec3");
- mQSTileHost.saveTilesToSettings(tiles);
+ saveSetting("spec1,spec2,spec3");
mQSTileHost.removeTiles(List.of("spec1", "spec2"));
+ mMainExecutor.runAllReady();
assertEquals(List.of("spec3"), mQSTileHost.mTileSpecs);
}
+ @Test
+ public void testTilesRemovedInQuickSuccession() {
+ saveSetting("spec1,spec2,spec3");
+ mQSTileHost.removeTile("spec1");
+ mQSTileHost.removeTile("spec3");
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ assertEquals("spec2", getSetting());
+ }
+
+ @Test
+ public void testAddTileInMainThread() {
+ saveSetting("spec1,spec2");
+
+ mQSTileHost.addTile("spec3");
+ assertEquals(List.of("spec1", "spec2"), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testRemoveTileInMainThread() {
+ saveSetting("spec1,spec2");
+
+ mQSTileHost.removeTile("spec1");
+ assertEquals(List.of("spec1", "spec2"), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testRemoveTilesInMainThread() {
+ saveSetting("spec1,spec2,spec3");
+
+ mQSTileHost.removeTiles(List.of("spec3", "spec1"));
+ assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testRemoveTileByUserInMainThread() {
+ saveSetting("spec1," + CUSTOM_TILE_SPEC);
+
+ mQSTileHost.removeTileByUser(CUSTOM_TILE);
+ assertEquals(List.of("spec1", CUSTOM_TILE_SPEC), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testNonValidTileNotStoredInSettings() {
+ saveSetting("spec1,not-valid");
+
+ assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ assertEquals("spec1", getSetting());
+ }
+
+ @Test
+ public void testNotAvailableTileNotStoredInSettings() {
+ saveSetting("spec1,na");
+
+ assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ assertEquals("spec1", getSetting());
+ }
+
private class TestQSTileHost extends QSTileHost {
TestQSTileHost(Context context, StatusBarIconController iconController,
- QSFactory defaultFactory, Handler mainHandler, Looper bgLooper,
+ QSFactory defaultFactory, Executor mainExecutor,
PluginManager pluginManager, TunerService tunerService,
Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
- BroadcastDispatcher broadcastDispatcher, CentralSurfaces centralSurfaces,
- QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker,
- SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister,
+ CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger,
+ UserTracker userTracker, SecureSettings secureSettings,
+ CustomTileStatePersister customTileStatePersister,
TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
TileLifecycleManager.Factory tileLifecycleManagerFactory) {
- super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager,
- tunerService, autoTiles, dumpManager, broadcastDispatcher,
- Optional.of(centralSurfaces), qsLogger, uiEventLogger, userTracker,
- secureSettings, customTileStatePersister, tileServiceRequestControllerBuilder,
- tileLifecycleManagerFactory);
+ super(context, iconController, defaultFactory, mainExecutor, pluginManager,
+ tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
+ uiEventLogger, userTracker, secureSettings, customTileStatePersister,
+ tileServiceRequestControllerBuilder, tileLifecycleManagerFactory);
}
@Override
@@ -455,25 +551,16 @@
@Override
public void onPluginDisconnected(QSFactory plugin) {
}
-
- @Override
- void saveTilesToSettings(List<String> tileSpecs) {
- super.saveTilesToSettings(tileSpecs);
- // After tiles are changed, make sure to call onTuningChanged with the new setting if it
- // changed
- String specs = mSecureSettings.getStringForUser(
- QSTileHost.TILES_SETTING, mUserTracker.getUserId());
- onTuningChanged(TILES_SETTING, specs);
- }
}
+
private class TestTile extends QSTileImpl<QSTile.State> {
protected TestTile(QSHost host) {
super(
host,
- mLooper.getLooper(),
- new Handler(mLooper.getLooper()),
+ mock(Looper.class),
+ mock(Handler.class),
new FalsingManagerFake(),
mock(MetricsLogger.class),
mock(StatusBarStateController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
index 3d53062..d42cbe3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
@@ -55,6 +55,6 @@
@Test
public void testResetNotifiesHost() {
mTileAdapter.resetTileSpecs(Collections.emptyList());
- verify(mQSTileHost).changeTiles(any(), any());
+ verify(mQSTileHost).changeTilesByUser(any(), any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 6b7e5b93..471ddfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -29,6 +29,7 @@
import android.content.ComponentName;
import android.content.Intent;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
@@ -65,6 +66,7 @@
import java.util.ArrayList;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Provider;
@@ -130,17 +132,16 @@
.thenReturn(mTileLifecycleManager);
Provider<Handler> provider = () -> new Handler(mTestableLooper.getLooper());
+ Executor executor = new HandlerExecutor(provider.get());
QSTileHost host = new QSTileHost(mContext,
mStatusBarIconController,
mQSFactory,
- provider.get(),
- mTestableLooper.getLooper(),
+ executor,
mPluginManager,
mTunerService,
() -> mAutoTileManager,
mDumpManager,
- mock(BroadcastDispatcher.class),
Optional.of(mCentralSurfaces),
mQSLogger,
mUiEventLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 7687d12..dd2b667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -29,7 +29,9 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -59,6 +61,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.KeyguardNotificationSuppressor;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -325,6 +328,38 @@
}
@Test
+ public void testUpdateIsPublicMode() {
+ when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+
+ NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
+ mLockscreenUserManager.addNotificationStateChangedListener(listener);
+ mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+
+ // first call explicitly sets user 0 to not public; notifies
+ mLockscreenUserManager.updatePublicMode();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
+
+ // Calling again with keyguard now showing makes user 0 public; notifies
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mLockscreenUserManager.updatePublicMode();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
+ }
+
+ @Test
public void testShowSilentNotifications_settingSaysShow() {
mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 90627cb..25add0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -24,6 +24,7 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.google.common.truth.Truth.assertThat;
@@ -31,6 +32,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -95,6 +97,8 @@
NotifPipelineFlags mFlags;
@Mock
KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
+ @Mock
+ PendingIntent mPendingIntent;
private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
@@ -422,6 +426,122 @@
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
}
+ @Test
+ public void testShouldNotFullScreen_notPendingIntent() throws RemoteException {
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldNotFullScreen_notHighImportance() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger).logNoFullscreen(entry, "Not important enough");
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+ when(mDreamManager.isDreaming()).thenReturn(true);
+ when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldFullScreen_notInteractive() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Device is not interactive");
+ }
+
+ @Test
+ public void testShouldFullScreen_isDreaming() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(true);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Device is dreaming");
+ }
+
+ @Test
+ public void testShouldFullScreen_onKeyguard() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Keyguard is showing");
+ }
+
+ @Test
+ public void testShouldNotFullScreen_willHun() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldFullScreen_packageSnoozed() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger).logNoHeadsUpPackageSnoozed(entry.getSbn());
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Expected not to HUN");
+ }
+
/**
* Bubbles can happen.
*/
@@ -526,6 +646,10 @@
.setContentText("content text")
.build();
+ return createNotification(importance, n);
+ }
+
+ private NotificationEntry createNotification(int importance, Notification n) {
return new NotificationEntryBuilder()
.setPkg("a")
.setOpPkg("a")
@@ -536,6 +660,20 @@
.build();
}
+ private NotificationEntry createFsiNotification(int importance, boolean silent) {
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setContentTitle("title")
+ .setContentText("content text")
+ .setFullScreenIntent(mPendingIntent, true)
+ .setGroup("fsi")
+ .setGroupAlertBehavior(silent
+ ? Notification.GROUP_ALERT_SUMMARY
+ : Notification.GROUP_ALERT_ALL)
+ .build();
+
+ return createNotification(importance, n);
+ }
+
private final NotificationInterruptSuppressor
mSuppressAwakeHeadsUp =
new NotificationInterruptSuppressor() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 371119c..4ccbc6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -490,7 +490,7 @@
mAutoTileManager.init();
when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
- verify(mQsTileHost, times(1)).removeTile(safetyComponent);
+ verify(mQsTileHost, times(1)).removeTile(TEST_CUSTOM_SAFETY_SPEC);
}
@Test
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 9b29bae..9e42887 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -176,6 +176,7 @@
private boolean mSendMotionEvents;
+ private SparseArray<Boolean> mServiceDetectsGestures = new SparseArray<>(0);
boolean mRequestFilterKeyEvents;
boolean mRetrieveInteractiveWindows;
@@ -2344,9 +2345,17 @@
}
public void setServiceDetectsGesturesEnabled(int displayId, boolean mode) {
+ mServiceDetectsGestures.put(displayId, mode);
mSystemSupport.setServiceDetectsGesturesEnabled(displayId, mode);
}
+ public boolean isServiceDetectsGesturesEnabled(int displayId) {
+ if (mServiceDetectsGestures.contains(displayId)) {
+ return mServiceDetectsGestures.get(displayId);
+ }
+ return false;
+ }
+
public void requestTouchExploration(int displayId) {
mSystemSupport.requestTouchExploration(displayId);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 75724bf..bf4c02d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -176,6 +176,8 @@
private int mEnabledFeatures;
+ // Display-specific features
+ private SparseArray<Boolean> mServiceDetectsGestures = new SparseArray<>();
private final SparseArray<EventStreamState> mMouseStreamStates = new SparseArray<>(0);
private final SparseArray<EventStreamState> mTouchScreenStreamStates = new SparseArray<>(0);
@@ -458,7 +460,9 @@
final Context displayContext = mContext.createDisplayContext(display);
final int displayId = display.getDisplayId();
-
+ if (!mServiceDetectsGestures.contains(displayId)) {
+ mServiceDetectsGestures.put(displayId, false);
+ }
if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
if (mAutoclickController == null) {
mAutoclickController = new AutoclickController(
@@ -481,6 +485,7 @@
if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) {
explorer.setSendMotionEventsEnabled(true);
}
+ explorer.setServiceDetectsGestures(mServiceDetectsGestures.get(displayId));
addFirstEventHandler(displayId, explorer);
mTouchExplorer.put(displayId, explorer);
}
@@ -565,7 +570,8 @@
mTouchExplorer.remove(displayId);
}
- final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId);
+ final MagnificationGestureHandler handler =
+ mMagnificationGestureHandler.get(displayId);
if (handler != null) {
handler.onDestroy();
mMagnificationGestureHandler.remove(displayId);
@@ -897,6 +903,7 @@
if (mTouchExplorer.contains(displayId)) {
mTouchExplorer.get(displayId).setServiceDetectsGestures(mode);
}
+ mServiceDetectsGestures.put(displayId, mode);
}
public void requestTouchExploration(int displayId) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8044af5..73793c7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1721,31 +1721,34 @@
}
private boolean scheduleNotifyMotionEvent(MotionEvent event) {
+ boolean result = false;
+ int displayId = event.getDisplayId();
synchronized (mLock) {
AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestTouchExplorationMode) {
+ if (service.isServiceDetectsGesturesEnabled(displayId)) {
service.notifyMotionEvent(event);
- return true;
+ result = true;
}
}
}
- return false;
+ return result;
}
private boolean scheduleNotifyTouchState(int displayId, int touchState) {
+ boolean result = false;
synchronized (mLock) {
AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestTouchExplorationMode) {
+ if (service.isServiceDetectsGesturesEnabled(displayId)) {
service.notifyTouchState(displayId, touchState);
- return true;
+ result = true;
}
}
}
- return false;
+ return result;
}
private void notifyClearAccessibilityCacheLocked() {
@@ -2301,7 +2304,6 @@
if (userState.isTwoFingerPassthroughEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_REQUEST_2_FINGER_PASSTHROUGH;
}
- }
if (userState.isFilterKeyEventsEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
}
@@ -2314,6 +2316,7 @@
if (userState.isPerformGesturesEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
}
+
if (flags != 0) {
if (!mHasInputFilter) {
mHasInputFilter = true;
@@ -2326,9 +2329,20 @@
}
mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
} else {
- if (mHasInputFilter) {
- mHasInputFilter = false;
- mInputFilter.setUserAndEnabledFeatures(userState.mUserId, 0);
+ if (mHasInputFilter) {
+ mHasInputFilter = false;
+ mInputFilter.setUserAndEnabledFeatures(userState.mUserId, 0);
+ if (userState.isTouchExplorationEnabledLocked()) {
+ // Service gesture detection is turned on and off on a per-display
+ // basis.
+ final ArrayList<Display> displays = getValidDisplayList();
+ for (Display display : displays) {
+ int displayId = display.getDisplayId();
+ boolean mode = userState.isServiceDetectsGesturesEnabled(displayId);
+ mInputFilter.setServiceDetectsGesturesEnabled(displayId, mode);
+ }
+ }
+ }
inputFilter = null;
setInputFilter = true;
}
@@ -4304,6 +4318,7 @@
private void setServiceDetectsGesturesInternal(int displayId, boolean mode) {
synchronized (mLock) {
+ getCurrentUserStateLocked().setServiceDetectsGesturesEnabled(displayId, mode);
if (mHasInputFilter && mInputFilter != null) {
mInputFilter.setServiceDetectsGesturesEnabled(displayId, mode);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 55dc196..0e8f3c0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -44,6 +44,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
@@ -118,6 +119,7 @@
private boolean mRequestMultiFingerGestures;
private boolean mRequestTwoFingerPassthrough;
private boolean mSendMotionEventsEnabled;
+ private SparseArray<Boolean> mServiceDetectsGestures = new SparseArray<>(0);
private int mUserInteractiveUiTimeout;
private int mUserNonInteractiveUiTimeout;
private int mNonInteractiveUiTimeout = 0;
@@ -987,4 +989,15 @@
mFocusStrokeWidth = strokeWidth;
mFocusColor = color;
}
+
+ public void setServiceDetectsGesturesEnabled(int displayId, boolean mode) {
+ mServiceDetectsGestures.put(displayId, mode);
+ }
+
+ public boolean isServiceDetectsGesturesEnabled(int displayId) {
+ if (mServiceDetectsGestures.contains(displayId)) {
+ return mServiceDetectsGestures.get(displayId);
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 5dbdb9b..c441859 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -16,6 +16,7 @@
# Health
per-file BatteryService.java = file:platform/hardware/interfaces:/health/aidl/OWNERS
+per-file *Accessibility* = file:/services/accessibility/OWNERS
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
per-file *AppOp* = file:/core/java/android/permission/OWNERS
per-file *Battery* = file:/BATTERY_STATS_OWNERS
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 7d8c19f..6b731c3 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1820,6 +1820,14 @@
if (account == null) {
return false;
}
+ if (account.name != null && account.name.length() > 200) {
+ Log.w(TAG, "Account cannot be added - Name longer than 200 chars");
+ return false;
+ }
+ if (account.type != null && account.type.length() > 200) {
+ Log.w(TAG, "Account cannot be added - Name longer than 200 chars");
+ return false;
+ }
if (!isLocalUnlockedUser(accounts.userId)) {
Log.w(TAG, "Account " + account.toSafeString() + " cannot be added - user "
+ accounts.userId + " is locked. callingUid=" + callingUid);
@@ -2065,6 +2073,10 @@
+ ", pid " + Binder.getCallingPid());
}
if (accountToRename == null) throw new IllegalArgumentException("account is null");
+ if (newName != null && newName.length() > 200) {
+ Log.e(TAG, "renameAccount failed - account name longer than 200");
+ throw new IllegalArgumentException("account name longer than 200");
+ }
int userId = UserHandle.getCallingUserId();
if (!isAccountManagedByCaller(accountToRename.type, callingUid, userId)) {
String msg = String.format(
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 6076eb1..7a09ce7 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -2790,13 +2790,6 @@
if (isOnSystemDeviceIdleAllowlist(uid)) {
return REASON_SYSTEM_ALLOW_LISTED;
}
- if (isOnDeviceIdleAllowlist(uid)) {
- return REASON_ALLOWLISTED_PACKAGE;
- }
- final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
- if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
- return REASON_COMPANION_DEVICE_MANAGER;
- }
if (UserManager.isDeviceInDemoMode(mContext)) {
return REASON_DEVICE_DEMO_MODE;
}
@@ -2805,6 +2798,7 @@
.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)) {
return REASON_DISALLOW_APPS_CONTROL;
}
+ final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
if (am.isDeviceOwner(uid)) {
return REASON_DEVICE_OWNER;
}
@@ -2822,14 +2816,9 @@
final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+ // Check each packages to see if any of them is in the "fixed" exemption cases.
for (String pkg : packages) {
- if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
- uid, pkg) == AppOpsManager.MODE_ALLOWED) {
- return REASON_OP_ACTIVATE_VPN;
- } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
- uid, pkg) == AppOpsManager.MODE_ALLOWED) {
- return REASON_OP_ACTIVATE_PLATFORM_VPN;
- } else if (isSystemModule(pkg)) {
+ if (isSystemModule(pkg)) {
return REASON_SYSTEM_MODULE;
} else if (isCarrierApp(pkg)) {
return REASON_CARRIER_PRIVILEGED_APP;
@@ -2843,6 +2832,16 @@
return REASON_ACTIVE_DEVICE_ADMIN;
}
}
+ // Loop the packages again, and check the user-configurable exemptions.
+ for (String pkg : packages) {
+ if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
+ uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+ return REASON_OP_ACTIVATE_VPN;
+ } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
+ uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+ return REASON_OP_ACTIVATE_PLATFORM_VPN;
+ }
+ }
}
if (isRoleHeldByUid(RoleManager.ROLE_DIALER, uid)) {
return REASON_ROLE_DIALER;
@@ -2850,6 +2849,12 @@
if (isRoleHeldByUid(RoleManager.ROLE_EMERGENCY, uid)) {
return REASON_ROLE_EMERGENCY;
}
+ if (isOnDeviceIdleAllowlist(uid)) {
+ return REASON_ALLOWLISTED_PACKAGE;
+ }
+ if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
+ return REASON_COMPANION_DEVICE_MANAGER;
+ }
return REASON_DENIED;
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 9a51712..8e179b9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -424,7 +424,7 @@
AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "requestedCommunicationDevice, device: "
- + device + "mAudioModeOwner: " + mAudioModeOwner.toString());
+ + device + " mAudioModeOwner: " + mAudioModeOwner.toString());
}
return device;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a4f10ba..018a2fc 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7072,6 +7072,7 @@
@GuardedBy("mNotificationLock")
void snoozeLocked(NotificationRecord r) {
+ final List<NotificationRecord> recordsToSnooze = new ArrayList<>();
if (r.getSbn().isGroup()) {
final List<NotificationRecord> groupNotifications =
findCurrentAndSnoozedGroupNotificationsLocked(
@@ -7080,8 +7081,8 @@
if (r.getNotification().isGroupSummary()) {
// snooze all children
for (int i = 0; i < groupNotifications.size(); i++) {
- if (mKey != groupNotifications.get(i).getKey()) {
- snoozeNotificationLocked(groupNotifications.get(i));
+ if (!mKey.equals(groupNotifications.get(i).getKey())) {
+ recordsToSnooze.add(groupNotifications.get(i));
}
}
} else {
@@ -7091,8 +7092,8 @@
if (groupNotifications.size() == 2) {
// snooze summary and the one child
for (int i = 0; i < groupNotifications.size(); i++) {
- if (mKey != groupNotifications.get(i).getKey()) {
- snoozeNotificationLocked(groupNotifications.get(i));
+ if (!mKey.equals(groupNotifications.get(i).getKey())) {
+ recordsToSnooze.add(groupNotifications.get(i));
}
}
}
@@ -7100,7 +7101,15 @@
}
}
// snooze the notification
- snoozeNotificationLocked(r);
+ recordsToSnooze.add(r);
+
+ if (mSnoozeHelper.canSnooze(recordsToSnooze.size())) {
+ for (int i = 0; i < recordsToSnooze.size(); i++) {
+ snoozeNotificationLocked(recordsToSnooze.get(i));
+ }
+ } else {
+ Log.w(TAG, "Cannot snooze " + r.getKey() + ": too many snoozed notifications");
+ }
}
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 7f265df..15d7c1e 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -62,6 +62,8 @@
public class SnoozeHelper {
public static final int XML_SNOOZED_NOTIFICATION_VERSION = 1;
+ static final int CONCURRENT_SNOOZE_LIMIT = 500;
+
protected static final String XML_TAG_NAME = "snoozed-notifications";
private static final String XML_SNOOZED_NOTIFICATION = "notification";
@@ -135,6 +137,15 @@
}
}
+ protected boolean canSnooze(int numberToSnooze) {
+ synchronized (mLock) {
+ if ((mPackages.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@NonNull
protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) {
Long time = null;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 9e0c975..2b00ad7 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -330,7 +330,8 @@
int newRuleInstanceCount = getCurrentInstanceCount(automaticZenRule.getOwner())
+ getCurrentInstanceCount(automaticZenRule.getConfigurationActivity())
+ 1;
- if (newRuleInstanceCount > RULE_LIMIT_PER_PACKAGE
+ int newPackageRuleCount = getPackageRuleCount(pkg) + 1;
+ if (newPackageRuleCount > RULE_LIMIT_PER_PACKAGE
|| (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount)) {
throw new IllegalArgumentException("Rule instance limit exceeded");
}
@@ -511,6 +512,23 @@
return count;
}
+ // Equivalent method to getCurrentInstanceCount, but for all rules associated with a specific
+ // package rather than a condition provider service or activity.
+ private int getPackageRuleCount(String pkg) {
+ if (pkg == null) {
+ return 0;
+ }
+ int count = 0;
+ synchronized (mConfig) {
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (pkg.equals(rule.getPkg())) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
public boolean canManageAutomaticZenRule(ZenRule rule) {
final int callingUid = Binder.getCallingUid();
if (callingUid == 0 || callingUid == Process.SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ab936a6..83687e9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4884,8 +4884,12 @@
ActivityOptions takeOptions() {
if (DEBUG_TRANSITION) Slog.i(TAG, "Taking options for " + this + " callers="
+ Debug.getCallers(6));
+ if (mPendingOptions == null) return null;
final ActivityOptions opts = mPendingOptions;
mPendingOptions = null;
+ // Strip sensitive information from options before sending it to app.
+ opts.setRemoteTransition(null);
+ opts.setRemoteAnimationAdapter(null);
return opts;
}
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index f83173b..0bb773a 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -220,6 +220,11 @@
// Attempt to add starting window from the top-most activity.
for (int i = mDeferringAddStartActivities.size() - 1; i >= 0; --i) {
final DeferringStartingWindowRecord next = mDeferringAddStartActivities.get(i);
+ if (next.mDeferring.getTask() == null) {
+ Slog.e(TAG, "No task exists: " + next.mDeferring.shortComponentName
+ + " parent: " + next.mDeferring.getParent());
+ continue;
+ }
next.mDeferring.showStartingWindow(next.mPrev, mInitNewTask, mInitTaskSwitch,
mInitProcessRunning, true /* startActivity */, next.mSource, topOptions);
// If one succeeds, it is done.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index b9d6b2c..994df22 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -392,7 +392,7 @@
whenever(rule.mocks().appsFilter.getVisibilityAllowList(
any(PackageDataSnapshot::class.java),
argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java),
- any() as ArrayMap<String, out PackageStateInternal>
+ any<ArrayMap<String, out PackageStateInternal>>()
))
.thenReturn(list)
}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index d5c5745..30ec163 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -39,6 +39,7 @@
import android.app.AppOpsManager;
import android.app.PropertyInvalidatedCache;
import android.app.INotificationManager;
+import android.app.PropertyInvalidatedCache;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.BroadcastReceiver;
@@ -253,6 +254,26 @@
}
@SmallTest
+ public void testCheckAddAccountLongName() throws Exception {
+ unlockSystemUser();
+ String longString = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ + "aaaaa";
+ Account a11 = new Account(longString, AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+
+ mAms.addAccountExplicitly(
+ a11, /* password= */ "p11", /* extras= */ null, /* callerPackage= */ null);
+
+ String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+ when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+ Account[] accounts = mAms.getAccountsAsUser(null,
+ UserHandle.getCallingUserId(), mContext.getOpPackageName());
+ assertEquals(0, accounts.length);
+ }
+
+
+ @SmallTest
public void testPasswords() throws Exception {
unlockSystemUser();
Account a11 = new Account("account1", AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 709c928..480ad11 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3380,19 +3380,80 @@
}
@Test
- public void testSnoozeRunnable_reSnoozeASingleSnoozedNotification() throws Exception {
+ public void testSnoozeRunnable_tooManySnoozed_singleNotification() {
+ final NotificationRecord notification = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ mService.addNotification(notification);
+
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
+ when(mSnoozeHelper.canSnooze(1)).thenReturn(false);
+
+ NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
+ mService.new SnoozeNotificationRunnable(
+ notification.getKey(), 100, null);
+ snoozeNotificationRunnable.run();
+
+ verify(mSnoozeHelper, never()).snooze(any(NotificationRecord.class), anyLong());
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testSnoozeRunnable_tooManySnoozed_singleGroupChildNotification() {
+ final NotificationRecord notification = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ final NotificationRecord notificationChild = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", false);
+ mService.addNotification(notification);
+ mService.addNotification(notificationChild);
+
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
+ when(mSnoozeHelper.canSnooze(2)).thenReturn(false);
+
+ NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
+ mService.new SnoozeNotificationRunnable(
+ notificationChild.getKey(), 100, null);
+ snoozeNotificationRunnable.run();
+
+ verify(mSnoozeHelper, never()).snooze(any(NotificationRecord.class), anyLong());
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void testSnoozeRunnable_tooManySnoozed_summaryNotification() {
+ final NotificationRecord notification = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ final NotificationRecord notificationChild = generateNotificationRecord(
+ mTestNotificationChannel, 12, "group", false);
+ final NotificationRecord notificationChild2 = generateNotificationRecord(
+ mTestNotificationChannel, 13, "group", false);
+ mService.addNotification(notification);
+ mService.addNotification(notificationChild);
+ mService.addNotification(notificationChild2);
+
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
+ when(mSnoozeHelper.canSnooze(3)).thenReturn(false);
+
+ NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
+ mService.new SnoozeNotificationRunnable(
+ notification.getKey(), 100, null);
+ snoozeNotificationRunnable.run();
+
+ verify(mSnoozeHelper, never()).snooze(any(NotificationRecord.class), anyLong());
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(3);
+ }
+
+ @Test
+ public void testSnoozeRunnable_reSnoozeASingleSnoozedNotification() {
final NotificationRecord notification = generateNotificationRecord(
mTestNotificationChannel, 1, null, true);
mService.addNotification(notification);
when(mSnoozeHelper.getNotification(any())).thenReturn(notification);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
notification.getKey(), 100, null);
snoozeNotificationRunnable.run();
- NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable2 =
- mService.new SnoozeNotificationRunnable(
- notification.getKey(), 100, null);
snoozeNotificationRunnable.run();
// snooze twice
@@ -3400,19 +3461,17 @@
}
@Test
- public void testSnoozeRunnable_reSnoozeASnoozedNotificationWithGroupKey() throws Exception {
+ public void testSnoozeRunnable_reSnoozeASnoozedNotificationWithGroupKey() {
final NotificationRecord notification = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
mService.addNotification(notification);
when(mSnoozeHelper.getNotification(any())).thenReturn(notification);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
notification.getKey(), 100, null);
snoozeNotificationRunnable.run();
- NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable2 =
- mService.new SnoozeNotificationRunnable(
- notification.getKey(), 100, null);
snoozeNotificationRunnable.run();
// snooze twice
@@ -3430,6 +3489,7 @@
when(mSnoozeHelper.getNotification(any())).thenReturn(notification);
when(mSnoozeHelper.getNotifications(
anyString(), anyString(), anyInt())).thenReturn(new ArrayList<>());
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
@@ -3439,8 +3499,8 @@
.thenReturn(new ArrayList<>(Arrays.asList(notification, notification2)));
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable2 =
mService.new SnoozeNotificationRunnable(
- notification.getKey(), 100, null);
- snoozeNotificationRunnable.run();
+ notification2.getKey(), 100, null);
+ snoozeNotificationRunnable2.run();
// snooze twice
verify(mSnoozeHelper, times(4)).snooze(any(NotificationRecord.class), anyLong());
@@ -3454,6 +3514,7 @@
mTestNotificationChannel, 2, "group", false);
mService.addNotification(grouped);
mService.addNotification(nonGrouped);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
@@ -3483,6 +3544,7 @@
mService.addNotification(parent);
mService.addNotification(child);
mService.addNotification(child2);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
@@ -3504,6 +3566,7 @@
mService.addNotification(parent);
mService.addNotification(child);
mService.addNotification(child2);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
@@ -3529,6 +3592,7 @@
mTestNotificationChannel, 2, "group", false);
mService.addNotification(parent);
mService.addNotification(child);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
@@ -3556,6 +3620,7 @@
final NotificationRecord child = generateNotificationRecord(
mTestNotificationChannel, 2, "group", false);
mService.addNotification(child);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 2ae2ef7..8bead57 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIMIT;
import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
import static junit.framework.Assert.assertEquals;
@@ -281,6 +282,22 @@
}
@Test
+ public void testSnoozeLimit() {
+ for (int i = 0; i < CONCURRENT_SNOOZE_LIMIT; i++ ) {
+ NotificationRecord r = getNotificationRecord("pkg", i, i+"", UserHandle.SYSTEM);
+
+ assertTrue("cannot snooze record " + i, mSnoozeHelper.canSnooze(1));
+
+ if (i % 2 == 0) {
+ mSnoozeHelper.snooze(r, null);
+ } else {
+ mSnoozeHelper.snooze(r, 9000);
+ }
+ }
+ assertFalse(mSnoozeHelper.canSnooze(1));
+ }
+
+ @Test
public void testCancelByApp() throws Exception {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index fd1536c..4550b56 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1622,7 +1622,9 @@
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+ // We need the package name to be something that's not "android" so there aren't any
+ // existing rules under that package.
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
assertNotNull(id);
}
try {
@@ -1632,12 +1634,41 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
}
+ }
+ @Test
+ public void testAddAutomaticZenRule_beyondSystemLimit_differentComponents() {
+ // Make sure the system limit is enforced per-package even with different component provider
+ // names.
+ for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
+ ScheduleInfo si = new ScheduleInfo();
+ si.startHour = i;
+ AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
+ null,
+ new ComponentName("android", "ScheduleConditionProvider" + i),
+ ZenModeConfig.toScheduleConditionId(si),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ assertNotNull(id);
+ }
+ try {
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName("android", "ScheduleConditionProviderFinal"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ fail("allowed too many rules to be created");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
}
@Test
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 23f6c88..3787f3b 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -51,8 +51,10 @@
util::Utf16ToUtf8(StringPiece16(name_in.package, name_in.packageLen));
std::optional<ResourceNamedTypeRef> type;
+ std::string converted;
if (name_in.type) {
- type = ParseResourceNamedType(util::Utf16ToUtf8(StringPiece16(name_in.type, name_in.typeLen)));
+ converted = util::Utf16ToUtf8(StringPiece16(name_in.type, name_in.typeLen));
+ type = ParseResourceNamedType(converted);
} else if (name_in.type8) {
type = ParseResourceNamedType(StringPiece(name_in.type8, name_in.typeLen));
} else {
@@ -85,9 +87,10 @@
name_out.package = std::string(name_in.package, name_in.package_len);
std::optional<ResourceNamedTypeRef> type;
+ std::string converted;
if (name_in.type16) {
- type =
- ParseResourceNamedType(util::Utf16ToUtf8(StringPiece16(name_in.type16, name_in.type_len)));
+ converted = util::Utf16ToUtf8(StringPiece16(name_in.type16, name_in.type_len));
+ type = ParseResourceNamedType(converted);
} else if (name_in.type) {
type = ParseResourceNamedType(StringPiece(name_in.type, name_in.type_len));
} else {
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index d8c76e2..0170c4a 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -270,6 +270,8 @@
"Changes the name of the target package for overlay. Most useful\n"
"when used in conjunction with --rename-manifest-package.",
&options_.manifest_fixer_options.rename_overlay_target_package);
+ AddOptionalFlag("--rename-overlay-category", "Changes the category for the overlay.",
+ &options_.manifest_fixer_options.rename_overlay_category);
AddOptionalFlagList("-0", "File suffix not to compress.",
&options_.extensions_to_not_compress);
AddOptionalSwitch("--no-compress", "Do not compress any resources.",
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index d432341..efa42a2 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -449,13 +449,18 @@
manifest_action["attribution"]["inherit-from"];
manifest_action["original-package"];
manifest_action["overlay"].Action([&](xml::Element* el) -> bool {
- if (!options_.rename_overlay_target_package) {
- return true;
+ if (options_.rename_overlay_target_package) {
+ if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "targetPackage")) {
+ attr->value = options_.rename_overlay_target_package.value();
+ }
}
-
- if (xml::Attribute* attr =
- el->FindAttribute(xml::kSchemaAndroid, "targetPackage")) {
- attr->value = options_.rename_overlay_target_package.value();
+ if (options_.rename_overlay_category) {
+ if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "category")) {
+ attr->value = options_.rename_overlay_category.value();
+ } else {
+ el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, "category",
+ options_.rename_overlay_category.value()});
+ }
}
return true;
});
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index d5d1d17..a8707d9 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -48,6 +48,9 @@
// <overlay>.
std::optional<std::string> rename_overlay_target_package;
+ // The category to use instead of the one defined in 'android:category' in <overlay>.
+ std::optional<std::string> rename_overlay_category;
+
// The version name to set if 'android:versionName' is not defined in <manifest> or if
// replace_version is set.
std::optional<std::string> version_name_default;
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 432f10b..098d0be 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -351,6 +351,54 @@
EXPECT_THAT(attr->value, StrEq("com.android"));
}
+TEST_F(ManifestFixerTest, AddOverlayCategory) {
+ ManifestFixerOptions options;
+ options.rename_overlay_category = std::string("category");
+
+ std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <overlay android:targetName="Customization" android:targetPackage="android" />
+ </manifest>)EOF",
+ options);
+ ASSERT_THAT(doc, NotNull());
+
+ xml::Element* manifest_el = doc->root.get();
+ ASSERT_THAT(manifest_el, NotNull());
+
+ xml::Element* overlay_el = manifest_el->FindChild({}, "overlay");
+ ASSERT_THAT(overlay_el, NotNull());
+
+ xml::Attribute* attr = overlay_el->FindAttribute(xml::kSchemaAndroid, "category");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("category"));
+}
+
+TEST_F(ManifestFixerTest, OverrideOverlayCategory) {
+ ManifestFixerOptions options;
+ options.rename_overlay_category = std::string("category");
+
+ std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <overlay android:targetName="Customization"
+ android:targetPackage="android"
+ android:category="yrogetac"/>
+ </manifest>)EOF",
+ options);
+ ASSERT_THAT(doc, NotNull());
+
+ xml::Element* manifest_el = doc->root.get();
+ ASSERT_THAT(manifest_el, NotNull());
+
+ xml::Element* overlay_el = manifest_el->FindChild({}, "overlay");
+ ASSERT_THAT(overlay_el, NotNull());
+
+ xml::Attribute* attr = overlay_el->FindAttribute(xml::kSchemaAndroid, "category");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("category"));
+}
+
TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) {
ManifestFixerOptions options;
options.version_name_default = std::string("Beta");
diff --git a/tools/preload/loadclass/LoadClass.java b/tools/preload/loadclass/LoadClass.java
index a71b6a8..3f6658a 100644
--- a/tools/preload/loadclass/LoadClass.java
+++ b/tools/preload/loadclass/LoadClass.java
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import android.util.Log;
import android.os.Debug;
+import android.util.Log;
/**
* Loads a class, runs the garbage collector, and prints showmap output.
@@ -28,7 +28,7 @@
System.loadLibrary("android_runtime");
if (registerNatives() < 0) {
- throw new RuntimeException("Error registering natives.");
+ throw new RuntimeException("Error registering natives.");
}
Debug.startAllocCounting();
@@ -46,7 +46,7 @@
}
}
- System.gc();
+ Runtime.getRuntime().gc();
int allocCount = Debug.getGlobalAllocCount();
int allocSize = Debug.getGlobalAllocSize();
@@ -73,7 +73,7 @@
response.append(',').append(freedCount);
response.append(',').append(freedSize);
response.append(',').append(nativeHeapSize);
-
+
System.out.println(response.toString());
}