summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/LocaleManager.java28
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java254
-rw-r--r--core/java/android/content/AbstractThreadedSyncAdapter.java13
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java27
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl10
-rw-r--r--core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl2
-rw-r--r--core/java/android/os/Parcel.java1
-rw-r--r--core/java/android/provider/Settings.java7
-rw-r--r--core/java/android/service/autofill/FillRequest.java21
-rw-r--r--core/java/android/util/SparseSetArray.java19
-rw-r--r--core/java/android/view/autofill/AutofillManager.java66
-rw-r--r--core/java/android/widget/TextView.java19
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt407
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml9
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_complication_weather.xml8
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/styles.xml14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayNotificationCountProvider.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaData.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt207
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt482
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt174
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt167
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java54
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java8
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java11
-rw-r--r--services/autofill/java/com/android/server/autofill/ViewState.java6
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java10
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java90
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java3
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java36
-rw-r--r--services/core/java/com/android/server/dreams/DreamUiEventLogger.java62
-rw-r--r--services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java50
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java1
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java5
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecNetwork.java66
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java4
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssLocationProvider.java2
-rw-r--r--services/core/java/com/android/server/pm/AppsFilterImpl.java (renamed from services/core/java/com/android/server/pm/AppsFilter.java)1012
-rw-r--r--services/core/java/com/android/server/pm/AppsFilterSnapshot.java86
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java11
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java2
-rw-r--r--services/core/java/com/android/server/pm/DistractingPackageHelper.java198
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerInternalBase.java5
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java132
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceInjector.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java151
-rw-r--r--services/core/java/com/android/server/utils/WatchedArrayList.java7
-rw-r--r--services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java20
-rw-r--r--services/core/java/com/android/server/utils/WatchedSparseSetArray.java177
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java7
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java38
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java46
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt195
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt141
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt127
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java170
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java68
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java (renamed from services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java)215
-rw-r--r--services/tests/servicestests/src/com/android/server/utils/WatcherTest.java61
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java2
101 files changed, 4534 insertions, 1491 deletions
diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java
index efe9e35d4c64..794c6946f7a8 100644
--- a/core/java/android/app/LocaleManager.java
+++ b/core/java/android/app/LocaleManager.java
@@ -54,8 +54,18 @@ public class LocaleManager {
*
* <p>Pass a {@link LocaleList#getEmptyLocaleList()} to reset to the system locale.
*
- * <p><b>Note:</b> The set locales are persisted; they are backed up if the user has enabled
- * Backup & Restore.
+ * <p><b>Note:</b> Changes to app locales will result in a configuration change (and potentially
+ * an Activity lifecycle event) being applied to the calling application. For more information,
+ * see the <a
+ * href="https://developer.android.com/guide/topics/resources/runtime-changes">section on
+ * handling configuration changes</a>. The set locales are persisted; they are backed up if the
+ * user has enabled Backup & Restore.
+ *
+ * <p><b>Note:</b> Users' locale preferences are passed to applications by creating a union of
+ * any app-specific locales and system locales, with the app-specific locales appearing first.
+ * Language resources are then chosen per usual (as described in the <a
+ * href="https://developer.android.com/guide/topics/resources/multilingual-support">section on
+ * locale resolution</a>).
*
* @param locales the desired locales for the calling app.
*/
@@ -69,8 +79,18 @@ public class LocaleManager {
*
* <p>Pass a {@link LocaleList#getEmptyLocaleList()} to reset to the system locale.
*
- * <p><b>Note:</b> The set locales are persisted; they are backed up if the user has enabled
- * Backup & Restore.
+ * <p><b>Note:</b> Changes to app locales will result in a configuration change (and potentially
+ * an Activity lifecycle event) being applied to the specified application. For more
+ * information, see the <a
+ * href="https://developer.android.com/guide/topics/resources/runtime-changes">section on
+ * handling configuration changes</a>. The set locales are persisted; they are backed up if the
+ * user has enabled Backup & Restore.
+ *
+ * <p><b>Note:</b> Users' locale preferences are passed to applications by creating a union of
+ * any app-specific locales and system locales, with the app-specific locales appearing first.
+ * Language resources are then chosen per usual (as described in the <a
+ * href="https://developer.android.com/guide/topics/resources/multilingual-support">section on
+ * locale resolution</a>).
*
* @param appPackageName the package name of the app for which to set the locales.
* @param locales the desired locales for the specified app.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f9f0c58b2200..88ffdecfb537 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -64,6 +64,7 @@ import android.nfc.NfcAdapter;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.IpcDataCache;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.PersistableBundle;
@@ -3781,6 +3782,60 @@ public class DevicePolicyManager {
public static final String EXTRA_RESOURCE_IDS =
"android.app.extra.RESOURCE_IDS";
+ /**
+ * A convenience class that wraps some IpcDataCache methods. Instantiate it with an
+ * API string. Instances can and should be final static. All instances of this class
+ * use the same key for invalidation.
+ */
+ private static class BinderApi {
+ private final static String KEY = "DevicePolicyManager";
+ private final String mApi;
+ BinderApi(String api) {
+ mApi = api;
+ }
+ final String api() {
+ return mApi;
+ }
+ final String key() {
+ return KEY;
+ }
+ final static void invalidate() {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, KEY);
+ }
+ final void disable() {
+ IpcDataCache.disableForCurrentProcess(mApi);
+ }
+ }
+
+ /** @hide */
+ public static void invalidateBinderCaches() {
+ BinderApi.invalidate();
+ }
+
+ /**
+ * A simple wrapper for binder caches in this class. All caches are created with a
+ * maximum of 8 entries, the SYSTEM module, and a cache name that is the same as the api.
+ */
+ private static class BinderCache<Q,R> extends IpcDataCache<Q,R> {
+ BinderCache(BinderApi api, IpcDataCache.QueryHandler<Q,R> handler) {
+ super(8, IpcDataCache.MODULE_SYSTEM, api.key(), api.api(), handler);
+ }
+ }
+
+ /**
+ * Disable all caches in the local process.
+ * @hide
+ */
+ public static void disableLocalProcessCaches() {
+ disableGetKeyguardDisabledFeaturesCache();
+ disableHasDeviceOwnerCache();
+ disableGetProfileOwnerOrDeviceOwnerSupervisionComponentCache();
+ disableIsOrganizationOwnedDeviceWithManagedProfileCache();
+ disableGetDeviceOwnerOrganizationNameCache();
+ disableGetOrganizationNameForUserCache();
+ disableIsNetworkLoggingEnabled();
+ }
+
/** @hide */
@NonNull
@TestApi
@@ -8380,17 +8435,57 @@ public class DevicePolicyManager {
return getKeyguardDisabledFeatures(admin, myUserId());
}
+ // A key into the keyguard cache.
+ private static class KeyguardQuery {
+ private final ComponentName mAdmin;
+ private final int mUserHandle;
+ KeyguardQuery(@Nullable ComponentName admin, int userHandle) {
+ mAdmin = admin;
+ mUserHandle = userHandle;
+ }
+ public boolean equals(Object o) {
+ if (o instanceof KeyguardQuery) {
+ KeyguardQuery r = (KeyguardQuery) o;
+ return Objects.equals(mAdmin, r.mAdmin) && mUserHandle == r.mUserHandle;
+ } else {
+ return false;
+ }
+ }
+ public int hashCode() {
+ return ((mAdmin != null) ? mAdmin.hashCode() : 0) * 13 + mUserHandle;
+ }
+ }
+
+ // The query handler does not cache wildcard user IDs, although they should never
+ // appear in the query.
+ private static final BinderApi sGetKeyguardDisabledFeatures =
+ new BinderApi("getKeyguardDisabledFeatures");
+ private BinderCache<KeyguardQuery, Integer> mGetKeyGuardDisabledFeaturesCache =
+ new BinderCache<>(sGetKeyguardDisabledFeatures,
+ new IpcDataCache.QueryHandler<KeyguardQuery, Integer>() {
+ @Override
+ public Integer apply(KeyguardQuery query) {
+ try {
+ return mService.getKeyguardDisabledFeatures(
+ query.mAdmin, query.mUserHandle, mParentInstance);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }});
+
+ /** @hide */
+ public static void disableGetKeyguardDisabledFeaturesCache() {
+ sGetKeyguardDisabledFeatures.disable();
+ }
+
/** @hide per-user version */
@UnsupportedAppUsage
public int getKeyguardDisabledFeatures(@Nullable ComponentName admin, int userHandle) {
if (mService != null) {
- try {
- return mService.getKeyguardDisabledFeatures(admin, userHandle, mParentInstance);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mGetKeyGuardDisabledFeaturesCache.query(new KeyguardQuery(admin, userHandle));
+ } else {
+ return KEYGUARD_DISABLE_FEATURES_NONE;
}
- return KEYGUARD_DISABLE_FEATURES_NONE;
}
/**
@@ -8769,6 +8864,24 @@ public class DevicePolicyManager {
return name != null ? name.getPackageName() : null;
}
+ private static final BinderApi sHasDeviceOwner =
+ new BinderApi("hasDeviceOwner");
+ private BinderCache<Void, Boolean> mHasDeviceOwnerCache =
+ new BinderCache<>(sHasDeviceOwner,
+ new IpcDataCache.QueryHandler<Void, Boolean>() {
+ @Override
+ public Boolean apply(Void query) {
+ try {
+ return mService.hasDeviceOwner();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }});
+ /** @hide */
+ public static void disableHasDeviceOwnerCache() {
+ sHasDeviceOwner.disable();
+ }
+
/**
* Called by the system to find out whether the device is managed by a Device Owner.
*
@@ -8781,11 +8894,7 @@ public class DevicePolicyManager {
@SystemApi
@SuppressLint("RequiresPermission")
public boolean isDeviceManaged() {
- try {
- return mService.hasDeviceOwner();
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ return mHasDeviceOwnerCache.query(null);
}
/**
@@ -9147,6 +9256,26 @@ public class DevicePolicyManager {
return null;
}
+ private final static BinderApi sGetProfileOwnerOrDeviceOwnerSupervisionComponent =
+ new BinderApi("getProfileOwnerOrDeviceOwnerSupervisionComponent");
+ private final BinderCache<UserHandle, ComponentName>
+ mGetProfileOwnerOrDeviceOwnerSupervisionComponentCache =
+ new BinderCache(sGetProfileOwnerOrDeviceOwnerSupervisionComponent,
+ new IpcDataCache.QueryHandler<UserHandle, ComponentName>() {
+ @Override
+ public ComponentName apply(UserHandle user) {
+ try {
+ return mService.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }});
+ /** @hide */
+ public static void disableGetProfileOwnerOrDeviceOwnerSupervisionComponentCache() {
+ sGetProfileOwnerOrDeviceOwnerSupervisionComponent.disable();
+ }
+
/**
* Returns the configured supervision app if it exists and is the device owner or policy owner.
* @hide
@@ -9154,11 +9283,7 @@ public class DevicePolicyManager {
public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
@NonNull UserHandle user) {
if (mService != null) {
- try {
- return mService.getProfileOwnerOrDeviceOwnerSupervisionComponent(user);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ return mGetProfileOwnerOrDeviceOwnerSupervisionComponentCache.query(user);
}
return null;
}
@@ -9204,6 +9329,24 @@ public class DevicePolicyManager {
return null;
}
+ private final static BinderApi sIsOrganizationOwnedDeviceWithManagedProfile =
+ new BinderApi("isOrganizationOwnedDeviceWithManagedProfile");
+ private final BinderCache<Void, Boolean> mIsOrganizationOwnedDeviceWithManagedProfileCache =
+ new BinderCache(sIsOrganizationOwnedDeviceWithManagedProfile,
+ new IpcDataCache.QueryHandler<Void, Boolean>() {
+ @Override
+ public Boolean apply(Void query) {
+ try {
+ return mService.isOrganizationOwnedDeviceWithManagedProfile();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }});
+ /** @hide */
+ public static void disableIsOrganizationOwnedDeviceWithManagedProfileCache() {
+ sIsOrganizationOwnedDeviceWithManagedProfile.disable();
+ }
+
/**
* Apps can use this method to find out if the device was provisioned as
* organization-owend device with a managed profile.
@@ -9220,11 +9363,7 @@ public class DevicePolicyManager {
public boolean isOrganizationOwnedDeviceWithManagedProfile() {
throwIfParentInstance("isOrganizationOwnedDeviceWithManagedProfile");
if (mService != null) {
- try {
- return mService.isOrganizationOwnedDeviceWithManagedProfile();
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ return mIsOrganizationOwnedDeviceWithManagedProfileCache.query(null);
}
return false;
}
@@ -12788,6 +12927,24 @@ public class DevicePolicyManager {
}
}
+ private final static BinderApi sGetDeviceOwnerOrganizationName =
+ new BinderApi("getDeviceOwnerOrganizationName");
+ private final BinderCache<Void, CharSequence> mGetDeviceOwnerOrganizationNameCache =
+ new BinderCache(sGetDeviceOwnerOrganizationName,
+ new IpcDataCache.QueryHandler<Void, CharSequence>() {
+ @Override
+ public CharSequence apply(Void query) {
+ try {
+ return mService.getDeviceOwnerOrganizationName();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }});
+ /** @hide */
+ public static void disableGetDeviceOwnerOrganizationNameCache() {
+ sGetDeviceOwnerOrganizationName.disable();
+ }
+
/**
* Called by the system to retrieve the name of the organization managing the device.
*
@@ -12800,11 +12957,25 @@ public class DevicePolicyManager {
@SystemApi
@SuppressLint("RequiresPermission")
public @Nullable CharSequence getDeviceOwnerOrganizationName() {
- try {
- return mService.getDeviceOwnerOrganizationName();
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ return mGetDeviceOwnerOrganizationNameCache.query(null);
+ }
+
+ private final static BinderApi sGetOrganizationNameForUser =
+ new BinderApi("getOrganizationNameForUser");
+ private final BinderCache<Integer, CharSequence> mGetOrganizationNameForUserCache =
+ new BinderCache(sGetOrganizationNameForUser,
+ new IpcDataCache.QueryHandler<Integer, CharSequence>() {
+ @Override
+ public CharSequence apply(Integer userHandle) {
+ try {
+ return mService.getOrganizationNameForUser(userHandle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }});
+ /** @hide */
+ public static void disableGetOrganizationNameForUserCache() {
+ sGetOrganizationNameForUser.disable();
}
/**
@@ -12816,11 +12987,7 @@ public class DevicePolicyManager {
* @hide
*/
public @Nullable CharSequence getOrganizationNameForUser(int userHandle) {
- try {
- return mService.getOrganizationNameForUser(userHandle);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ return mGetOrganizationNameForUserCache.query(userHandle);
}
/**
@@ -13205,6 +13372,25 @@ public class DevicePolicyManager {
}
}
+ private final static BinderApi sNetworkLoggingApi = new BinderApi("isNetworkLoggingEnabled");
+ private BinderCache<ComponentName, Boolean> mIsNetworkLoggingEnabledCache =
+ new BinderCache<>(sNetworkLoggingApi,
+ new IpcDataCache.QueryHandler<ComponentName, Boolean>() {
+ @Override
+ public Boolean apply(ComponentName admin) {
+ try {
+ return mService.isNetworkLoggingEnabled(
+ admin, mContext.getPackageName());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }});
+
+ /** @hide */
+ public static void disableIsNetworkLoggingEnabled() {
+ sNetworkLoggingApi.disable();
+ }
+
/**
* Return whether network logging is enabled by a device owner or profile owner of
* a managed profile.
@@ -13219,11 +13405,7 @@ public class DevicePolicyManager {
*/
public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) {
throwIfParentInstance("isNetworkLoggingEnabled");
- try {
- return mService.isNetworkLoggingEnabled(admin, mContext.getPackageName());
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ return mIsNetworkLoggingEnabledCache.query(admin);
}
/**
diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java
index a086a308d0d9..c4ac483fd10e 100644
--- a/core/java/android/content/AbstractThreadedSyncAdapter.java
+++ b/core/java/android/content/AbstractThreadedSyncAdapter.java
@@ -21,6 +21,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa
import android.accounts.Account;
import android.annotation.MainThread;
import android.annotation.NonNull;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -171,8 +172,17 @@ public abstract class AbstractThreadedSyncAdapter {
}
private class ISyncAdapterImpl extends ISyncAdapter.Stub {
+ private void enforceCallerSystem() {
+ final long callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ android.util.EventLog.writeEvent(0x534e4554, "203229608", -1, "");
+ return;
+ }
+ }
+
@Override
public void onUnsyncableAccount(ISyncAdapterUnsyncableAccountCallback cb) {
+ enforceCallerSystem();
Handler.getMain().sendMessage(obtainMessage(
AbstractThreadedSyncAdapter::handleOnUnsyncableAccount,
AbstractThreadedSyncAdapter.this, cb));
@@ -187,6 +197,8 @@ public abstract class AbstractThreadedSyncAdapter {
}
Log.d(TAG, "startSync() start " + authority + " " + account + " " + extras);
}
+ enforceCallerSystem();
+
try {
final SyncContext syncContextClient = new SyncContext(syncContext);
@@ -242,6 +254,7 @@ public abstract class AbstractThreadedSyncAdapter {
@Override
public void cancelSync(ISyncContext syncContext) {
+ enforceCallerSystem();
try {
// synchronize to make sure that mSyncThreads doesn't change between when we
// check it and when we use it
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 29221b801ef6..6d2cdf3cfadc 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -594,8 +594,16 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
mAuthenticationCallback = callback;
mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
- final long authId = mService.authenticate(mToken, operationId, sensorId, userId,
- mServiceReceiver, mContext.getOpPackageName(), ignoreEnrollmentState);
+ final long authId =
+ mService.authenticate(
+ mToken,
+ operationId,
+ sensorId,
+ userId,
+ mServiceReceiver,
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
+ ignoreEnrollmentState);
if (cancel != null) {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
}
@@ -838,7 +846,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public List<Fingerprint> getEnrolledFingerprints(int userId) {
if (mService != null) try {
- return mService.getEnrolledFingerprints(userId, mContext.getOpPackageName());
+ return mService.getEnrolledFingerprints(
+ userId, mContext.getOpPackageName(), mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -997,7 +1006,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
INTERACT_ACROSS_USERS})
public boolean hasEnrolledFingerprints(int userId) {
if (mService != null) try {
- return mService.hasEnrolledFingerprintsDeprecated(userId, mContext.getOpPackageName());
+ return mService.hasEnrolledFingerprintsDeprecated(
+ userId, mContext.getOpPackageName(), mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1021,7 +1031,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
if (mService != null) {
try {
- return mService.isHardwareDetectedDeprecated(mContext.getOpPackageName());
+ return mService.isHardwareDetectedDeprecated(
+ mContext.getOpPackageName(), mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1331,7 +1342,11 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
private void cancelAuthentication(long requestId) {
if (mService != null) try {
- mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
+ mService.cancelAuthentication(
+ mToken,
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
+ requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 12114aa3fa33..d60bb6ef1543 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -52,7 +52,7 @@ interface IFingerprintService {
// permission. This is effectively deprecated, since it only comes through FingerprintManager
// now. A requestId is returned that can be used to cancel this operation.
long authenticate(IBinder token, long operationId, int sensorId, int userId,
- IFingerprintServiceReceiver receiver, String opPackageName,
+ IFingerprintServiceReceiver receiver, String opPackageName, String attributionTag,
boolean shouldIgnoreEnrollmentState);
// Uses the fingerprint hardware to detect for the presence of a finger, without giving details
@@ -74,7 +74,7 @@ interface IFingerprintService {
void startPreparedClient(int sensorId, int cookie);
// Cancel authentication for the given requestId.
- void cancelAuthentication(IBinder token, String opPackageName, long requestId);
+ void cancelAuthentication(IBinder token, String opPackageName, String attributionTag, long requestId);
// Cancel finger detection for the given requestId.
void cancelFingerprintDetect(IBinder token, String opPackageName, long requestId);
@@ -101,10 +101,10 @@ interface IFingerprintService {
void rename(int fingerId, int userId, String name);
// Get a list of enrolled fingerprints in the given userId.
- List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName);
+ List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName, String attributionTag);
// Determine if the HAL is loaded and ready. Meant to support the deprecated FingerprintManager APIs
- boolean isHardwareDetectedDeprecated(String opPackageName);
+ boolean isHardwareDetectedDeprecated(String opPackageName, String attributionTag);
// Determine if the specified HAL is loaded and ready
boolean isHardwareDetected(int sensorId, String opPackageName);
@@ -116,7 +116,7 @@ interface IFingerprintService {
void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge);
// Determine if a user has at least one enrolled fingerprint. Meant to support the deprecated FingerprintManager APIs
- boolean hasEnrolledFingerprintsDeprecated(int userId, String opPackageName);
+ boolean hasEnrolledFingerprintsDeprecated(int userId, String opPackageName, String attributionTag);
// Determine if a user has at least one enrolled fingerprint.
boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName);
diff --git a/core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl b/core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl
index 69f29111635c..b2ddef9c29a6 100644
--- a/core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl
@@ -22,6 +22,8 @@ import android.hardware.hdmi.HdmiDeviceInfo;
* Callback interface definition for HDMI client to get informed of
* the CEC logical device status change event.
*
+ * Only to be used on TV panel and Audio System devices (b/226317598).
+ *
* @hide
*/
oneway interface IHdmiDeviceEventListener {
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 295bba8618d3..2664f05a9664 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -549,6 +549,7 @@ public final class Parcel {
*/
public final void recycle() {
if (DEBUG_RECYCLE) mStack = null;
+ mClassCookies = null;
freeBuffer();
if (mOwnsNativeParcelObject) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 07c770ffa0a6..d8d9c7fc259e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7230,6 +7230,13 @@ public final class Settings {
*/
public static final String LOCATION_SHOW_SYSTEM_OPS = "locationShowSystemOps";
+
+ /**
+ * Whether or not an indicator experiment has started.
+ * @hide
+ */
+ public static final String LOCATION_INDICATOR_EXPERIMENT_STARTED =
+ "locationIndicatorExperimentStarted";
/**
* A flag containing settings used for biometric weak
* @hide
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 431a88d324d8..1507c87c0452 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -105,6 +105,12 @@ public final class FillRequest implements Parcelable {
*/
public static final @RequestFlags int FLAG_SUPPORTS_FILL_DIALOG = 0x40;
+ /**
+ * Indicates the ime is showing while request coming.
+ * @hide
+ */
+ public static final @RequestFlags int FLAG_IME_SHOWING = 0x80;
+
/** @hide */
public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
@@ -201,7 +207,8 @@ public final class FillRequest implements Parcelable {
FLAG_COMPATIBILITY_MODE_REQUEST,
FLAG_PASSWORD_INPUT_TYPE,
FLAG_VIEW_NOT_FOCUSED,
- FLAG_SUPPORTS_FILL_DIALOG
+ FLAG_SUPPORTS_FILL_DIALOG,
+ FLAG_IME_SHOWING
})
@Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
@@ -227,6 +234,8 @@ public final class FillRequest implements Parcelable {
return "FLAG_VIEW_NOT_FOCUSED";
case FLAG_SUPPORTS_FILL_DIALOG:
return "FLAG_SUPPORTS_FILL_DIALOG";
+ case FLAG_IME_SHOWING:
+ return "FLAG_IME_SHOWING";
default: return Integer.toHexString(value);
}
}
@@ -302,7 +311,8 @@ public final class FillRequest implements Parcelable {
| FLAG_COMPATIBILITY_MODE_REQUEST
| FLAG_PASSWORD_INPUT_TYPE
| FLAG_VIEW_NOT_FOCUSED
- | FLAG_SUPPORTS_FILL_DIALOG);
+ | FLAG_SUPPORTS_FILL_DIALOG
+ | FLAG_IME_SHOWING);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
this.mDelayedFillIntentSender = delayedFillIntentSender;
@@ -462,7 +472,8 @@ public final class FillRequest implements Parcelable {
| FLAG_COMPATIBILITY_MODE_REQUEST
| FLAG_PASSWORD_INPUT_TYPE
| FLAG_VIEW_NOT_FOCUSED
- | FLAG_SUPPORTS_FILL_DIALOG);
+ | FLAG_SUPPORTS_FILL_DIALOG
+ | FLAG_IME_SHOWING);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
this.mDelayedFillIntentSender = delayedFillIntentSender;
@@ -484,10 +495,10 @@ public final class FillRequest implements Parcelable {
};
@DataClass.Generated(
- time = 1647644111186L,
+ time = 1647856966565L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
- inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java
index f5025f7a9e99..eb53b425a228 100644
--- a/core/java/android/util/SparseSetArray.java
+++ b/core/java/android/util/SparseSetArray.java
@@ -21,9 +21,26 @@ package android.util;
* @hide
*/
public class SparseSetArray<T> {
- private final SparseArray<ArraySet<T>> mData = new SparseArray<>();
+ private final SparseArray<ArraySet<T>> mData;
public SparseSetArray() {
+ mData = new SparseArray<>();
+ }
+
+ /**
+ * Copy constructor
+ */
+ public SparseSetArray(SparseSetArray<T> src) {
+ final int arraySize = src.size();
+ mData = new SparseArray<>(arraySize);
+ for (int i = 0; i < arraySize; i++) {
+ final int key = src.keyAt(i);
+ final ArraySet<T> set = src.get(key);
+ final int setSize = set.size();
+ for (int j = 0; j < setSize; j++) {
+ add(key, set.valueAt(j));
+ }
+ }
}
/**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 6fa6d3964ad4..b05b7916d960 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,6 +16,7 @@
package android.view.autofill;
+import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
@@ -70,6 +71,7 @@ import android.view.ContentInfo;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRootImpl;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -647,6 +649,9 @@ public final class AutofillManager {
private final boolean mIsFillDialogEnabled;
+ // Indicates whether called the showAutofillDialog() method.
+ private boolean mShowAutofillDialogCalled = false;
+
/** @hide */
public interface AutofillClient {
/**
@@ -1103,6 +1108,14 @@ public final class AutofillManager {
notifyViewEntered(view, flags);
}
+ private int getImeStateFlag(View v) {
+ final WindowInsets rootWindowInsets = v.getRootWindowInsets();
+ if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) {
+ return FLAG_IME_SHOWING;
+ }
+ return 0;
+ }
+
@GuardedBy("mLock")
private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) {
if (isDisabledByServiceLocked()) {
@@ -1182,6 +1195,8 @@ public final class AutofillManager {
flags |= FLAG_PASSWORD_INPUT_TYPE;
}
+ flags |= getImeStateFlag(view);
+
if (!isActiveLocked()) {
// Starts new session.
startSessionLocked(id, null, value, flags);
@@ -1358,6 +1373,8 @@ public final class AutofillManager {
flags |= FLAG_PASSWORD_INPUT_TYPE;
}
+ flags |= getImeStateFlag(view);
+
if (!isActiveLocked()) {
// Starts new session.
startSessionLocked(id, bounds, null, flags);
@@ -1473,7 +1490,7 @@ public final class AutofillManager {
value = view.getAutofillValue();
}
- updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
+ updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, getImeStateFlag(view));
}
}
@@ -1498,7 +1515,7 @@ public final class AutofillManager {
}
final AutofillId id = getAutofillId(view, virtualId);
- updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
+ updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, getImeStateFlag(view));
}
}
@@ -2057,6 +2074,8 @@ public final class AutofillManager {
mIdShownFillUi = null;
mIsFillRequested = false;
mRequireAutofill = false;
+ mShowAutofillDialogCalled = false;
+ mFillDialogTriggerIds = null;
if (resetEnteredIds) {
mEnteredIds = null;
}
@@ -3051,8 +3070,9 @@ public final class AutofillManager {
* dialog-style UI</a> are available for {@code view}, shows a dialog allowing the user to
* select a suggestion and returns {@code true}.
* <p>
- * The dialog may not be available if the autofill service does not support it, or if the
- * autofill request has not returned a response yet.
+ * The dialog may not be shown if the autofill service does not support it, if the autofill
+ * request has not returned a response yet, if the dialog was shown previously, or if the
+ * input method is already shown.
* <p>
* It is recommended apps to call this method the first time a user focuses on
* an autofill-able form, and to avoid showing the input method if the dialog is shown. If
@@ -3068,9 +3088,16 @@ public final class AutofillManager {
// TODO(b/210926084): Consider whether to include the one-time show logic within this method.
public boolean showAutofillDialog(@NonNull View view) {
Objects.requireNonNull(view);
- if (shouldShowAutofillDialog(view.getAutofillId())) {
- // If the id matches a trigger id, this will trigger the fill dialog.
- notifyViewEntered(view);
+ if (shouldShowAutofillDialog(view, view.getAutofillId())) {
+ mShowAutofillDialogCalled = true;
+ final WeakReference<View> wrView = new WeakReference<>(view);
+ // The id matches a trigger id, this will trigger the fill dialog.
+ post(() -> {
+ final View v = wrView.get();
+ if (v != null) {
+ notifyViewEntered(v);
+ }
+ });
return true;
}
return false;
@@ -3099,18 +3126,33 @@ public final class AutofillManager {
*/
public boolean showAutofillDialog(@NonNull View view, int virtualId) {
Objects.requireNonNull(view);
- if (shouldShowAutofillDialog(getAutofillId(view, virtualId))) {
- // If the id matches a trigger id, this will trigger the fill dialog.
- notifyViewEntered(view, virtualId, /* bounds= */ null, /* flags= */ 0);
+ if (shouldShowAutofillDialog(view, getAutofillId(view, virtualId))) {
+ mShowAutofillDialogCalled = true;
+ final WeakReference<View> wrView = new WeakReference<>(view);
+ // The id matches a trigger id, this will trigger the fill dialog.
+ post(() -> {
+ final View v = wrView.get();
+ if (v != null) {
+ notifyViewEntered(v, virtualId, /* bounds= */ null, /* flags= */ 0);
+ }
+ });
return true;
}
return false;
}
- private boolean shouldShowAutofillDialog(AutofillId id) {
- if (!hasFillDialogUiFeature() || mFillDialogTriggerIds == null) {
+ private boolean shouldShowAutofillDialog(View view, AutofillId id) {
+ if (!hasFillDialogUiFeature()
+ || mShowAutofillDialogCalled
+ || mFillDialogTriggerIds == null) {
return false;
}
+
+ if (getImeStateFlag(view) == FLAG_IME_SHOWING) {
+ // IME is showing
+ return false;
+ }
+
final int size = mFillDialogTriggerIds.size();
for (int i = 0; i < size; i++) {
AutofillId fillId = mFillDialogTriggerIds.get(i);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 6f83a45ce7c6..f9c964158e2e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -11458,7 +11458,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = getInputMethodManager();
viewClicked(imm);
- if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
+ if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null
+ && !showAutofillDialog()) {
imm.showSoftInput(this, 0);
}
@@ -11476,6 +11477,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return superResult;
}
+ /**
+ * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI.
+ * If autofill suggestions are available when the user clicks on a field that supports filling
+ * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area
+ * to display the datasets, so it is easy for users to pay attention to the datasets and
+ * selecting a dataset. The autofill dialog is shown as the bottom sheet, the better
+ * experience is not to show the IME if there is a fill dialog.
+ */
+ private boolean showAutofillDialog() {
+ final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
+ if (afm != null) {
+ return afm.showAutofillDialog(this);
+ }
+ return false;
+ }
+
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (mMovement != null && mText instanceof Spannable && mLayout != null) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index fa58a71f25b9..29ca3bffb6aa 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2403,6 +2403,9 @@
<!-- Limit of how long the device can remain unlocked due to attention checking. -->
<integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes. -->
+ <!-- The prefix of dream component names that are loggable. If empty, logs "other" for all. -->
+ <string name ="config_loggable_dream_prefix" translatable="false"></string>
+
<!-- ComponentName of a dream to show whenever the system would otherwise have
gone to sleep. When the PowerManager is asked to go to sleep, it will instead
try to start this dream if possible. The dream should typically call startDozing()
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f366a53f6341..8fa5dd2c756b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2235,6 +2235,7 @@
<java-symbol type="array" name="config_supportedDreamComplications" />
<java-symbol type="array" name="config_dreamComplicationsEnabledByDefault" />
<java-symbol type="array" name="config_disabledDreamComponents" />
+ <java-symbol type="string" name="config_loggable_dream_prefix" />
<java-symbol type="string" name="config_dozeComponent" />
<java-symbol type="string" name="enable_explore_by_touch_warning_title" />
<java-symbol type="string" name="enable_explore_by_touch_warning_message" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index c480197d23dc..d15b8c169535 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -24,59 +24,19 @@ import android.util.IntProperty
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
+import kotlin.math.max
+import kotlin.math.min
/**
* A class that allows changes in bounds within a view hierarchy to animate seamlessly between the
* start and end state.
*/
class ViewHierarchyAnimator {
- // TODO(b/221418522): make this private once it can't be passed as an arg anymore.
- enum class Bound(val label: String, val overrideTag: Int) {
- LEFT("left", R.id.tag_override_left) {
- override fun setValue(view: View, value: Int) {
- view.left = value
- }
-
- override fun getValue(view: View): Int {
- return view.left
- }
- },
- TOP("top", R.id.tag_override_top) {
- override fun setValue(view: View, value: Int) {
- view.top = value
- }
-
- override fun getValue(view: View): Int {
- return view.top
- }
- },
- RIGHT("right", R.id.tag_override_right) {
- override fun setValue(view: View, value: Int) {
- view.right = value
- }
-
- override fun getValue(view: View): Int {
- return view.right
- }
- },
- BOTTOM("bottom", R.id.tag_override_bottom) {
- override fun setValue(view: View, value: Int) {
- view.bottom = value
- }
-
- override fun getValue(view: View): Int {
- return view.bottom
- }
- };
-
- abstract fun setValue(view: View, value: Int)
- abstract fun getValue(view: View): Int
- }
-
companion object {
/** Default values for the animation. These can all be overridden at call time. */
private const val DEFAULT_DURATION = 500L
- private val DEFAULT_INTERPOLATOR = Interpolators.EMPHASIZED
+ private val DEFAULT_INTERPOLATOR = Interpolators.STANDARD
+ private val DEFAULT_ADDITION_INTERPOLATOR = Interpolators.STANDARD_DECELERATE
private val DEFAULT_BOUNDS = setOf(Bound.LEFT, Bound.TOP, Bound.RIGHT, Bound.BOTTOM)
/** The properties used to animate the view bounds. */
@@ -112,6 +72,9 @@ class ViewHierarchyAnimator {
* Successive calls to this method override the previous settings ([interpolator] and
* [duration]). The changes take effect on the next animation.
*
+ * Returns true if the [rootView] is already visible and will be animated, false otherwise.
+ * To animate the addition of a view, see [animateAddition].
+ *
* TODO(b/221418522): remove the ability to select which bounds to animate and always
* animate all of them.
*/
@@ -121,8 +84,8 @@ class ViewHierarchyAnimator {
bounds: Set<Bound> = DEFAULT_BOUNDS,
interpolator: Interpolator = DEFAULT_INTERPOLATOR,
duration: Long = DEFAULT_DURATION
- ) {
- animate(rootView, bounds, interpolator, duration, false /* ephemeral */)
+ ): Boolean {
+ return animate(rootView, bounds, interpolator, duration, ephemeral = false)
}
/**
@@ -138,8 +101,8 @@ class ViewHierarchyAnimator {
bounds: Set<Bound> = DEFAULT_BOUNDS,
interpolator: Interpolator = DEFAULT_INTERPOLATOR,
duration: Long = DEFAULT_DURATION
- ) {
- animate(rootView, bounds, interpolator, duration, true /* ephemeral */)
+ ): Boolean {
+ return animate(rootView, bounds, interpolator, duration, ephemeral = true)
}
private fun animate(
@@ -148,9 +111,42 @@ class ViewHierarchyAnimator {
interpolator: Interpolator,
duration: Long,
ephemeral: Boolean
- ) {
- val listener = createListener(bounds, interpolator, duration, ephemeral)
+ ): Boolean {
+ if (!isVisible(
+ rootView.visibility,
+ rootView.left,
+ rootView.top,
+ rootView.right,
+ rootView.bottom
+ )
+ ) {
+ return false
+ }
+
+ val listener = createUpdateListener(bounds, interpolator, duration, ephemeral)
recursivelyAddListener(rootView, listener)
+ return true
+ }
+
+ /**
+ * Returns a new [View.OnLayoutChangeListener] that when called triggers a layout animation
+ * for the specified [bounds], using [interpolator] and [duration].
+ *
+ * If [ephemeral] is true, the listener is unregistered after the first animation. Otherwise
+ * it keeps listening for further updates.
+ */
+ private fun createUpdateListener(
+ bounds: Set<Bound>,
+ interpolator: Interpolator,
+ duration: Long,
+ ephemeral: Boolean
+ ): View.OnLayoutChangeListener {
+ return createListener(
+ bounds,
+ interpolator,
+ duration,
+ ephemeral
+ )
}
/**
@@ -173,11 +169,87 @@ class ViewHierarchyAnimator {
}
}
+ /**
+ * Instruct the animator to watch for changes to the layout of [rootView] and its children,
+ * and animate the next time the hierarchy appears after not being visible. It uses the
+ * given [interpolator] and [duration].
+ *
+ * The start state of the animation is controlled by [origin]. This value can be any of the
+ * four corners, any of the four edges, or the center of the view. If any margins are added
+ * on the side(s) of the origin, the translation of those margins can be included by
+ * specifying [includeMargins].
+ *
+ * Returns true if the [rootView] is invisible and will be animated, false otherwise. To
+ * animate an already visible view, see [animate] and [animateNextUpdate].
+ *
+ * Then animator unregisters itself once the first addition animation is complete.
+ */
+ @JvmOverloads
+ fun animateAddition(
+ rootView: View,
+ origin: Hotspot = Hotspot.CENTER,
+ interpolator: Interpolator = DEFAULT_ADDITION_INTERPOLATOR,
+ duration: Long = DEFAULT_DURATION,
+ includeMargins: Boolean = false
+ ): Boolean {
+ if (isVisible(
+ rootView.visibility,
+ rootView.left,
+ rootView.top,
+ rootView.right,
+ rootView.bottom
+ )
+ ) {
+ return false
+ }
+
+ val listener = createAdditionListener(
+ origin, interpolator, duration, ignorePreviousValues = !includeMargins
+ )
+ recursivelyAddListener(rootView, listener)
+ return true
+ }
+
+ /**
+ * Returns a new [View.OnLayoutChangeListener] that on the next call triggers a layout
+ * addition animation from the given [origin], using [interpolator] and [duration].
+ *
+ * If [ignorePreviousValues] is true, the animation will only span the area covered by the
+ * new bounds. Otherwise it will include the margins between the previous and new bounds.
+ */
+ private fun createAdditionListener(
+ origin: Hotspot,
+ interpolator: Interpolator,
+ duration: Long,
+ ignorePreviousValues: Boolean
+ ): View.OnLayoutChangeListener {
+ return createListener(
+ DEFAULT_BOUNDS,
+ interpolator,
+ duration,
+ ephemeral = true,
+ origin = origin,
+ ignorePreviousValues = ignorePreviousValues
+ )
+ }
+
+ /**
+ * Returns a new [View.OnLayoutChangeListener] that when called triggers a layout animation
+ * for the specified [bounds], using [interpolator] and [duration].
+ *
+ * If [ephemeral] is true, the listener is unregistered after the first animation. Otherwise
+ * it keeps listening for further updates.
+ *
+ * [origin] specifies whether the start values should be determined by a hotspot, and
+ * [ignorePreviousValues] controls whether the previous values should be taken into account.
+ */
private fun createListener(
bounds: Set<Bound>,
interpolator: Interpolator,
duration: Long,
- ephemeral: Boolean
+ ephemeral: Boolean,
+ origin: Hotspot? = null,
+ ignorePreviousValues: Boolean = false
): View.OnLayoutChangeListener {
return object : View.OnLayoutChangeListener {
override fun onLayoutChange(
@@ -186,21 +258,21 @@ class ViewHierarchyAnimator {
top: Int,
right: Int,
bottom: Int,
- oldLeft: Int,
- oldTop: Int,
- oldRight: Int,
- oldBottom: Int
+ previousLeft: Int,
+ previousTop: Int,
+ previousRight: Int,
+ previousBottom: Int
) {
if (view == null) return
- val startLeft = getBound(view, Bound.LEFT) ?: oldLeft
- val startTop = getBound(view, Bound.TOP) ?: oldTop
- val startRight = getBound(view, Bound.RIGHT) ?: oldRight
- val startBottom = getBound(view, Bound.BOTTOM) ?: oldBottom
+ val startLeft = getBound(view, Bound.LEFT) ?: previousLeft
+ val startTop = getBound(view, Bound.TOP) ?: previousTop
+ val startRight = getBound(view, Bound.RIGHT) ?: previousRight
+ val startBottom = getBound(view, Bound.BOTTOM) ?: previousBottom
(view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel()
- if (view.visibility == View.GONE || view.visibility == View.INVISIBLE) {
+ if (!isVisible(view.visibility, left, top, right, bottom)) {
setBound(view, Bound.LEFT, left)
setBound(view, Bound.TOP, top)
setBound(view, Bound.RIGHT, right)
@@ -208,11 +280,17 @@ class ViewHierarchyAnimator {
return
}
- val startValues = mapOf(
- Bound.LEFT to startLeft,
- Bound.TOP to startTop,
- Bound.RIGHT to startRight,
- Bound.BOTTOM to startBottom
+ val startValues = processStartValues(
+ origin,
+ left,
+ top,
+ right,
+ bottom,
+ startLeft,
+ startTop,
+ startRight,
+ startBottom,
+ ignorePreviousValues
)
val endValues = mapOf(
Bound.LEFT to left,
@@ -221,11 +299,12 @@ class ViewHierarchyAnimator {
Bound.BOTTOM to bottom
)
- val boundsToAnimate = bounds.toMutableSet()
- if (left == startLeft) boundsToAnimate.remove(Bound.LEFT)
- if (top == startTop) boundsToAnimate.remove(Bound.TOP)
- if (right == startRight) boundsToAnimate.remove(Bound.RIGHT)
- if (bottom == startBottom) boundsToAnimate.remove(Bound.BOTTOM)
+ val boundsToAnimate = mutableSetOf<Bound>()
+ bounds.forEach { bound ->
+ if (endValues.getValue(bound) != startValues.getValue(bound)) {
+ boundsToAnimate.add(bound)
+ }
+ }
if (boundsToAnimate.isNotEmpty()) {
startAnimation(
@@ -242,11 +321,136 @@ class ViewHierarchyAnimator {
}
}
+ /**
+ * Returns whether the given [visibility] and bounds are consistent with a view being
+ * currently visible on screen.
+ */
+ private fun isVisible(
+ visibility: Int,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int
+ ): Boolean {
+ return visibility == View.VISIBLE && left != right && top != bottom
+ }
+
+ /**
+ * Compute the actual starting values based on the requested [origin] and on
+ * [ignorePreviousValues].
+ *
+ * If [origin] is null, the resolved start values will be the same as those passed in, or
+ * the same as the new values if [ignorePreviousValues] is true. If [origin] is not null,
+ * the start values are resolved based on it, and [ignorePreviousValues] controls whether or
+ * not newly introduced margins are included.
+ *
+ * Base case
+ * 1) origin=TOP
+ * x---------x x---------x x---------x x---------x x---------x
+ * x---------x | | | | | |
+ * -> -> x---------x -> | | -> | |
+ * x---------x | |
+ * x---------x
+ * 2) origin=BOTTOM_LEFT
+ * x---------x
+ * x-------x | |
+ * -> -> x----x -> | | -> | |
+ * x--x | | | | | |
+ * x x--x x----x x-------x x---------x
+ * 3) origin=CENTER
+ * x---------x
+ * x-----x x-------x | |
+ * x -> x---x -> | | -> | | -> | |
+ * x-----x x-------x | |
+ * x---------x
+ *
+ * In case the start and end values differ in the direction of the origin, and
+ * [ignorePreviousValues] is false, the previous values are used and a translation is
+ * included in addition to the view expansion.
+ *
+ * origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70)
+ * x
+ * x--x
+ * x--x x----x
+ * -> -> | | -> x------x
+ * x----x | |
+ * | |
+ * x------x
+ */
+ private fun processStartValues(
+ origin: Hotspot?,
+ newLeft: Int,
+ newTop: Int,
+ newRight: Int,
+ newBottom: Int,
+ previousLeft: Int,
+ previousTop: Int,
+ previousRight: Int,
+ previousBottom: Int,
+ ignorePreviousValues: Boolean
+ ): Map<Bound, Int> {
+ val startLeft = if (ignorePreviousValues) newLeft else previousLeft
+ val startTop = if (ignorePreviousValues) newTop else previousTop
+ val startRight = if (ignorePreviousValues) newRight else previousRight
+ val startBottom = if (ignorePreviousValues) newBottom else previousBottom
+
+ var left = startLeft
+ var top = startTop
+ var right = startRight
+ var bottom = startBottom
+
+ if (origin != null) {
+ left = when (origin) {
+ Hotspot.CENTER -> (newLeft + newRight) / 2
+ Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> min(startLeft, newLeft)
+ Hotspot.TOP, Hotspot.BOTTOM -> newLeft
+ Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> max(
+ startRight,
+ newRight
+ )
+ }
+ top = when (origin) {
+ Hotspot.CENTER -> (newTop + newBottom) / 2
+ Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> min(startTop, newTop)
+ Hotspot.LEFT, Hotspot.RIGHT -> newTop
+ Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> max(
+ startBottom,
+ newBottom
+ )
+ }
+ right = when (origin) {
+ Hotspot.CENTER -> (newLeft + newRight) / 2
+ Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> max(
+ startRight,
+ newRight
+ )
+ Hotspot.TOP, Hotspot.BOTTOM -> newRight
+ Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> min(startLeft, newLeft)
+ }
+ bottom = when (origin) {
+ Hotspot.CENTER -> (newTop + newBottom) / 2
+ Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> max(
+ startBottom,
+ newBottom
+ )
+ Hotspot.LEFT, Hotspot.RIGHT -> newBottom
+ Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> min(startTop, newTop)
+ }
+ }
+
+ return mapOf(
+ Bound.LEFT to left,
+ Bound.TOP to top,
+ Bound.RIGHT to right,
+ Bound.BOTTOM to bottom
+ )
+ }
+
private fun recursivelyAddListener(view: View, listener: View.OnLayoutChangeListener) {
// Make sure that only one listener is active at a time.
- val oldListener = view.getTag(R.id.tag_layout_listener)
- if (oldListener != null && oldListener is View.OnLayoutChangeListener) {
- view.removeOnLayoutChangeListener(oldListener)
+ val previousListener = view.getTag(R.id.tag_layout_listener)
+ if (previousListener != null && previousListener is View.OnLayoutChangeListener) {
+ view.removeOnLayoutChangeListener(previousListener)
}
view.addOnLayoutChangeListener(listener)
@@ -268,9 +472,12 @@ class ViewHierarchyAnimator {
}
/**
- * Initiates the animation of a single bound by creating the animator, registering it with
- * the [view], and starting it. If [ephemeral], the layout change listener is unregistered
- * at the end of the animation, so no more animations happen.
+ * Initiates the animation of the requested [bounds] between [startValues] and [endValues]
+ * by creating the animator, registering it with the [view], and starting it using
+ * [interpolator] and [duration].
+ *
+ * If [ephemeral] is true, the layout change listener is unregistered at the end of the
+ * animation, so no more animations happen.
*/
private fun startAnimation(
view: View,
@@ -325,4 +532,52 @@ class ViewHierarchyAnimator {
animator.start()
}
}
+
+ /** An enum used to determine the origin of addition animations. */
+ enum class Hotspot {
+ CENTER, LEFT, TOP_LEFT, TOP, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM, BOTTOM_LEFT
+ }
+
+ // TODO(b/221418522): make this private once it can't be passed as an arg anymore.
+ enum class Bound(val label: String, val overrideTag: Int) {
+ LEFT("left", R.id.tag_override_left) {
+ override fun setValue(view: View, value: Int) {
+ view.left = value
+ }
+
+ override fun getValue(view: View): Int {
+ return view.left
+ }
+ },
+ TOP("top", R.id.tag_override_top) {
+ override fun setValue(view: View, value: Int) {
+ view.top = value
+ }
+
+ override fun getValue(view: View): Int {
+ return view.top
+ }
+ },
+ RIGHT("right", R.id.tag_override_right) {
+ override fun setValue(view: View, value: Int) {
+ view.right = value
+ }
+
+ override fun getValue(view: View): Int {
+ return view.right
+ }
+ },
+ BOTTOM("bottom", R.id.tag_override_bottom) {
+ override fun setValue(view: View, value: Int) {
+ view.bottom = value
+ }
+
+ override fun getValue(view: View): Int {
+ return view.bottom
+ }
+ };
+
+ abstract fun setValue(view: View, value: Int)
+ abstract fun getValue(view: View): Int
+ }
}
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
index cb63300f819b..3f56bafef134 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
@@ -17,13 +17,8 @@
<TextClock
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/date_view"
+ style="@style/clock_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingHorizontal="@dimen/dream_overlay_complication_shadow_padding"
- android:gravity="center_horizontal"
- android:textColor="@android:color/white"
- android:shadowColor="@color/keyguard_shadow_color"
- android:shadowRadius="?attr/shadowRadius"
android:format12Hour="EEE, MMM d"
- android:format24Hour="EEE, MMM d"
- android:textSize="@dimen/dream_overlay_complication_clock_date_text_size"/>
+ android:format24Hour="EEE, MMM d"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
index 76fe58c29852..f05922fb395c 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
@@ -17,10 +17,6 @@
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/weather_view"
+ style="@style/clock_subtitle"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingHorizontal="@dimen/dream_overlay_complication_shadow_padding"
- android:textColor="@android:color/white"
- android:shadowColor="@color/keyguard_shadow_color"
- android:shadowRadius="?attr/shadowRadius"
- android:textSize="@dimen/dream_overlay_complication_weather_text_size"/>
+ android:layout_height="wrap_content" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5217dce3d983..7702724cec16 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1382,8 +1382,7 @@
<!-- Dream overlay complications related dimensions -->
<dimen name="dream_overlay_complication_clock_time_text_size">72sp</dimen>
- <dimen name="dream_overlay_complication_clock_date_text_size">18sp</dimen>
- <dimen name="dream_overlay_complication_weather_text_size">18sp</dimen>
+ <dimen name="dream_overlay_complication_clock_subtitle_text_size">18sp</dimen>
<dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
<dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
<dimen name="dream_overlay_complication_shadow_padding">2dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 96ce5454e795..847aefdd67ce 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1133,4 +1133,18 @@
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
+
+ <!-- Dream Overlay Complication UI styles -->
+ <style name="clock_subtitle">
+ <item name="android:textSize">@dimen/dream_overlay_complication_clock_subtitle_text_size
+ </item>
+ <item name="android:paddingHorizontal">@dimen/dream_overlay_complication_shadow_padding
+ </item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textColor">@android:color/white</item>
+ <item name="android:gravity">center_horizontal</item>
+ <item name="android:ellipsize">none</item>
+ <item name="android:shadowColor">@color/keyguard_shadow_color</item>
+ <item name="android:shadowRadius">?attr/shadowRadius</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index ea366eaf4568..ffded659c2ac 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -652,7 +652,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
// pre-emptively set to true to hide view
mIsBouncerShowing = true;
if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
- mAuthRippleController.showRipple(FINGERPRINT);
+ mAuthRippleController.showUnlockRipple(FINGERPRINT);
}
updateVisibility();
if (mOnGestureDetectedRunnable != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index b811c51c952b..0cde745d2e12 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -52,7 +52,11 @@ import javax.inject.Inject
import javax.inject.Provider
/***
- * Controls the ripple effect that shows when authentication is successful.
+ * Controls two ripple effects:
+ * 1. Unlocked ripple: shows when authentication is successful
+ * 2. UDFPS dwell ripple: shows when the user has their finger down on the UDFPS area and reacts
+ * to errors and successes
+ *
* The ripple uses the accent color of the current theme.
*/
@CentralSurfacesScope
@@ -115,7 +119,7 @@ class AuthRippleController @Inject constructor(
notificationShadeWindowController.setForcePluginOpen(false, this)
}
- fun showRipple(biometricSourceType: BiometricSourceType?) {
+ fun showUnlockRipple(biometricSourceType: BiometricSourceType?) {
if (!(keyguardUpdateMonitor.isKeyguardVisible || keyguardUpdateMonitor.isDreaming) ||
keyguardUpdateMonitor.userNeedsStrongAuth()) {
return
@@ -252,11 +256,16 @@ class AuthRippleController @Inject constructor(
biometricSourceType: BiometricSourceType?,
isStrongBiometric: Boolean
) {
- showRipple(biometricSourceType)
+ if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+ mView.fadeDwellRipple()
+ }
+ showUnlockRipple(biometricSourceType)
}
override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
- mView.retractRipple()
+ if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+ mView.retractDwellRipple()
+ }
}
override fun onBiometricAcquired(
@@ -264,8 +273,16 @@ class AuthRippleController @Inject constructor(
acquireInfo: Int
) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
- acquireInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_PARTIAL) {
- mView.retractRipple()
+ BiometricFingerprintConstants.shouldTurnOffHbm(acquireInfo) &&
+ acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) {
+ // received an 'acquiredBad' message, so immediately retract
+ mView.retractDwellRipple()
+ }
+ }
+
+ override fun onKeyguardBouncerChanged(bouncerIsOrWillBeShowing: Boolean) {
+ if (bouncerIsOrWillBeShowing) {
+ mView.fadeDwellRipple()
}
}
}
@@ -294,7 +311,7 @@ class AuthRippleController @Inject constructor(
}
override fun onFingerUp() {
- mView.retractRipple()
+ mView.retractDwellRipple()
}
}
@@ -337,12 +354,12 @@ class AuthRippleController @Inject constructor(
"fingerprint" -> {
updateSensorLocation()
pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
- showRipple(BiometricSourceType.FINGERPRINT)
+ showUnlockRipple(BiometricSourceType.FINGERPRINT)
}
"face" -> {
updateSensorLocation()
pw.println("face ripple sensorLocation=$faceSensorLocation")
- showRipple(BiometricSourceType.FACE)
+ showUnlockRipple(BiometricSourceType.FACE)
}
"custom" -> {
if (args.size != 3 ||
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index d67363079e17..378ae14f0327 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -35,12 +35,13 @@ import com.android.systemui.statusbar.charging.RippleShader
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
/**
- * Expanding ripple effect
- * - startUnlockedRipple for the transition from biometric authentication success to showing
- * launcher.
- * - startDwellRipple for the ripple expansion out when the user has their finger down on the UDFPS
- * sensor area
- * - retractRipple for the ripple animation inwards to signal a failure
+ * Handles two ripple effects: dwell ripple and unlocked ripple
+ * Dwell Ripple:
+ * - startDwellRipple: dwell ripple expands outwards around the biometric area
+ * - retractDwellRipple: retracts the dwell ripple to radius 0 to signal a failure
+ * - fadeDwellRipple: fades the dwell ripple away to alpha 0
+ * Unlocked ripple:
+ * - startUnlockedRipple: ripple expands from biometric auth location to the edges of the screen
*/
class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f)
@@ -52,6 +53,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
private var drawRipple: Boolean = false
private var lockScreenColorVal = Color.WHITE
+ private val fadeDuration = 83L
private val retractDuration = 400L
private var alphaInDuration: Long = 0
private var unlockedRippleInProgress: Boolean = false
@@ -59,7 +61,8 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
private val dwellPaint = Paint()
private val rippleShader = RippleShader()
private val ripplePaint = Paint()
- private var retractAnimator: Animator? = null
+ private var fadeDwellAnimator: Animator? = null
+ private var retractDwellAnimator: Animator? = null
private var dwellPulseOutAnimator: Animator? = null
private var dwellRadius: Float = 0f
set(value) {
@@ -112,15 +115,15 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
}
/**
- * Animate ripple inwards back to radius 0
+ * Animate dwell ripple inwards back to radius 0
*/
- fun retractRipple() {
- if (retractAnimator?.isRunning == true) {
+ fun retractDwellRipple() {
+ if (retractDwellAnimator?.isRunning == true || fadeDwellAnimator?.isRunning == true) {
return // let the animation finish
}
if (dwellPulseOutAnimator?.isRunning == true) {
- val retractRippleAnimator = ValueAnimator.ofFloat(dwellShader.progress, 0f)
+ val retractDwellRippleAnimator = ValueAnimator.ofFloat(dwellShader.progress, 0f)
.apply {
interpolator = retractInterpolator
duration = retractDuration
@@ -145,8 +148,8 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
}
}
- retractAnimator = AnimatorSet().apply {
- playTogether(retractRippleAnimator, retractAlphaAnimator)
+ retractDwellAnimator = AnimatorSet().apply {
+ playTogether(retractDwellRippleAnimator, retractAlphaAnimator)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
dwellPulseOutAnimator?.cancel()
@@ -164,6 +167,42 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
}
/**
+ * Animate ripple fade to alpha=0
+ */
+ fun fadeDwellRipple() {
+ if (fadeDwellAnimator?.isRunning == true) {
+ return // let the animation finish
+ }
+
+ if (dwellPulseOutAnimator?.isRunning == true || retractDwellAnimator?.isRunning == true) {
+ fadeDwellAnimator = ValueAnimator.ofInt(Color.alpha(dwellShader.color), 0).apply {
+ interpolator = Interpolators.LINEAR
+ duration = fadeDuration
+ addUpdateListener { animator ->
+ dwellShader.color = ColorUtils.setAlphaComponent(
+ dwellShader.color,
+ animator.animatedValue as Int
+ )
+ invalidate()
+ }
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator?) {
+ retractDwellAnimator?.cancel()
+ dwellPulseOutAnimator?.cancel()
+ drawDwell = true
+ }
+
+ override fun onAnimationEnd(animation: Animator?) {
+ drawDwell = false
+ resetDwellAlpha()
+ }
+ })
+ start()
+ }
+ }
+ }
+
+ /**
* Plays a ripple animation that grows to the dwellRadius with distortion.
*/
fun startDwellRipple(isDozing: Boolean) {
@@ -205,7 +244,8 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
- retractAnimator?.cancel()
+ retractDwellAnimator?.cancel()
+ fadeDwellAnimator?.cancel()
visibility = VISIBLE
drawDwell = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayNotificationCountProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayNotificationCountProvider.java
index aaa34ed32c7e..6589f26dbde2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayNotificationCountProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayNotificationCountProvider.java
@@ -21,6 +21,7 @@ import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.policy.CallbackController;
@@ -30,6 +31,7 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -78,10 +80,16 @@ public class DreamOverlayNotificationCountProvider
@Inject
public DreamOverlayNotificationCountProvider(
- NotificationListener notificationListener) {
+ NotificationListener notificationListener,
+ @Background Executor bgExecutor) {
notificationListener.addNotificationHandler(mNotificationHandler);
- Arrays.stream(notificationListener.getActiveNotifications())
- .forEach(sbn -> mNotificationKeys.add(sbn.getKey()));
+
+ bgExecutor.execute(() -> {
+ Arrays.stream(notificationListener.getActiveNotifications())
+ .forEach(sbn -> mNotificationKeys.add(sbn.getKey()));
+ reportNotificationCountChanged();
+ }
+ );
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 20029fec4dd1..d4e2214f8b38 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -12,6 +12,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.InstanceId
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
@@ -59,7 +60,7 @@ class MediaCarouselController @Inject constructor(
falsingCollector: FalsingCollector,
falsingManager: FalsingManager,
dumpManager: DumpManager,
- private val mediaFlags: MediaFlags
+ private val logger: MediaUiEventLogger
) : Dumpable {
/**
* The current width of the carousel
@@ -119,7 +120,9 @@ class MediaCarouselController @Inject constructor(
private val mediaCarousel: MediaScrollView
val mediaCarouselScrollHandler: MediaCarouselScrollHandler
val mediaFrame: ViewGroup
- private lateinit var settingsButton: View
+ @VisibleForTesting
+ lateinit var settingsButton: View
+ private set
private val mediaContent: ViewGroup
private val pageIndicator: PageIndicator
private val visualStabilityCallback: OnReorderingAllowedListener
@@ -183,7 +186,8 @@ class MediaCarouselController @Inject constructor(
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
executor, this::onSwipeToDismiss, this::updatePageIndicatorLocation,
- this::closeGuts, falsingCollector, falsingManager, this::logSmartspaceImpression)
+ this::closeGuts, falsingCollector, falsingManager, this::logSmartspaceImpression,
+ logger)
isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
inflateSettingsButton()
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
@@ -220,7 +224,7 @@ class MediaCarouselController @Inject constructor(
MediaPlayerData.getMediaPlayer(key)?.let {
/* ktlint-disable max-line-length */
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mInstanceId,
+ it.mSmartspaceId,
it.mUid,
/* isRecommendationCard */ false,
intArrayOf(
@@ -239,12 +243,12 @@ class MediaCarouselController @Inject constructor(
// resume card is ranked first
MediaPlayerData.players().forEachIndexed { index, it ->
if (it.recommendationViewHolder == null) {
- it.mInstanceId = SmallHash.hash(it.mUid +
+ it.mSmartspaceId = SmallHash.hash(it.mUid +
systemClock.currentTimeMillis().toInt())
it.mIsImpressed = false
/* ktlint-disable max-line-length */
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mInstanceId,
+ it.mSmartspaceId,
it.mUid,
/* isRecommendationCard */ false,
intArrayOf(
@@ -291,12 +295,12 @@ class MediaCarouselController @Inject constructor(
// recommendation card is valid and ranked first
MediaPlayerData.players().forEachIndexed { index, it ->
if (it.recommendationViewHolder == null) {
- it.mInstanceId = SmallHash.hash(it.mUid +
+ it.mSmartspaceId = SmallHash.hash(it.mUid +
systemClock.currentTimeMillis().toInt())
it.mIsImpressed = false
/* ktlint-disable max-line-length */
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mInstanceId,
+ it.mSmartspaceId,
it.mUid,
/* isRecommendationCard */ false,
intArrayOf(
@@ -312,7 +316,7 @@ class MediaCarouselController @Inject constructor(
MediaPlayerData.getMediaPlayer(key)?.let {
/* ktlint-disable max-line-length */
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mInstanceId,
+ it.mSmartspaceId,
it.mUid,
/* isRecommendationCard */ true,
intArrayOf(
@@ -369,6 +373,7 @@ class MediaCarouselController @Inject constructor(
mediaFrame.addView(settingsButton)
mediaCarouselScrollHandler.onSettingsButtonUpdated(settings)
settingsButton.setOnClickListener {
+ logger.logCarouselSettings()
activityStarter.startActivity(settingsIntent, true /* dismissShade */)
}
}
@@ -752,7 +757,7 @@ class MediaCarouselController @Inject constructor(
return
}
logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
- mediaControlPanel.mInstanceId,
+ mediaControlPanel.mSmartspaceId,
mediaControlPanel.mUid,
isRecommendationCard,
intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging))
@@ -836,7 +841,7 @@ class MediaCarouselController @Inject constructor(
index, it ->
if (it.mIsImpressed) {
logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
- it.mInstanceId,
+ it.mSmartspaceId,
it.mUid,
it.recommendationViewHolder != null,
intArrayOf(it.surfaceForSmartspaceLogging),
@@ -846,6 +851,7 @@ class MediaCarouselController @Inject constructor(
it.mIsImpressed = false
}
}
+ logger.logSwipeDismiss()
mediaManager.onSwipeToDismiss()
}
@@ -881,7 +887,9 @@ internal object MediaPlayerData {
clickIntent = null,
device = null,
active = true,
- resumeAction = null)
+ resumeAction = null,
+ instanceId = InstanceId.fakeInstanceId(-1),
+ appUid = -1)
// Whether should prioritize Smartspace card.
internal var shouldPrioritizeSs: Boolean = false
private set
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index 5dc4bcfa92ff..ef49fd35d703 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -57,12 +57,13 @@ class MediaCarouselScrollHandler(
private val scrollView: MediaScrollView,
private val pageIndicator: PageIndicator,
private val mainExecutor: DelayableExecutor,
- private val dismissCallback: () -> Unit,
+ val dismissCallback: () -> Unit,
private var translationChangedListener: () -> Unit,
private val closeGuts: (immediate: Boolean) -> Unit,
private val falsingCollector: FalsingCollector,
private val falsingManager: FalsingManager,
- private val logSmartspaceImpression: (Boolean) -> Unit
+ private val logSmartspaceImpression: (Boolean) -> Unit,
+ private val logger: MediaUiEventLogger
) {
/**
* Is the view in RTL
@@ -476,6 +477,7 @@ class MediaCarouselScrollHandler(
visibleMediaIndex = newIndex
if (oldIndex != visibleMediaIndex && visibleToUser) {
logSmartspaceImpression(qsExpanded)
+ logger.logMediaCarouselPage(newIndex)
}
closeGuts(false)
updatePlayerVisibilities()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index b815f0c447d0..7ac70bd78953 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -53,6 +53,7 @@ import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.InstanceId;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -131,8 +132,6 @@ public class MediaControlPanel {
private MediaController mController;
private Lazy<MediaDataManager> mMediaDataManagerLazy;
private int mBackgroundColor;
- // Instance id for logging purpose.
- protected int mInstanceId = -1;
// Uid for the media app.
protected int mUid = Process.INVALID_UID;
private int mSmartspaceMediaItemsCount;
@@ -140,9 +139,13 @@ public class MediaControlPanel {
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
private final FalsingManager mFalsingManager;
- // Used for swipe-to-dismiss logging.
+ // Used for logging.
protected boolean mIsImpressed = false;
private SystemClock mSystemClock;
+ private MediaUiEventLogger mLogger;
+ private InstanceId mInstanceId;
+ protected int mSmartspaceId = -1;
+ private String mPackageName;
/**
* Initialize a new control panel
@@ -157,7 +160,7 @@ public class MediaControlPanel {
Lazy<MediaDataManager> lazyMediaDataManager,
MediaOutputDialogFactory mediaOutputDialogFactory,
MediaCarouselController mediaCarouselController,
- FalsingManager falsingManager, SystemClock systemClock) {
+ FalsingManager falsingManager, SystemClock systemClock, MediaUiEventLogger logger) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
@@ -169,8 +172,12 @@ public class MediaControlPanel {
mMediaCarouselController = mediaCarouselController;
mFalsingManager = falsingManager;
mSystemClock = systemClock;
+ mLogger = logger;
- mSeekBarViewModel.setLogSmartspaceClick(() -> {
+ mSeekBarViewModel.setLogSeek(() -> {
+ if (mPackageName != null && mInstanceId != null) {
+ mLogger.logSeek(mUid, mPackageName, mInstanceId);
+ }
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
/* isRecommendationCard */ false);
return Unit.INSTANCE;
@@ -261,6 +268,7 @@ public class MediaControlPanel {
});
vh.getSettings().setOnClickListener(v -> {
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mLogger.logLongPressSettings(mUid, mPackageName, mInstanceId);
mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
}
});
@@ -301,16 +309,13 @@ public class MediaControlPanel {
}
mKey = key;
MediaSession.Token token = data.getToken();
- PackageManager packageManager = mContext.getPackageManager();
- try {
- mUid = packageManager.getApplicationInfo(data.getPackageName(), 0 /* flags */).uid;
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Unable to look up package name", e);
- }
+ mPackageName = data.getPackageName();
+ mUid = data.getAppUid();
// Only assigns instance id if it's unassigned.
- if (mInstanceId == -1) {
- mInstanceId = SmallHash.hash(mUid + (int) mSystemClock.currentTimeMillis());
+ if (mSmartspaceId == -1) {
+ mSmartspaceId = SmallHash.hash(mUid + (int) mSystemClock.currentTimeMillis());
}
+ mInstanceId = data.getInstanceId();
mBackgroundColor = data.getBackgroundColor();
if (mToken == null || !mToken.equals(token)) {
@@ -401,6 +406,7 @@ public class MediaControlPanel {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
+ mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
if (device.getIntent() != null) {
if (device.getIntent().isActivity()) {
mActivityStarter.startActivity(
@@ -413,7 +419,7 @@ public class MediaControlPanel {
}
}
} else {
- mMediaOutputDialogFactory.create(data.getPackageName(), true,
+ mMediaOutputDialogFactory.create(mPackageName, true,
mMediaViewHolder.getSeamlessButton());
}
});
@@ -437,6 +443,7 @@ public class MediaControlPanel {
logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
/* isRecommendationCard */ false);
+ mLogger.logLongPressDismiss(mUid, mPackageName, mInstanceId);
if (mKey != null) {
closeGuts();
@@ -675,6 +682,7 @@ public class MediaControlPanel {
button.setEnabled(true);
button.setOnClickListener(v -> {
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
/* isRecommendationCard */ false);
action.run();
@@ -794,7 +802,7 @@ public class MediaControlPanel {
return;
}
- mInstanceId = SmallHash.hash(data.getTargetId());
+ mSmartspaceId = SmallHash.hash(data.getTargetId());
mBackgroundColor = data.getBackgroundColor();
TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations();
recommendationCard.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
@@ -976,6 +984,7 @@ public class MediaControlPanel {
mRecommendationViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
}
mMediaViewController.openGuts();
+ mLogger.logLongPressOpen(mUid, mPackageName, mInstanceId);
}
/**
@@ -1138,7 +1147,7 @@ public class MediaControlPanel {
private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard,
int interactedSubcardRank, int interactedSubcardCardinality) {
mMediaCarouselController.logSmartspaceCardReported(eventId,
- mInstanceId,
+ mSmartspaceId,
mUid,
isRecommendationCard,
new int[]{getSurfaceForSmartspaceLogging()},
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 47a0991d3d1e..a4d2f7bc96c4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -20,6 +20,7 @@ import android.app.PendingIntent
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.session.MediaSession
+import com.android.internal.logging.InstanceId
import com.android.systemui.R
/** State of a media view. */
@@ -115,7 +116,17 @@ data class MediaData(
/**
* Timestamp when this player was last active.
*/
- var lastActive: Long = 0L
+ var lastActive: Long = 0L,
+
+ /**
+ * Instance ID for logging purposes
+ */
+ val instanceId: InstanceId,
+
+ /**
+ * The UID of the app, used for logging
+ */
+ val appUid: Int
) {
companion object {
/** Media is playing on the local device */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 029ea9ad99f0..08c3395b4528 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -27,6 +27,7 @@ import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.graphics.drawable.Icon
@@ -37,6 +38,7 @@ import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
import android.os.Parcelable
+import android.os.Process
import android.os.UserHandle
import android.provider.Settings
import android.service.notification.StatusBarNotification
@@ -44,6 +46,7 @@ import android.text.TextUtils
import android.util.Log
import androidx.media.utils.MediaConstants
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.logging.InstanceId
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -94,7 +97,9 @@ private val LOADING = MediaData(
clickIntent = null,
device = null,
active = true,
- resumeAction = null)
+ resumeAction = null,
+ instanceId = InstanceId.fakeInstanceId(-1),
+ appUid = Process.INVALID_UID)
@VisibleForTesting
internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false,
@@ -138,7 +143,8 @@ class MediaDataManager(
private val useQsMediaPlayer: Boolean,
private val systemClock: SystemClock,
private val tunerService: TunerService,
- private val mediaFlags: MediaFlags
+ private val mediaFlags: MediaFlags,
+ private val logger: MediaUiEventLogger
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -202,12 +208,13 @@ class MediaDataManager(
smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
clock: SystemClock,
tunerService: TunerService,
- mediaFlags: MediaFlags
+ mediaFlags: MediaFlags,
+ logger: MediaUiEventLogger
) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context),
- Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags)
+ Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags, logger)
private val appChangeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@@ -298,17 +305,24 @@ class MediaDataManager(
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
if (useQsMediaPlayer && isMediaNotification(sbn)) {
+ var logEvent = false
Assert.isMainThread()
val oldKey = findExistingEntry(key, sbn.packageName)
if (oldKey == null) {
- val temp = LOADING.copy(packageName = sbn.packageName)
+ val instanceId = logger.getNewInstanceId()
+ val temp = LOADING.copy(
+ packageName = sbn.packageName,
+ instanceId = instanceId
+ )
mediaEntries.put(key, temp)
+ logEvent = true
} else if (oldKey != key) {
- // Move to new key
+ // Resume -> active conversion; move to new key
val oldData = mediaEntries.remove(oldKey)!!
+ logEvent = true
mediaEntries.put(key, oldData)
}
- loadMediaData(key, sbn, oldKey)
+ loadMediaData(key, sbn, oldKey, logEvent)
} else {
onNotificationRemoved(key)
}
@@ -340,9 +354,23 @@ class MediaDataManager(
) {
// Resume controls don't have a notification key, so store by package name instead
if (!mediaEntries.containsKey(packageName)) {
- val resumeData = LOADING.copy(packageName = packageName, resumeAction = action,
- hasCheckedForResume = true)
+ val instanceId = logger.getNewInstanceId()
+ val appUid = try {
+ context.packageManager.getApplicationInfo(packageName, 0)?.uid!!
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Could not get app UID for $packageName", e)
+ Process.INVALID_UID
+ }
+
+ val resumeData = LOADING.copy(
+ packageName = packageName,
+ resumeAction = action,
+ hasCheckedForResume = true,
+ instanceId = instanceId,
+ appUid = appUid
+ )
mediaEntries.put(packageName, resumeData)
+ logger.logResumeMediaAdded(appUid, packageName, instanceId)
}
backgroundExecutor.execute {
loadMediaDataInBgForResumption(userId, desc, action, token, appName, appIntent,
@@ -368,10 +396,11 @@ class MediaDataManager(
private fun loadMediaData(
key: String,
sbn: StatusBarNotification,
- oldKey: String?
+ oldKey: String?,
+ logEvent: Boolean = false
) {
backgroundExecutor.execute {
- loadMediaDataInBg(key, sbn, oldKey)
+ loadMediaDataInBg(key, sbn, oldKey, logEvent)
}
}
@@ -449,6 +478,10 @@ class MediaDataManager(
*/
internal fun setTimedOut(key: String, timedOut: Boolean, forceUpdate: Boolean = false) {
mediaEntries[key]?.let {
+ if (timedOut && !forceUpdate) {
+ // Only log this event when media expires on its own
+ logger.logMediaTimeout(it.appUid, it.packageName, it.instanceId)
+ }
if (it.active == !timedOut && !forceUpdate) {
if (it.resumption) {
if (DEBUG) Log.d(TAG, "timing out resume player $key")
@@ -463,7 +496,9 @@ class MediaDataManager(
}
private fun removeEntry(key: String) {
- mediaEntries.remove(key)
+ mediaEntries.remove(key)?.let {
+ logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
+ }
notifyMediaDataRemoved(key)
}
@@ -537,6 +572,10 @@ class MediaDataManager(
null
}
+ val currentEntry = mediaEntries.get(packageName)
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ val appUid = currentEntry?.appUid ?: Process.INVALID_UID
+
val mediaAction = getResumeMediaAction(resumeAction)
val lastActive = systemClock.elapsedRealtime()
foregroundExecutor.execute {
@@ -545,14 +584,16 @@ class MediaDataManager(
MediaButton(playOrPause = mediaAction), packageName, token, appIntent,
device = null, active = false,
resumeAction = resumeAction, resumption = true, notificationKey = packageName,
- hasCheckedForResume = true, lastActive = lastActive))
+ hasCheckedForResume = true, lastActive = lastActive, instanceId = instanceId,
+ appUid = appUid))
}
}
private fun loadMediaDataInBg(
key: String,
sbn: StatusBarNotification,
- oldKey: String?
+ oldKey: String?,
+ logEvent: Boolean = false
) {
val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
as MediaSession.Token?
@@ -636,6 +677,22 @@ class MediaDataManager(
MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL
else MediaData.PLAYBACK_CAST_LOCAL
val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
+
+ val currentEntry = mediaEntries.get(key)
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ val appUid = try {
+ context.packageManager.getApplicationInfo(sbn.packageName, 0)?.uid!!
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Could not get app UID for ${sbn.packageName}", e)
+ Process.INVALID_UID
+ }
+
+ if (logEvent) {
+ logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
+ } else if (playbackLocation != currentEntry?.playbackLocation) {
+ logger.logPlaybackLocationChange(appUid, sbn.packageName, instanceId, playbackLocation)
+ }
+
val lastActive = systemClock.elapsedRealtime()
foregroundExecutor.execute {
val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
@@ -647,7 +704,7 @@ class MediaDataManager(
active, resumeAction = resumeAction, playbackLocation = playbackLocation,
notificationKey = key, hasCheckedForResume = hasCheckedForResume,
isPlaying = isPlaying, isClearable = sbn.isClearable(),
- lastActive = lastActive))
+ lastActive = lastActive, instanceId = instanceId, appUid = appUid))
}
}
@@ -989,10 +1046,12 @@ class MediaDataManager(
notifyMediaDataRemoved(key)
notifyMediaDataLoaded(pkg, pkg, updated)
}
+ logger.logActiveConvertedToResume(updated.appUid, pkg, updated.instanceId)
return
}
if (removed != null) {
notifyMediaDataRemoved(key)
+ logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
}
}
@@ -1009,6 +1068,7 @@ class MediaDataManager(
filtered.forEach {
mediaEntries.remove(it.key)
notifyMediaDataRemoved(it.key)
+ logger.logMediaRemoved(it.value.appUid, it.value.packageName, it.value.instanceId)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
new file mode 100644
index 000000000000..862b2797b77c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
@@ -0,0 +1,207 @@
+/*
+ * 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 com.android.systemui.media
+
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import java.lang.IllegalArgumentException
+import javax.inject.Inject
+
+private const val INSTANCE_ID_MAX = 1 shl 20
+
+/**
+ * A helper class to log events related to the media controls
+ */
+@SysUISingleton
+class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) {
+
+ private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
+
+ /**
+ * Get a new instance ID for a new media control
+ */
+ fun getNewInstanceId(): InstanceId {
+ return instanceIdSequence.newInstanceId()
+ }
+
+ fun logActiveMediaAdded(
+ uid: Int,
+ packageName: String,
+ instanceId: InstanceId,
+ playbackLocation: Int
+ ) {
+ val event = when (playbackLocation) {
+ MediaData.PLAYBACK_LOCAL -> MediaUiEvent.LOCAL_MEDIA_ADDED
+ MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.CAST_MEDIA_ADDED
+ MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.REMOTE_MEDIA_ADDED
+ else -> throw IllegalArgumentException("Unknown playback location")
+ }
+ logger.logWithInstanceId(event, uid, packageName, instanceId)
+ }
+
+ fun logPlaybackLocationChange(
+ uid: Int,
+ packageName: String,
+ instanceId: InstanceId,
+ playbackLocation: Int
+ ) {
+ val event = when (playbackLocation) {
+ MediaData.PLAYBACK_LOCAL -> MediaUiEvent.TRANSFER_TO_LOCAL
+ MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.TRANSFER_TO_CAST
+ MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.TRANSFER_TO_REMOTE
+ else -> throw IllegalArgumentException("Unknown playback location")
+ }
+ logger.logWithInstanceId(event, uid, packageName, instanceId)
+ }
+
+ fun logResumeMediaAdded(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.RESUME_MEDIA_ADDED, uid, packageName, instanceId)
+ }
+
+ fun logActiveConvertedToResume(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.ACTIVE_TO_RESUME, uid, packageName, instanceId)
+ }
+
+ fun logMediaTimeout(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.MEDIA_TIMEOUT, uid, packageName, instanceId)
+ }
+
+ fun logMediaRemoved(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.MEDIA_REMOVED, uid, packageName, instanceId)
+ }
+
+ fun logMediaCarouselPage(position: Int) {
+ // Since this operation is on the carousel, we don't include package information
+ logger.logWithPosition(MediaUiEvent.CAROUSEL_PAGE, 0, null, position)
+ }
+
+ fun logSwipeDismiss() {
+ // Since this operation is on the carousel, we don't include package information
+ logger.log(MediaUiEvent.DISMISS_SWIPE)
+ }
+
+ fun logLongPressOpen(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.OPEN_LONG_PRESS, uid, packageName, instanceId)
+ }
+
+ fun logLongPressDismiss(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.DISMISS_LONG_PRESS, uid, packageName, instanceId)
+ }
+
+ fun logLongPressSettings(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.OPEN_SETTINGS_LONG_PRESS, uid, packageName,
+ instanceId)
+ }
+
+ fun logCarouselSettings() {
+ // Since this operation is on the carousel, we don't include package information
+ logger.log(MediaUiEvent.OPEN_SETTINGS_CAROUSEL)
+ }
+
+ fun logTapAction(buttonId: Int, uid: Int, packageName: String, instanceId: InstanceId) {
+ val event = when (buttonId) {
+ R.id.actionPlayPause -> MediaUiEvent.TAP_ACTION_PLAY_PAUSE
+ R.id.actionPrev -> MediaUiEvent.TAP_ACTION_PREV
+ R.id.actionNext -> MediaUiEvent.TAP_ACTION_NEXT
+ else -> MediaUiEvent.TAP_ACTION_OTHER
+ }
+
+ logger.logWithInstanceId(event, uid, packageName, instanceId)
+ }
+
+ fun logSeek(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.ACTION_SEEK, uid, packageName, instanceId)
+ }
+
+ fun logOpenOutputSwitcher(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.OPEN_OUTPUT_SWITCHER, uid, packageName, instanceId)
+ }
+}
+
+enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "A new media control was added for media playing locally on the device")
+ LOCAL_MEDIA_ADDED(1029),
+
+ @UiEvent(doc = "A new media control was added for media cast from the device")
+ CAST_MEDIA_ADDED(1030),
+
+ @UiEvent(doc = "A new media control was added for media playing remotely")
+ REMOTE_MEDIA_ADDED(1031),
+
+ @UiEvent(doc = "The media for an existing control was transferred to local playback")
+ TRANSFER_TO_LOCAL(1032),
+
+ @UiEvent(doc = "The media for an existing control was transferred to a cast device")
+ TRANSFER_TO_CAST(1033),
+
+ @UiEvent(doc = "The media for an existing control was transferred to a remote device")
+ TRANSFER_TO_REMOTE(1034),
+
+ @UiEvent(doc = "A new resumable media control was added")
+ RESUME_MEDIA_ADDED(1013),
+
+ @UiEvent(doc = "An existing active media control was converted into resumable media")
+ ACTIVE_TO_RESUME(1014),
+
+ @UiEvent(doc = "Media timed out")
+ MEDIA_TIMEOUT(1015),
+
+ @UiEvent(doc = "A media control was removed from the carousel")
+ MEDIA_REMOVED(1016),
+
+ @UiEvent(doc = "User swiped to another control within the media carousel")
+ CAROUSEL_PAGE(1017),
+
+ @UiEvent(doc = "The user swiped away the media carousel")
+ DISMISS_SWIPE(1018),
+
+ @UiEvent(doc = "The user opened the long press menu")
+ OPEN_LONG_PRESS(1019),
+
+ @UiEvent(doc = "The user dismissed via long press menu")
+ DISMISS_LONG_PRESS(1020),
+
+ @UiEvent(doc = "The user opened settings from long press menu")
+ OPEN_SETTINGS_LONG_PRESS(1021),
+
+ @UiEvent(doc = "The user opened settings from the carousel")
+ OPEN_SETTINGS_CAROUSEL(1022),
+
+ @UiEvent(doc = "The play/pause button was tapped")
+ TAP_ACTION_PLAY_PAUSE(1023),
+
+ @UiEvent(doc = "The previous button was tapped")
+ TAP_ACTION_PREV(1024),
+
+ @UiEvent(doc = "The next button was tapped")
+ TAP_ACTION_NEXT(1025),
+
+ @UiEvent(doc = "A custom or generic action button was tapped")
+ TAP_ACTION_OTHER(1026),
+
+ @UiEvent(doc = "The user seeked using the seekbar")
+ ACTION_SEEK(1027),
+
+ @UiEvent(doc = "The user opened the output switcher from a media control")
+ OPEN_OUTPUT_SWITCHER(1028);
+
+ override fun getId() = metricId
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index a9a8fd1c0e9d..8c1845ac1ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -131,7 +131,7 @@ class SeekBarViewModel @Inject constructor(
}
}
- lateinit var logSmartspaceClick: () -> Unit
+ lateinit var logSeek: () -> Unit
fun getEnabled() = _data.enabled
@@ -175,7 +175,7 @@ class SeekBarViewModel @Inject constructor(
scrubbing = false
checkPlaybackPosition()
} else {
- logSmartspaceClick()
+ logSeek()
controller?.transportControls?.seekTo(position)
// Invalidate the cached playbackState to avoid the thumb jumping back to the previous
// position.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 19f59f76f8a5..93103e2c3f49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -174,10 +174,21 @@ public class StatusBarStateControllerImpl implements
if (state > MAX_STATE || state < MIN_STATE) {
throw new IllegalArgumentException("Invalid state " + state);
}
- if (!force && state == mState) {
+
+ // Unless we're explicitly asked to force the state change, don't apply the new state if
+ // it's identical to both the current and upcoming states, since that should not be
+ // necessary.
+ if (!force && state == mState && state == mUpcomingState) {
return false;
}
+ if (state != mUpcomingState) {
+ Log.d(TAG, "setState: requested state " + StatusBarState.toString(state)
+ + "!= upcomingState: " + StatusBarState.toString(mUpcomingState) + ". "
+ + "This usually means the status bar state transition was interrupted before "
+ + "the upcoming state could be applied.");
+ }
+
// Record the to-be mState and mLastState
recordHistoricalState(state /* newState */, mState /* lastState */, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 8b25c2bc20b9..ebe91c7dc5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -292,11 +292,20 @@ public class DozeParameters implements
}
public void updateControlScreenOff() {
- if (!getDisplayNeedsBlanking()) {
- final boolean controlScreenOff =
- getAlwaysOn() && (mKeyguardShowing || shouldControlUnlockedScreenOff());
- setControlScreenOffAnimation(controlScreenOff);
- }
+ final boolean controlScreenOff = shouldControlUnlockedScreenOff()
+ || (!getDisplayNeedsBlanking() && getAlwaysOn() && mKeyguardShowing);
+ setControlScreenOffAnimation(controlScreenOff);
+ }
+
+ /**
+ * Whether we're capable of controlling the screen off animation if we want to. This isn't
+ * possible if AOD isn't even enabled or if the flag is disabled, or if the display needs
+ * blanking.
+ */
+ public boolean canControlUnlockedScreenOff() {
+ return getAlwaysOn()
+ && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
+ && !getDisplayNeedsBlanking();
}
/**
@@ -309,8 +318,7 @@ public class DozeParameters implements
* disabled for a11y.
*/
public boolean shouldControlUnlockedScreenOff() {
- return canControlUnlockedScreenOff()
- && mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
+ return mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
}
public boolean shouldDelayKeyguardShow() {
@@ -342,16 +350,6 @@ public class DozeParameters implements
return getAlwaysOn() && mKeyguardShowing;
}
- /**
- * Whether we're capable of controlling the screen off animation if we want to. This isn't
- * possible if AOD isn't even enabled or if the flag is disabled.
- */
- public boolean canControlUnlockedScreenOff() {
- return getAlwaysOn()
- && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
- && !getDisplayNeedsBlanking();
- }
-
private boolean getBoolean(String propName, int resId) {
return SystemProperties.getBoolean(propName, mResources.getBoolean(resId));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index c11d450e47b2..ea935be334c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -59,8 +59,14 @@ class UnlockedScreenOffAnimationController @Inject constructor(
private val powerManager: PowerManager,
private val handler: Handler = Handler()
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
-
private lateinit var mCentralSurfaces: CentralSurfaces
+ /**
+ * Whether or not [initialize] has been called to provide us with the StatusBar,
+ * NotificationPanelViewController, and LightRevealSrim so that we can run the unlocked screen
+ * off animation.
+ */
+ private var initialized = false
+
private lateinit var lightRevealScrim: LightRevealScrim
private var animatorDurationScale = 1f
@@ -116,6 +122,7 @@ class UnlockedScreenOffAnimationController @Inject constructor(
centralSurfaces: CentralSurfaces,
lightRevealScrim: LightRevealScrim
) {
+ this.initialized = true
this.lightRevealScrim = lightRevealScrim
this.mCentralSurfaces = centralSurfaces
@@ -262,6 +269,18 @@ class UnlockedScreenOffAnimationController @Inject constructor(
* on the current state of the device.
*/
fun shouldPlayUnlockedScreenOffAnimation(): Boolean {
+ // If we haven't been initialized yet, we don't have a StatusBar/LightRevealScrim yet, so we
+ // can't perform the animation.
+ if (!initialized) {
+ return false
+ }
+
+ // If the device isn't in a state where we can control unlocked screen off (no AOD enabled,
+ // power save, etc.) then we shouldn't try to do so.
+ if (!dozeParameters.get().canControlUnlockedScreenOff()) {
+ return false
+ }
+
// If we explicitly already decided not to play the screen off animation, then never change
// our mind.
if (decidedToAnimateGoingToSleep == false) {
@@ -304,7 +323,7 @@ class UnlockedScreenOffAnimationController @Inject constructor(
}
override fun shouldDelayDisplayDozeTransition(): Boolean =
- dozeParameters.get().shouldControlUnlockedScreenOff()
+ shouldPlayUnlockedScreenOffAnimation()
/**
* Whether we're doing the light reveal animation or we're done with that and animating in the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index f52c6ed0a232..3e50acb75401 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -86,6 +86,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
private boolean mShouldDisplayAllAccesses;
private boolean mShowSystemAccessesFlag;
private boolean mShowSystemAccessesSetting;
+ private boolean mExperimentStarted;
@Inject
public LocationControllerImpl(Context context, AppOpsController appOpsController,
@@ -107,6 +108,9 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
mShouldDisplayAllAccesses = getAllAccessesSetting();
mShowSystemAccessesFlag = getShowSystemFlag();
mShowSystemAccessesSetting = getShowSystemSetting();
+ mExperimentStarted = getExperimentStarted();
+ toggleSystemSettingIfExperimentJustStarted();
+
mContentObserver = new ContentObserver(mBackgroundHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -123,8 +127,15 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
DeviceConfig.NAMESPACE_PRIVACY,
backgroundHandler::post,
properties -> {
+ // Update the Device Config flag which controls the experiment to display
+ // location accesses.
mShouldDisplayAllAccesses = getAllAccessesSetting();
- mShowSystemAccessesFlag = getShowSystemSetting();
+ // Update the Device Config flag which controls the experiment to display
+ // system location accesses.
+ mShowSystemAccessesFlag = getShowSystemFlag();
+ // Update the local flag for the system accesses experiment and potentially
+ // update the behavior based on the flag value.
+ toggleSystemSettingIfExperimentJustStarted();
updateActiveLocationRequests();
});
@@ -222,6 +233,33 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
return mSecureSettings.getInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1;
}
+ private boolean getExperimentStarted() {
+ return mSecureSettings
+ .getInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED, 0) == 1;
+ }
+
+ private void toggleSystemSettingIfExperimentJustStarted() {
+ // mShowSystemAccessesFlag indicates whether the Device Config flag is flipped
+ // by an experiment. mExperimentStarted is the local device value which indicates the last
+ // value the device has seen for the Device Config flag.
+ // The local device value is needed to determine that the Device Config flag was just
+ // flipped, as the experiment behavior should only happen once after the experiment is
+ // enabled.
+ if (mShowSystemAccessesFlag && !mExperimentStarted) {
+ // If the Device Config flag is enabled, but the local device setting is not then the
+ // experiment just started. Update the local flag to match and enable the experiment
+ // behavior by flipping the show system setting value.
+ mSecureSettings.putInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED, 1);
+ mSecureSettings.putInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 1);
+ mExperimentStarted = true;
+ } else if (!mShowSystemAccessesFlag && mExperimentStarted) {
+ // If the Device Config flag is disabled, but the local device flag is enabled then
+ // update the local device flag to match.
+ mSecureSettings.putInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED, 0);
+ mExperimentStarted = false;
+ }
+ }
+
/**
* Returns true if there currently exist active high power location requests.
*/
@@ -249,7 +287,6 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
}
boolean hadActiveLocationRequests = mAreActiveLocationRequests;
boolean shouldDisplay = false;
- boolean showSystem = mShowSystemAccessesFlag || mShowSystemAccessesSetting;
boolean systemAppOp = false;
boolean nonSystemAppOp = false;
boolean isSystemApp;
@@ -267,7 +304,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
nonSystemAppOp = true;
}
- shouldDisplay = showSystem || shouldDisplay || !isSystemApp;
+ shouldDisplay = mShowSystemAccessesSetting || shouldDisplay || !isSystemApp;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 8eb0918beedf..98d57a3c5da8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -9,8 +9,10 @@ import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
+import junit.framework.Assert.assertTrue
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -25,15 +27,23 @@ class ViewHierarchyAnimatorTest : SysuiTestCase() {
private val TEST_INTERPOLATOR = Interpolators.LINEAR
}
+ private val childParams = LinearLayout.LayoutParams(
+ 0 /* width */,
+ LinearLayout.LayoutParams.MATCH_PARENT
+ )
private lateinit var rootView: LinearLayout
@Before
fun setUp() {
rootView = LinearLayout(mContext)
+ rootView.orientation = LinearLayout.HORIZONTAL
+ rootView.weightSum = 1f
+ childParams.weight = 0.5f
}
@After
fun tearDown() {
+ endAnimation(rootView)
ViewHierarchyAnimator.stopAnimating(rootView)
}
@@ -41,13 +51,45 @@ class ViewHierarchyAnimatorTest : SysuiTestCase() {
fun respectsAnimationParameters() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
- ViewHierarchyAnimator.animate(
+ // animate()
+ var success = ViewHierarchyAnimator.animate(
rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
)
rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */)
+ assertTrue(success)
assertNotNull(rootView.getTag(R.id.tag_animator))
- val animator = rootView.getTag(R.id.tag_animator) as ObjectAnimator
+ var animator = rootView.getTag(R.id.tag_animator) as ObjectAnimator
+ assertEquals(animator.interpolator, TEST_INTERPOLATOR)
+ assertEquals(animator.duration, TEST_DURATION)
+
+ endAnimation(rootView)
+ ViewHierarchyAnimator.stopAnimating(rootView)
+
+ // animateNextUpdate()
+ success = ViewHierarchyAnimator.animateNextUpdate(
+ rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
+ )
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ animator = rootView.getTag(R.id.tag_animator) as ObjectAnimator
+ assertEquals(animator.interpolator, TEST_INTERPOLATOR)
+ assertEquals(animator.duration, TEST_DURATION)
+
+ endAnimation(rootView)
+
+ // animateAddition()
+ rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
+ )
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ animator = rootView.getTag(R.id.tag_animator) as ObjectAnimator
assertEquals(animator.interpolator, TEST_INTERPOLATOR)
assertEquals(animator.duration, TEST_DURATION)
}
@@ -56,10 +98,11 @@ class ViewHierarchyAnimatorTest : SysuiTestCase() {
fun animatesFromStartToEnd() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
- ViewHierarchyAnimator.animate(rootView)
+ val success = ViewHierarchyAnimator.animate(rootView)
// Change all bounds.
rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
+ assertTrue(success)
assertNotNull(rootView.getTag(R.id.tag_animator))
// The initial values should be those of the previous layout.
checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
@@ -73,10 +116,11 @@ class ViewHierarchyAnimatorTest : SysuiTestCase() {
fun animatesSuccessiveLayoutChanges() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
- ViewHierarchyAnimator.animate(rootView)
+ val success = ViewHierarchyAnimator.animate(rootView)
// Change all bounds.
rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
+ assertTrue(success)
assertNotNull(rootView.getTag(R.id.tag_animator))
endAnimation(rootView)
assertNull(rootView.getTag(R.id.tag_animator))
@@ -103,10 +147,12 @@ class ViewHierarchyAnimatorTest : SysuiTestCase() {
fun animatesFromPreviousAnimationProgress() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
- ViewHierarchyAnimator.animateNextUpdate(rootView, interpolator = TEST_INTERPOLATOR)
+ val success =
+ ViewHierarchyAnimator.animateNextUpdate(rootView, interpolator = TEST_INTERPOLATOR)
// Change all bounds.
rootView.layout(0 /* l */, 20 /* t */, 70 /* r */, 80 /* b */)
+ assertTrue(success)
assertNotNull(rootView.getTag(R.id.tag_animator))
advanceAnimation(rootView, fraction = 0.5f)
checkBounds(rootView, l = 5, t = 15, r = 60, b = 65)
@@ -124,57 +170,448 @@ class ViewHierarchyAnimatorTest : SysuiTestCase() {
@Test
fun animatesRootAndChildren() {
val firstChild = View(mContext)
+ firstChild.layoutParams = childParams
rootView.addView(firstChild)
val secondChild = View(mContext)
+ secondChild.layoutParams = childParams
rootView.addView(secondChild)
+ rootView.measure(
+ View.MeasureSpec.makeMeasureSpec(150, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ )
rootView.layout(0 /* l */, 0 /* t */, 150 /* r */, 100 /* b */)
- firstChild.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */)
- secondChild.layout(100 /* l */, 0 /* t */, 150 /* r */, 100 /* b */)
- ViewHierarchyAnimator.animate(rootView)
+ val success = ViewHierarchyAnimator.animate(rootView)
// Change all bounds.
+ rootView.measure(
+ View.MeasureSpec.makeMeasureSpec(190, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ )
rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
- firstChild.layout(10 /* l */, 20 /* t */, 150 /* r */, 120 /* b */)
- secondChild.layout(150 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
+ assertTrue(success)
assertNotNull(rootView.getTag(R.id.tag_animator))
+ assertNotNull(firstChild.getTag(R.id.tag_animator))
+ assertNotNull(secondChild.getTag(R.id.tag_animator))
// The initial values should be those of the previous layout.
checkBounds(rootView, l = 0, t = 0, r = 150, b = 100)
- checkBounds(firstChild, l = 0, t = 0, r = 100, b = 100)
- checkBounds(secondChild, l = 100, t = 0, r = 150, b = 100)
+ checkBounds(firstChild, l = 0, t = 0, r = 75, b = 100)
+ checkBounds(secondChild, l = 75, t = 0, r = 150, b = 100)
endAnimation(rootView)
assertNull(rootView.getTag(R.id.tag_animator))
+ assertNull(firstChild.getTag(R.id.tag_animator))
+ assertNull(secondChild.getTag(R.id.tag_animator))
// The end values should be those of the latest layout.
checkBounds(rootView, l = 10, t = 20, r = 200, b = 120)
- checkBounds(firstChild, l = 10, t = 20, r = 150, b = 120)
- checkBounds(secondChild, l = 150, t = 20, r = 200, b = 120)
+ checkBounds(firstChild, l = 0, t = 0, r = 95, b = 100)
+ checkBounds(secondChild, l = 95, t = 0, r = 190, b = 100)
+ }
+
+ @Test
+ fun animatesAppearingViewsFromStartToEnd() {
+ // Starting GONE.
+ rootView.visibility = View.GONE
+ rootView.layout(0 /* l */, 50 /* t */, 50 /* r */, 100 /* b */)
+ var success = ViewHierarchyAnimator.animateAddition(rootView)
+ rootView.visibility = View.VISIBLE
+ rootView.layout(0 /* l */, 100 /* t */, 100 /* r */, 200 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 50, t = 150, r = 50, b = 150)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 100, r = 100, b = 200)
+
+ // Starting INVISIBLE.
+ rootView.visibility = View.INVISIBLE
+ rootView.layout(0 /* l */, 50 /* t */, 50 /* r */, 100 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(rootView)
+ rootView.visibility = View.VISIBLE
+ rootView.layout(0 /* l */, 100 /* t */, 100 /* r */, 200 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 50, t = 150, r = 50, b = 150)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 100, r = 100, b = 200)
+
+ // Starting with nothing.
+ rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(rootView)
+ rootView.layout(0 /* l */, 20 /* t */, 50 /* r */, 80 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 25, t = 50, r = 25, b = 50)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 20, r = 50, b = 80)
+
+ // Starting with 0 width.
+ rootView.layout(0 /* l */, 50 /* t */, 0 /* r */, 100 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(rootView)
+ rootView.layout(0 /* l */, 50 /* t */, 50 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 25, t = 75, r = 25, b = 75)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 50, r = 50, b = 100)
+
+ // Starting with 0 height.
+ rootView.layout(0 /* l */, 50 /* t */, 50 /* r */, 50 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(rootView)
+ rootView.layout(0 /* l */, 50 /* t */, 50 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 25, t = 75, r = 25, b = 75)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 50, r = 50, b = 100)
+ }
+
+ @Test
+ fun animatesAppearingViewsRespectingOrigin() {
+ // CENTER.
+ rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
+ var success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 75, t = 75, r = 75, b = 75)
+ endAnimation(rootView)
+
+ // LEFT.
+ rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.LEFT
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 50, t = 50, r = 50, b = 100)
+ endAnimation(rootView)
+
+ // TOP_LEFT.
+ rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 50, t = 50, r = 50, b = 50)
+ endAnimation(rootView)
+
+ // TOP.
+ rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.TOP
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 50, t = 50, r = 100, b = 50)
+ endAnimation(rootView)
+
+ // TOP_RIGHT.
+ rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 100, t = 50, r = 100, b = 50)
+ endAnimation(rootView)
+
+ // RIGHT.
+ rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.RIGHT
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 100, t = 50, r = 100, b = 100)
+ endAnimation(rootView)
+
+ // BOTTOM_RIGHT.
+ rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 100, t = 100, r = 100, b = 100)
+ endAnimation(rootView)
+
+ // BOTTOM.
+ rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 50, t = 100, r = 100, b = 100)
+ endAnimation(rootView)
+
+ // BOTTOM_LEFT.
+ rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 50, t = 100, r = 50, b = 100)
+ endAnimation(rootView)
+ }
+
+ @Test
+ fun animatesAppearingViewsRespectingMargins() {
+ // CENTER.
+ rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
+ var success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 75, t = 75, r = 75, b = 75)
+ endAnimation(rootView)
+
+ // LEFT.
+ rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView, origin = ViewHierarchyAnimator.Hotspot.LEFT,
+ includeMargins = true
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 50, r = 0, b = 100)
+ endAnimation(rootView)
+
+ // TOP_LEFT.
+ rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+ includeMargins = true
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 0, r = 0, b = 0)
+ endAnimation(rootView)
+
+ // TOP.
+ rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView, origin = ViewHierarchyAnimator.Hotspot.TOP,
+ includeMargins = true
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 50, t = 0, r = 100, b = 0)
+ endAnimation(rootView)
+
+ // TOP_RIGHT.
+ rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+ includeMargins = true
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 150, t = 0, r = 150, b = 0)
+ endAnimation(rootView)
+
+ // RIGHT.
+ rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.RIGHT,
+ includeMargins = true
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 150, t = 50, r = 150, b = 100)
+ endAnimation(rootView)
+
+ // BOTTOM_RIGHT.
+ rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+ includeMargins = true
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 150, t = 150, r = 150, b = 150)
+ endAnimation(rootView)
+
+ // BOTTOM.
+ rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM,
+ includeMargins = true
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 50, t = 150, r = 100, b = 150)
+ endAnimation(rootView)
+
+ // BOTTOM_LEFT.
+ rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
+ success = ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+ includeMargins = true
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 150, r = 0, b = 150)
+ endAnimation(rootView)
}
@Test
fun doesNotAnimateInvisibleViews() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
- ViewHierarchyAnimator.animate(rootView)
- // GONE.
+ // GONE
rootView.visibility = View.GONE
+ var success = ViewHierarchyAnimator.animate(rootView)
rootView.layout(0 /* l */, 15 /* t */, 55 /* r */, 80 /* b */)
+ assertFalse(success)
assertNull(rootView.getTag(R.id.tag_animator))
checkBounds(rootView, l = 0, t = 15, r = 55, b = 80)
// INVISIBLE.
rootView.visibility = View.INVISIBLE
- rootView.layout(0 /* l */, 20 /* t */, 0 /* r */, 20 /* b */)
+ success = ViewHierarchyAnimator.animate(rootView)
+ rootView.layout(0 /* l */, 20 /* t */, 10 /* r */, 50 /* b */)
+
+ assertFalse(success)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 20, r = 10, b = 50)
+ }
+
+ @Test
+ fun doesNotAnimateAppearingViews() {
+ // Starting with nothing.
+ rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
+ var success = ViewHierarchyAnimator.animate(rootView)
+ rootView.layout(0 /* l */, 15 /* t */, 55 /* r */, 80 /* b */)
+
+ assertFalse(success)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 15, r = 55, b = 80)
+
+ // Starting with 0 width.
+ rootView.layout(0 /* l */, 50 /* t */, 0 /* r */, 100 /* b */)
+ success = ViewHierarchyAnimator.animate(rootView)
+ rootView.layout(0 /* l */, 15 /* t */, 55 /* r */, 80 /* b */)
+
+ assertFalse(success)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 15, r = 55, b = 80)
+
+ // Starting with 0 height.
+ rootView.layout(0 /* l */, 50 /* t */, 50 /* r */, 50 /* b */)
+ success = ViewHierarchyAnimator.animate(rootView)
+ rootView.layout(0 /* l */, 15 /* t */, 55 /* r */, 80 /* b */)
+
+ assertFalse(success)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 15, r = 55, b = 80)
+ }
+
+ @Test
+ fun doesNotAnimateDisappearingViews() {
+ rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */)
+
+ val success = ViewHierarchyAnimator.animate(rootView)
+ // Ending with nothing.
+ rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
+
+ assertTrue(success)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 0, r = 0, b = 0)
+
+ // Ending with 0 width.
+ rootView.layout(0 /* l */, 50 /* t */, 50 /* r */, 100 /* b */)
+ endAnimation(rootView)
+ rootView.layout(0 /* l */, 15 /* t */, 0 /* r */, 80 /* b */)
+
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 15, r = 0, b = 80)
+
+ // Ending with 0 height.
+ rootView.layout(0 /* l */, 50 /* t */, 50 /* r */, 100 /* b */)
+ endAnimation(rootView)
+ rootView.layout(0 /* l */, 50 /* t */, 55 /* r */, 50 /* b */)
+
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 50, r = 55, b = 50)
}
@Test
fun doesNotAnimateUnchangingBounds() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
- ViewHierarchyAnimator.animate(rootView)
+ val success = ViewHierarchyAnimator.animate(rootView)
// No bounds are changed.
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+ assertTrue(success)
assertNull(rootView.getTag(R.id.tag_animator))
checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
@@ -191,7 +628,7 @@ class ViewHierarchyAnimatorTest : SysuiTestCase() {
fun doesNotAnimateExcludedBounds() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
- ViewHierarchyAnimator.animate(
+ val success = ViewHierarchyAnimator.animate(
rootView,
bounds = setOf(ViewHierarchyAnimator.Bound.LEFT, ViewHierarchyAnimator.Bound.TOP),
interpolator = TEST_INTERPOLATOR
@@ -199,6 +636,7 @@ class ViewHierarchyAnimatorTest : SysuiTestCase() {
// Change all bounds.
rootView.layout(0 /* l */, 20 /* t */, 70 /* r */, 80 /* b */)
+ assertTrue(success)
assertNotNull(rootView.getTag(R.id.tag_animator))
advanceAnimation(rootView, 0.5f)
checkBounds(rootView, l = 5, t = 15, r = 70, b = 80)
@@ -211,10 +649,11 @@ class ViewHierarchyAnimatorTest : SysuiTestCase() {
fun stopsAnimatingAfterSingleLayout() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
- ViewHierarchyAnimator.animateNextUpdate(rootView)
+ val success = ViewHierarchyAnimator.animateNextUpdate(rootView)
// Change all bounds.
rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
+ assertTrue(success)
assertNotNull(rootView.getTag(R.id.tag_animator))
endAnimation(rootView)
assertNull(rootView.getTag(R.id.tag_animator))
@@ -231,10 +670,11 @@ class ViewHierarchyAnimatorTest : SysuiTestCase() {
fun stopsAnimatingWhenInstructed() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
- ViewHierarchyAnimator.animate(rootView)
+ val success = ViewHierarchyAnimator.animate(rootView)
// Change all bounds.
rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
+ assertTrue(success)
assertNotNull(rootView.getTag(R.id.tag_animator))
endAnimation(rootView)
assertNull(rootView.getTag(R.id.tag_animator))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index a95da6295350..5440b45a778f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -299,7 +299,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
`when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
- controller.showRipple(BiometricSourceType.FINGERPRINT)
+ controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
assertTrue("reveal didn't start on keyguardFadingAway",
controller.startLightRevealScrimOnKeyguardFadingAway)
`when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
index c86122141c8f..b7de6c15459c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
@@ -61,7 +61,8 @@ public class DreamOverlayNotificationCountProviderTest extends SysuiTestCase {
final StatusBarNotification[] notifications = {mNotification1};
when(mNotificationListener.getActiveNotifications()).thenReturn(notifications);
- mProvider = new DreamOverlayNotificationCountProvider(mNotificationListener);
+ mProvider = new DreamOverlayNotificationCountProvider(
+ mNotificationListener, Runnable::run);
mProvider.addCallback(mCallback);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index daf81bdc6e82..46e675684782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -35,26 +35,11 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import javax.inject.Provider
-private val DATA = MediaData(
- userId = -1,
- initialized = false,
- backgroundColor = 0,
- app = null,
- appIcon = null,
- artist = null,
- song = null,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
- packageName = "INVALID",
- token = null,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null)
+private val DATA = MediaTestUtils.emptyMediaData
private val SMARTSPACE_KEY = "smartspace"
@@ -74,7 +59,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
@Mock lateinit var falsingCollector: FalsingCollector
@Mock lateinit var falsingManager: FalsingManager
@Mock lateinit var dumpManager: DumpManager
- @Mock lateinit var mediaFlags: MediaFlags
+ @Mock lateinit var logger: MediaUiEventLogger
private val clock = FakeSystemClock()
private lateinit var mediaCarouselController: MediaCarouselController
@@ -95,7 +80,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
falsingCollector,
falsingManager,
dumpManager,
- mediaFlags
+ logger
)
MediaPlayerData.clear()
@@ -189,4 +174,18 @@ class MediaCarouselControllerTest : SysuiTestCase() {
val size = MediaPlayerData.playerKeys().size
assertTrue(MediaPlayerData.playerKeys().elementAt(size - 1).isSsMediaRec)
}
+
+ @Test
+ fun testSwipeDismiss_logged() {
+ mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
+
+ verify(logger).logSwipeDismiss()
+ }
+
+ @Test
+ fun testSettingsButton_logged() {
+ mediaCarouselController.settingsButton.callOnClick()
+
+ verify(logger).logCarouselSettings()
+ }
} \ No newline at end of file
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 63b3a7db40d3..f2e3edbc0761 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -41,6 +41,7 @@ import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
@@ -49,6 +50,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -59,6 +61,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.any
@@ -69,7 +72,6 @@ import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
private const val KEY = "TEST_KEY"
-private const val APP = "APP"
private const val BG_COLOR = Color.RED
private const val PACKAGE = "PKG"
private const val ARTIST = "ARTIST"
@@ -78,7 +80,6 @@ private const val DEVICE_NAME = "DEVICE_NAME"
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
-private const val USER_ID = 0
private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME"
@SmallTest
@@ -137,6 +138,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME)
private lateinit var mediaData: MediaData
private val clock = FakeSystemClock()
+ @Mock private lateinit var logger: MediaUiEventLogger
+ @Mock private lateinit var instanceId: InstanceId
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -148,7 +151,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player = MediaControlPanel(context, bgExecutor, activityStarter, broadcastSender,
mediaViewController, seekBarViewModel, Lazy { mediaDataManager },
- mediaOutputDialogFactory, mediaCarouselController, falsingManager, clock)
+ mediaOutputDialogFactory, mediaCarouselController, falsingManager, clock, logger)
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
// Set up mock views for the players
@@ -212,23 +215,14 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
session.setActive(true)
- mediaData = MediaData(
- userId = USER_ID,
- initialized = true,
+ mediaData = MediaTestUtils.emptyMediaData.copy(
backgroundColor = BG_COLOR,
- app = APP,
- appIcon = null,
artist = ARTIST,
song = TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
packageName = PACKAGE,
token = session.sessionToken,
- clickIntent = null,
device = device,
- active = true,
- resumeAction = null)
+ instanceId = instanceId)
}
/**
@@ -534,6 +528,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun longClick_gutsClosed() {
player.attachPlayer(viewHolder)
+ player.bindPlayer(mediaData, KEY)
whenever(mediaViewController.isGutsVisible).thenReturn(false)
val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
@@ -541,6 +536,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
captor.value.onLongClick(viewHolder.player)
verify(mediaViewController).openGuts()
+ verify(logger).logLongPressOpen(anyInt(), eq(PACKAGE), eq(instanceId))
}
@Test
@@ -568,8 +564,10 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun settingsButtonClick() {
player.attachPlayer(viewHolder)
+ player.bindPlayer(mediaData, KEY)
settings.callOnClick()
+ verify(logger).logLongPressSettings(anyInt(), eq(PACKAGE), eq(instanceId))
val captor = ArgumentCaptor.forClass(Intent::class.java)
verify(activityStarter).startActivity(captor.capture(), eq(true))
@@ -586,6 +584,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
assertThat(dismiss.isEnabled).isEqualTo(true)
dismiss.callOnClick()
+ verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId))
verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
}
@@ -614,4 +613,151 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false))
}
+
+ @Test
+ fun actionPlayPauseClick_isLogged() {
+ val semanticActions = MediaButton(
+ playOrPause = MediaAction(null, Runnable {}, "play", null)
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.actionPlayPause.callOnClick()
+ verify(logger).logTapAction(eq(R.id.actionPlayPause), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionPrevClick_isLogged() {
+ val semanticActions = MediaButton(
+ prevOrCustom = MediaAction(null, Runnable {}, "previous", null)
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.actionPrev.callOnClick()
+ verify(logger).logTapAction(eq(R.id.actionPrev), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionNextClick_isLogged() {
+ val semanticActions = MediaButton(
+ nextOrCustom = MediaAction(null, Runnable {}, "next", null)
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.actionNext.callOnClick()
+ verify(logger).logTapAction(eq(R.id.actionNext), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionCustom0Click_isLogged() {
+ val semanticActions = MediaButton(
+ custom0 = MediaAction(null, Runnable {}, "custom 0", null)
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action0.callOnClick()
+ verify(logger).logTapAction(eq(R.id.action0), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionCustom1Click_isLogged() {
+ val semanticActions = MediaButton(
+ custom1 = MediaAction(null, Runnable {}, "custom 1", null)
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action1.callOnClick()
+ verify(logger).logTapAction(eq(R.id.action1), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionCustom2Click_isLogged() {
+ val actions = listOf(
+ MediaAction(null, Runnable {}, "action 0", null),
+ MediaAction(null, Runnable {}, "action 1", null),
+ MediaAction(null, Runnable {}, "action 2", null),
+ MediaAction(null, Runnable {}, "action 3", null),
+ MediaAction(null, Runnable {}, "action 4", null)
+ )
+ val data = mediaData.copy(actions = actions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action2.callOnClick()
+ verify(logger).logTapAction(eq(R.id.action2), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionCustom3Click_isLogged() {
+ val actions = listOf(
+ MediaAction(null, Runnable {}, "action 0", null),
+ MediaAction(null, Runnable {}, "action 1", null),
+ MediaAction(null, Runnable {}, "action 2", null),
+ MediaAction(null, Runnable {}, "action 3", null),
+ MediaAction(null, Runnable {}, "action 4", null)
+ )
+ val data = mediaData.copy(actions = actions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action1.callOnClick()
+ verify(logger).logTapAction(eq(R.id.action1), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionCustom4Click_isLogged() {
+ val actions = listOf(
+ MediaAction(null, Runnable {}, "action 0", null),
+ MediaAction(null, Runnable {}, "action 1", null),
+ MediaAction(null, Runnable {}, "action 2", null),
+ MediaAction(null, Runnable {}, "action 3", null),
+ MediaAction(null, Runnable {}, "action 4", null)
+ )
+ val data = mediaData.copy(actions = actions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action1.callOnClick()
+ verify(logger).logTapAction(eq(R.id.action1), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun openOutputSwitcher_isLogged() {
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(mediaData, KEY)
+
+ seamless.callOnClick()
+
+ verify(logger).logOpenOutputSwitcher(anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun logSeek() {
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(mediaData, KEY)
+
+ val callback: () -> Unit = {}
+ val captor = KotlinArgumentCaptor(callback::class.java)
+ verify(seekBarViewModel).logSeek = captor.capture()
+ captor.value.invoke()
+
+ verify(logger).logSeek(anyInt(), eq(PACKAGE), eq(instanceId))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 7a487b871d9d..b0f6a80bc8a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -32,6 +32,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.InstanceId;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
@@ -77,7 +78,8 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
mMediaData = new MediaData(
USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
- MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L);
+ MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
+ InstanceId.fakeInstanceId(-1), -1);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index 82a48efafa3a..b8e249fcfeb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceAction
-import android.graphics.Color
import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -25,6 +24,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -33,7 +34,6 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
@@ -45,17 +45,9 @@ private const val KEY = "TEST_KEY"
private const val KEY_ALT = "TEST_KEY_2"
private const val USER_MAIN = 0
private const val USER_GUEST = 10
-private const val APP = "APP"
-private const val BG_COLOR = Color.RED
private const val PACKAGE = "PKG"
-private const val ARTIST = "ARTIST"
-private const val TITLE = "TITLE"
-private const val DEVICE_NAME = "DEVICE_NAME"
private const val SMARTSPACE_KEY = "SMARTSPACE_KEY"
-private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
-private fun <T> any(): T = Mockito.any()
-
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -68,8 +60,6 @@ class MediaDataFilterTest : SysuiTestCase() {
@Mock
private lateinit var broadcastSender: BroadcastSender
@Mock
- private lateinit var mediaResumeListener: MediaResumeListener
- @Mock
private lateinit var mediaDataManager: MediaDataManager
@Mock
private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
@@ -83,7 +73,6 @@ class MediaDataFilterTest : SysuiTestCase() {
private lateinit var mediaDataFilter: MediaDataFilter
private lateinit var dataMain: MediaData
private lateinit var dataGuest: MediaData
- private val device = MediaDeviceData(true, null, DEVICE_NAME)
private val clock = FakeSystemClock()
@Before
@@ -99,23 +88,9 @@ class MediaDataFilterTest : SysuiTestCase() {
setUser(USER_MAIN)
// Set up test media data
- dataMain = MediaData(
+ dataMain = MediaTestUtils.emptyMediaData.copy(
userId = USER_MAIN,
- initialized = true,
- backgroundColor = BG_COLOR,
- app = APP,
- appIcon = null,
- artist = ARTIST,
- song = TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
- packageName = PACKAGE,
- token = null,
- clickIntent = null,
- device = device,
- active = true,
- resumeAction = null)
+ packageName = PACKAGE)
dataGuest = dataMain.copy(userId = USER_GUEST)
`when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 066f49a16f19..ccd8ef1b7c6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -18,6 +18,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
+import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -38,10 +39,10 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -51,7 +52,8 @@ import org.mockito.Mockito.`when` as whenever
private const val KEY = "KEY"
private const val KEY_2 = "KEY_2"
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
-private const val PACKAGE_NAME = "com.android.systemui"
+private const val PACKAGE_NAME = "com.example.app"
+private const val SYSTEM_PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "SystemUI"
private const val SESSION_ARTIST = "artist"
private const val SESSION_TITLE = "title"
@@ -92,6 +94,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
@Mock private lateinit var mediaFlags: MediaFlags
+ @Mock private lateinit var logger: MediaUiEventLogger
lateinit var mediaDataManager: MediaDataManager
lateinit var mediaNotification: StatusBarNotification
@Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
@@ -99,6 +102,8 @@ class MediaDataManagerTest : SysuiTestCase() {
@Mock private lateinit var tunerService: TunerService
@Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
+ private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
+
private val originalSmartspaceSetting = Settings.Secure.getInt(context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
@@ -128,7 +133,8 @@ class MediaDataManagerTest : SysuiTestCase() {
useQsMediaPlayer = true,
systemClock = clock,
tunerService = tunerService,
- mediaFlags = mediaFlags
+ mediaFlags = mediaFlags,
+ logger = logger
)
verify(tunerService).addTunable(capture(tunableCaptor),
eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -169,6 +175,7 @@ class MediaDataManagerTest : SysuiTestCase() {
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem))
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
+ whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
}
@After
@@ -181,15 +188,13 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testSetTimedOut_active_deactivatesMedia() {
- val data = MediaData(userId = USER_ID, initialized = true, backgroundColor = 0, app = null,
- appIcon = null, artist = null, song = null, artwork = null, actions = emptyList(),
- actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null,
- clickIntent = null, device = null, active = true, resumeAction = null)
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
+ assertThat(data.active).isTrue()
mediaDataManager.setTimedOut(KEY, timedOut = true)
assertThat(data.active).isFalse()
+ verify(logger).logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
@@ -201,9 +206,15 @@ class MediaDataManagerTest : SysuiTestCase() {
}
mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
APP_NAME, pendingIntent, PACKAGE_NAME)
+
backgroundExecutor.runAllReady()
foregroundExecutor.runAllReady()
+ verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
+ eq(true), eq(0))
+
mediaDataManager.setTimedOut(PACKAGE_NAME, timedOut = true)
+ verify(logger).logMediaTimeout(anyInt(), eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId))
// THEN it is removed and listeners are informed
foregroundExecutor.advanceClockToLast()
@@ -219,10 +230,9 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testOnMetaDataLoaded_callsListener() {
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject(), eq(true),
- eq(0))
+ addNotificationAndLoad()
+ verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_LOCAL))
}
@Test
@@ -241,7 +251,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testOnNotificationAdded_isRcn_markedRemote() {
val rcn = SbnBuilder().run {
- setPkg("com.android.systemui") // System package
+ setPkg(SYSTEM_PACKAGE_NAME)
modifyNotification(context).also {
it.setSmallIcon(android.R.drawable.ic_media_pause)
it.setStyle(MediaStyle().apply {
@@ -259,25 +269,24 @@ class MediaDataManagerTest : SysuiTestCase() {
eq(0))
assertThat(mediaDataCaptor.value!!.playbackLocation).isEqualTo(
MediaData.PLAYBACK_CAST_REMOTE)
+ verify(logger).logActiveMediaAdded(anyInt(), eq(SYSTEM_PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE))
}
@Test
fun testOnNotificationRemoved_callsListener() {
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
mediaDataManager.onNotificationRemoved(KEY)
verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
fun testOnNotificationRemoved_withResumption() {
// GIVEN that the manager has a notification with a resume action
whenever(controller.metadata).thenReturn(metadataBuilder.build())
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
val data = mediaDataCaptor.value
assertThat(data.resumption).isFalse()
mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
@@ -289,6 +298,7 @@ class MediaDataManagerTest : SysuiTestCase() {
eq(0))
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+ verify(logger).logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
@@ -333,15 +343,13 @@ class MediaDataManagerTest : SysuiTestCase() {
whenever(controller.metadata).thenReturn(metadataBuilder.build())
whenever(playbackInfo.playbackType).thenReturn(
MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
val data = mediaDataCaptor.value
val dataRemoteWithResume = data.copy(resumeAction = Runnable {},
playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume)
+ verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL))
// WHEN the notification is removed
mediaDataManager.onNotificationRemoved(KEY)
@@ -373,12 +381,34 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(data.actions).hasSize(1)
assertThat(data.semanticActions!!.playOrPause).isNotNull()
assertThat(data.lastActive).isAtLeast(currentTime)
+ verify(logger).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+ }
+
+ @Test
+ fun testResumptionDisabled_dismissesResumeControls() {
+ // WHEN there are resume controls and resumption is switched off
+ val desc = MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
+ APP_NAME, pendingIntent, PACKAGE_NAME)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
+ eq(true), eq(0))
+ val data = mediaDataCaptor.value
+ mediaDataManager.setMediaResumptionEnabled(false)
+
+ // THEN the resume controls are dismissed
+ verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME))
+ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
fun testDismissMedia_listenerCalled() {
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
val removed = mediaDataManager.dismissMediaData(KEY, 0L)
assertThat(removed).isTrue()
@@ -386,6 +416,7 @@ class MediaDataManagerTest : SysuiTestCase() {
foregroundExecutor.runAllReady()
verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
@@ -513,11 +544,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testOnMediaDataChanged_updatesLastActiveTime() {
val currentTime = clock.elapsedRealtime()
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
assertThat(mediaDataCaptor.value!!.lastActive).isAtLeast(currentTime)
}
@@ -543,12 +570,9 @@ class MediaDataManagerTest : SysuiTestCase() {
fun testOnActiveMediaConverted_doesNotUpdateLastActiveTime() {
// GIVEN that the manager has a notification with a resume action
whenever(controller.metadata).thenReturn(metadataBuilder.build())
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
val data = mediaDataCaptor.value
+ val instanceId = data.instanceId
assertThat(data.resumption).isFalse()
mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
@@ -563,6 +587,10 @@ class MediaDataManagerTest : SysuiTestCase() {
eq(0))
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
+
+ // Log as a conversion event, not as a new resume control
+ verify(logger).logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId))
+ verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
}
@Test
@@ -633,11 +661,7 @@ class MediaDataManagerTest : SysuiTestCase() {
}
whenever(controller.playbackState).thenReturn(stateBuilder.build())
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
val actions = mediaDataCaptor.value!!.semanticActions!!
@@ -679,11 +703,7 @@ class MediaDataManagerTest : SysuiTestCase() {
}
whenever(controller.playbackState).thenReturn(stateBuilder.build())
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
val actions = mediaDataCaptor.value!!.semanticActions!!
@@ -722,11 +742,7 @@ class MediaDataManagerTest : SysuiTestCase() {
whenever(controller.playbackState).thenReturn(stateBuilder.build())
whenever(controller.extras).thenReturn(extras)
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
val actions = mediaDataCaptor.value!!.semanticActions!!
@@ -744,4 +760,49 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(actions.custom1).isNotNull()
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1])
}
+
+ @Test
+ fun testPlaybackLocationChange_isLogged() {
+ // Media control added for local playback
+ addNotificationAndLoad()
+ val instanceId = mediaDataCaptor.value.instanceId
+
+ // Location is updated to local cast
+ whenever(controller.metadata).thenReturn(metadataBuilder.build())
+ whenever(playbackInfo.playbackType).thenReturn(
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+ addNotificationAndLoad()
+ verify(logger).logPlaybackLocationChange(anyInt(), eq(PACKAGE_NAME),
+ eq(instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL))
+
+ // update to remote cast
+ val rcn = SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME) // System package
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ setRemotePlaybackInfo("Remote device", 0, null)
+ })
+ }
+ build()
+ }
+
+ mediaDataManager.onNotificationAdded(KEY, rcn)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(logger).logPlaybackLocationChange(anyInt(), eq(SYSTEM_PACKAGE_NAME),
+ eq(instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE))
+ }
+
+ /**
+ * Helper function to add a media notification and capture the resulting MediaData
+ */
+ private fun addNotificationAndLoad() {
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+ eq(0))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index d912a8906ab3..e6f48ecd37de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -44,7 +45,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -57,12 +57,8 @@ private const val KEY = "TEST_KEY"
private const val KEY_OLD = "TEST_KEY_OLD"
private const val PACKAGE = "PKG"
private const val SESSION_KEY = "SESSION_KEY"
-private const val SESSION_TITLE = "SESSION_TITLE"
private const val DEVICE_NAME = "DEVICE_NAME"
private const val REMOTE_DEVICE_NAME = "REMOTE_DEVICE_NAME"
-private const val USER_ID = 0
-
-private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -115,24 +111,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
// Create a media sesssion and notification for testing.
session = MediaSession(context, SESSION_KEY)
- mediaData = MediaData(
- userId = USER_ID,
- initialized = true,
- backgroundColor = 0,
- app = PACKAGE,
- appIcon = null,
- artist = null,
- song = SESSION_TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
+ mediaData = MediaTestUtils.emptyMediaData.copy(
packageName = PACKAGE,
- token = session.sessionToken,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null)
-
+ token = session.sessionToken)
whenever(controllerFactory.create(session.sessionToken))
.thenReturn(controller)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
index ceeb0dbb159e..7bd210d762f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -163,27 +163,12 @@ public class MediaPlayerDataTest : SysuiTestCase() {
isPlaying: Boolean?,
location: Int,
resumption: Boolean
- ) = MediaData(
- userId = 0,
- initialized = false,
- backgroundColor = 0,
+ ) = MediaTestUtils.emptyMediaData.copy(
app = app,
- appIcon = null,
- artist = null,
- song = null,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
packageName = "package: $app",
- token = null,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null,
playbackLocation = location,
resumption = resumption,
notificationKey = "key: $app",
- hasCheckedForResume = false,
isPlaying = isPlaying
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
index 30ee2e4d3431..2d34913d3467 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -24,7 +24,6 @@ import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo
-import android.graphics.Color
import android.media.MediaDescription
import android.media.session.MediaSession
import android.provider.Settings
@@ -57,13 +56,9 @@ import org.mockito.MockitoAnnotations
private const val KEY = "TEST_KEY"
private const val OLD_KEY = "RESUME_KEY"
-private const val APP = "APP"
-private const val BG_COLOR = Color.RED
private const val PACKAGE_NAME = "PKG"
private const val CLASS_NAME = "CLASS"
-private const val ARTIST = "ARTIST"
private const val TITLE = "TITLE"
-private const val USER_ID = 0
private const val MEDIA_PREFERENCES = "media_control_prefs"
private const val RESUME_COMPONENTS = "package1/class1:package2/class2:package3/class3"
@@ -130,24 +125,10 @@ class MediaResumeListenerTest : SysuiTestCase() {
resumeListener.setManager(mediaDataManager)
mediaDataManager.addListener(resumeListener)
- data = MediaData(
- userId = USER_ID,
- initialized = true,
- backgroundColor = BG_COLOR,
- app = APP,
- appIcon = null,
- artist = ARTIST,
+ data = MediaTestUtils.emptyMediaData.copy(
song = TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
packageName = PACKAGE_NAME,
- token = token,
- clickIntent = null,
- device = device,
- active = true,
- notificationKey = KEY,
- resumeAction = null)
+ token = token)
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
index 5d53181c8345..ee4f8db48ae3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.media
-import android.graphics.Color
import android.media.session.MediaController
import android.media.session.MediaController.PlaybackInfo
import android.media.session.MediaSession
@@ -39,7 +38,6 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
@@ -50,35 +48,12 @@ import org.mockito.Mockito.`when` as whenever
private const val PACKAGE = "PKG"
private const val KEY = "TEST_KEY"
private const val NOTIF_KEY = "TEST_KEY"
-private const val SESSION_ARTIST = "SESSION_ARTIST"
-private const val SESSION_TITLE = "SESSION_TITLE"
-private const val APP_NAME = "APP_NAME"
-private const val USER_ID = 0
-
-private val info = MediaData(
- userId = USER_ID,
- initialized = true,
- backgroundColor = Color.DKGRAY,
- app = APP_NAME,
- appIcon = null,
- artist = SESSION_ARTIST,
- song = SESSION_TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
+
+private val info = MediaTestUtils.emptyMediaData.copy(
packageName = PACKAGE,
- token = null,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null,
- resumption = false,
- notificationKey = NOTIF_KEY,
- hasCheckedForResume = false
+ notificationKey = NOTIF_KEY
)
-private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
-
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
new file mode 100644
index 000000000000..c7ef94eb6a64
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
@@ -0,0 +1,27 @@
+package com.android.systemui.media
+
+import com.android.internal.logging.InstanceId
+
+class MediaTestUtils {
+ companion object {
+ val emptyMediaData = MediaData(
+ userId = 0,
+ initialized = true,
+ backgroundColor = 0,
+ app = null,
+ appIcon = null,
+ artist = null,
+ song = null,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = "",
+ token = null,
+ clickIntent = null,
+ device = null,
+ active = true,
+ resumeAction = null,
+ instanceId = InstanceId.fakeInstanceId(-1),
+ appUid = -1)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index 8c2fed5bd2ed..91169834752e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -50,9 +51,7 @@ private const val PACKAGE = "PKG"
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
-private const val USER_ID = 0
-private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
}
@@ -96,23 +95,11 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
}
session.setActive(true)
- mediaData = MediaData(
- userId = USER_ID,
- initialized = true,
- backgroundColor = 0,
- app = PACKAGE,
- appIcon = null,
- artist = null,
- song = SESSION_TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
- packageName = PACKAGE,
- token = session.sessionToken,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null)
+ mediaData = MediaTestUtils.emptyMediaData.copy(
+ app = PACKAGE,
+ packageName = PACKAGE,
+ token = session.sessionToken
+ )
resumeData = mediaData.copy(token = null, active = false, resumption = true)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index 399c89373d63..20f5e4c19402 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -36,6 +36,7 @@ import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Ignore
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -46,6 +47,7 @@ import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
@SmallTest
@@ -71,14 +73,14 @@ public class SeekBarViewModelTest : SysuiTestCase() {
private val token1 = MediaSession.Token(1, null)
private val token2 = MediaSession.Token(2, null)
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+
@Before
fun setUp() {
fakeExecutor = FakeExecutor(FakeSystemClock())
viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor))
- viewModel.logSmartspaceClick = { }
- mockController = mock(MediaController::class.java)
+ viewModel.logSeek = { }
whenever(mockController.sessionToken).thenReturn(token1)
- mockTransport = mock(MediaController.TransportControls::class.java)
// LiveData to run synchronously
ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 6abdea5d9230..bc1abe62c7ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -25,6 +25,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -87,4 +89,42 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
controller.setDozeAmount(0.5f, false /* animated */)
verify(listener).onDozeAmountChanged(eq(0.5f), anyFloat())
}
+
+ @Test
+ fun testSetState_appliesState_sameStateButDifferentUpcomingState() {
+ controller.state = StatusBarState.SHADE
+ controller.setUpcomingState(StatusBarState.KEYGUARD)
+
+ assertEquals(controller.state, StatusBarState.SHADE)
+
+ // We should return true (state change was applied) despite going from SHADE to SHADE, since
+ // the upcoming state was set to KEYGUARD.
+ assertTrue(controller.setState(StatusBarState.SHADE))
+ }
+
+ @Test
+ fun testSetState_appliesState_differentStateEqualToUpcomingState() {
+ controller.state = StatusBarState.SHADE
+ controller.setUpcomingState(StatusBarState.KEYGUARD)
+
+ assertEquals(controller.state, StatusBarState.SHADE)
+
+ // Make sure we apply a SHADE -> KEYGUARD state change when the upcoming state is KEYGUARD.
+ assertTrue(controller.setState(StatusBarState.KEYGUARD))
+ }
+
+ @Test
+ fun testSetState_doesNotApplyState_currentAndUpcomingStatesSame() {
+ controller.state = StatusBarState.SHADE
+ controller.setUpcomingState(StatusBarState.SHADE)
+
+ assertEquals(controller.state, StatusBarState.SHADE)
+
+ // We're going from SHADE -> SHADE, and the upcoming state is also SHADE, this should not do
+ // anything.
+ assertFalse(controller.setState(StatusBarState.SHADE))
+
+ // Double check that we can still force it to happen.
+ assertTrue(controller.setState(StatusBarState.SHADE, true /* force */))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 5f2bbd341962..077b41a0aa90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -126,6 +126,12 @@ public class DozeParametersTest extends SysuiTestCase {
setAodEnabledForTest(true);
setShouldControlUnlockedScreenOffForTest(true);
setDisplayNeedsBlankingForTest(false);
+
+ // Default to false here (with one test to make sure that when it returns true, we respect
+ // that). We'll test the specific conditions for this to return true/false in the
+ // UnlockedScreenOffAnimationController's tests.
+ when(mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation())
+ .thenReturn(false);
}
@Test
@@ -174,9 +180,12 @@ public class DozeParametersTest extends SysuiTestCase {
*/
@Test
public void testControlUnlockedScreenOffAnimation_dozeAfterScreenOff_false() {
+ mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true);
+
// If AOD is disabled, we shouldn't want to control screen off. Also, let's double check
// that when that value is updated, we called through to PowerManager.
setAodEnabledForTest(false);
+
assertFalse(mDozeParameters.shouldControlScreenOff());
assertTrue(mPowerManagerDozeAfterScreenOff);
@@ -188,7 +197,6 @@ public class DozeParametersTest extends SysuiTestCase {
@Test
public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() {
- setShouldControlUnlockedScreenOffForTest(true);
when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(false);
assertFalse(mDozeParameters.shouldControlUnlockedScreenOff());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 050563a5707c..0936b773d4b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.GlobalSettings
+import junit.framework.Assert.assertFalse
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -133,7 +134,7 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
*/
@Test
fun testAodUiShownIfNotInteractive() {
- `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
+ `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
`when`(powerManager.isInteractive).thenReturn(false)
val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
@@ -156,7 +157,7 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
*/
@Test
fun testAodUiNotShownIfInteractive() {
- `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
+ `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
`when`(powerManager.isInteractive).thenReturn(true)
val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
@@ -167,4 +168,13 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
verify(notificationPanelViewController, never()).showAodUi()
}
+
+ @Test
+ fun testNoAnimationPlaying_dozeParamsCanNotControlScreenOff() {
+ `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(false)
+
+ assertFalse(controller.shouldPlayUnlockedScreenOffAnimation())
+ controller.startAnimation()
+ assertFalse(controller.isAnimationPlaying())
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index 12cfb32ab57e..14e087846417 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -51,7 +51,7 @@ import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.DeviceConfigProxyFake;
-import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.FakeSettings;
import com.google.common.collect.ImmutableList;
@@ -70,11 +70,11 @@ public class LocationControllerImplTest extends SysuiTestCase {
private TestableLooper mTestableLooper;
private DeviceConfigProxy mDeviceConfigProxy;
private UiEventLoggerFake mUiEventLogger;
+ private FakeSettings mSecureSettings;
@Mock private PackageManager mPackageManager;
@Mock private AppOpsController mAppOpsController;
@Mock private UserTracker mUserTracker;
- @Mock private SecureSettings mSecureSettings;
@Before
public void setup() {
@@ -85,7 +85,7 @@ public class LocationControllerImplTest extends SysuiTestCase {
.thenReturn(ImmutableList.of(new UserInfo(0, "name", 0)));
mDeviceConfigProxy = new DeviceConfigProxyFake();
mUiEventLogger = new UiEventLoggerFake();
-
+ mSecureSettings = new FakeSettings();
mTestableLooper = TestableLooper.get(this);
mLocationController = new LocationControllerImpl(mContext,
@@ -248,8 +248,7 @@ public class LocationControllerImplTest extends SysuiTestCase {
@Test
public void testCallbackNotified_additionalOps_shouldShowSystem() {
- when(mSecureSettings.getInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0))
- .thenReturn(1);
+ mSecureSettings.putInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 1);
LocationChangeCallback callback = mock(LocationChangeCallback.class);
mLocationController.addCallback(callback);
mDeviceConfigProxy.setProperty(
@@ -282,8 +281,7 @@ public class LocationControllerImplTest extends SysuiTestCase {
verify(callback, times(1)).onLocationActiveChanged(false);
- when(mSecureSettings.getInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0))
- .thenReturn(0);
+ mSecureSettings.putInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0);
mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
"com.system.app", true);
@@ -367,4 +365,46 @@ public class LocationControllerImplTest extends SysuiTestCase {
// No new callbacks
verify(callback).onLocationSettingsChanged(anyBoolean());
}
+
+ @Test
+ public void testExperimentFlipsSystemFlag() throws Exception {
+ mSecureSettings.putInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0);
+ mDeviceConfigProxy.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED,
+ "true",
+ true);
+ // Show system experiment not running
+ mDeviceConfigProxy.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM,
+ "false",
+ false);
+ mTestableLooper.processAllMessages();
+
+ // Flip experiment on
+ mDeviceConfigProxy.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM,
+ "true",
+ true);
+ mTestableLooper.processAllMessages();
+
+ // Verify settings were flipped
+ assertThat(mSecureSettings.getInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS))
+ .isEqualTo(1);
+ assertThat(mSecureSettings.getInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED))
+ .isEqualTo(1);
+
+ // Flip experiment off
+ mDeviceConfigProxy.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM,
+ "false",
+ false);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mSecureSettings.getInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED))
+ .isEqualTo(0);
+ }
} \ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index c637045589e7..bc3ffab5ccb6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -647,6 +647,14 @@ public class AccessibilitySecurityPolicy {
return false;
}
+ if ((serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0) {
+ Slog.w(LOG_TAG, "Skipping accessibility service " + new ComponentName(
+ serviceInfo.packageName, serviceInfo.name).flattenToShortString()
+ + ": the service is the external one and doesn't allow to register as "
+ + "an accessibility service ");
+ return false;
+ }
+
int servicePackageUid = serviceInfo.applicationInfo.uid;
if (mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE,
servicePackageUid, serviceInfo.packageName, null, null)
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 75b6554a013b..7e277ba3c0d0 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -3182,7 +3182,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@Override
public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId,
- @Nullable AutofillValue value) {
+ @Nullable AutofillValue value, int flags) {
synchronized (mLock) {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onFillReady() rejected - session: "
@@ -3207,7 +3207,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
- if (requestShowFillDialog(response, filledId, filterText)) {
+ if (requestShowFillDialog(response, filledId, filterText, flags)) {
synchronized (mLock) {
final ViewState currentView = mViewStates.get(mCurrentViewId);
currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
@@ -3312,12 +3312,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
private boolean requestShowFillDialog(FillResponse response,
- AutofillId filledId, String filterText) {
+ AutofillId filledId, String filterText, int flags) {
if (!isFillDialogUiEnabled()) {
// Unsupported fill dialog UI
return false;
}
+ if ((flags & FillRequest.FLAG_IME_SHOWING) != 0) {
+ // IME is showing, fallback to normal suggestions UI
+ return false;
+ }
+
final AutofillId[] ids = response.getFillDialogTriggerIds();
if (ids == null || !ArrayUtils.contains(ids, filledId)) {
return false;
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index 4a14f1420cf2..12695ac6ad35 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -43,7 +43,7 @@ final class ViewState {
* Called when the fill UI is ready to be shown for this view.
*/
void onFillReady(@NonNull FillResponse fillResponse, @NonNull AutofillId focusedId,
- @Nullable AutofillValue value);
+ @Nullable AutofillValue value, int flags);
}
private static final String TAG = "ViewState";
@@ -202,7 +202,7 @@ final class ViewState {
/**
* Calls {@link
- * Listener#onFillReady(FillResponse, AutofillId, AutofillValue)} if the
+ * Listener#onFillReady(FillResponse, AutofillId, AutofillValue, int)} if the
* fill UI is ready to be displayed (i.e. when response and bounds are set).
*/
void maybeCallOnFillReady(int flags) {
@@ -213,7 +213,7 @@ final class ViewState {
// First try the current response associated with this View.
if (mResponse != null) {
if (mResponse.getDatasets() != null || mResponse.getAuthentication() != null) {
- mListener.onFillReady(mResponse, this.id, mCurrentValue);
+ mListener.onFillReady(mResponse, this.id, mCurrentValue, flags);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2c0514d7d0aa..730907c7d049 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1875,7 +1875,7 @@ public final class ActiveServices {
if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
resetFgsRestrictionLocked(r);
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
- r.appInfo.uid, r.intent.getIntent(), r, r.userId,false);
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId, false);
final String temp = "startForegroundDelayMs:" + delayMs;
if (r.mInfoAllowStartForeground != null) {
r.mInfoAllowStartForeground += "; " + temp;
@@ -1888,12 +1888,8 @@ public final class ActiveServices {
} else if (r.mStartForegroundCount >= 1) {
// The second or later time startForeground() is called after service is
// started. Check for app state again.
- final long delayMs = SystemClock.elapsedRealtime() -
- r.mLastSetFgsRestrictionTime;
- if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
- setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
- r.appInfo.uid, r.intent.getIntent(), r, r.userId,false);
- }
+ setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId, false);
}
// If the foreground service is not started from TOP process, do not allow it to
// have while-in-use location/camera/microphone access.
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index e8d8fb828542..5727ffc468df 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -280,15 +280,26 @@ public class FingerprintService extends SystemService {
@SuppressWarnings("deprecation")
@Override // Binder call
- public long authenticate(final IBinder token, final long operationId,
- final int sensorId, final int userId, final IFingerprintServiceReceiver receiver,
- final String opPackageName, boolean ignoreEnrollmentState) {
+ public long authenticate(
+ final IBinder token,
+ final long operationId,
+ final int sensorId,
+ final int userId,
+ final IFingerprintServiceReceiver receiver,
+ final String opPackageName,
+ final String attributionTag,
+ boolean ignoreEnrollmentState) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
- if (!canUseFingerprint(opPackageName, true /* requireForeground */, callingUid,
- callingPid, callingUserId)) {
+ if (!canUseFingerprint(
+ opPackageName,
+ attributionTag,
+ true /* requireForeground */,
+ callingUid,
+ callingPid,
+ callingUserId)) {
Slog.w(TAG, "Authenticate rejecting package: " + opPackageName);
return -1;
}
@@ -487,16 +498,23 @@ public class FingerprintService extends SystemService {
provider.startPreparedClient(sensorId, cookie);
}
-
@Override // Binder call
- public void cancelAuthentication(final IBinder token, final String opPackageName,
+ public void cancelAuthentication(
+ final IBinder token,
+ final String opPackageName,
+ final String attributionTag,
long requestId) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
- if (!canUseFingerprint(opPackageName, true /* requireForeground */, callingUid,
- callingPid, callingUserId)) {
+ if (!canUseFingerprint(
+ opPackageName,
+ attributionTag,
+ true /* requireForeground */,
+ callingUid,
+ callingPid,
+ callingUserId)) {
Slog.w(TAG, "cancelAuthentication rejecting package: " + opPackageName);
return;
}
@@ -645,9 +663,13 @@ public class FingerprintService extends SystemService {
}
@Override // Binder call
- public boolean isHardwareDetectedDeprecated(String opPackageName) {
- if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
- Binder.getCallingUid(), Binder.getCallingPid(),
+ public boolean isHardwareDetectedDeprecated(String opPackageName, String attributionTag) {
+ if (!canUseFingerprint(
+ opPackageName,
+ attributionTag,
+ false /* foregroundOnly */,
+ Binder.getCallingUid(),
+ Binder.getCallingPid(),
UserHandle.getCallingUserId())) {
return false;
}
@@ -696,9 +718,14 @@ public class FingerprintService extends SystemService {
}
@Override // Binder call
- public List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName) {
- if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
- Binder.getCallingUid(), Binder.getCallingPid(),
+ public List<Fingerprint> getEnrolledFingerprints(
+ int userId, String opPackageName, String attributionTag) {
+ if (!canUseFingerprint(
+ opPackageName,
+ attributionTag,
+ false /* foregroundOnly */,
+ Binder.getCallingUid(),
+ Binder.getCallingPid(),
UserHandle.getCallingUserId())) {
return Collections.emptyList();
}
@@ -711,9 +738,14 @@ public class FingerprintService extends SystemService {
}
@Override // Binder call
- public boolean hasEnrolledFingerprintsDeprecated(int userId, String opPackageName) {
- if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
- Binder.getCallingUid(), Binder.getCallingPid(),
+ public boolean hasEnrolledFingerprintsDeprecated(
+ int userId, String opPackageName, String attributionTag) {
+ if (!canUseFingerprint(
+ opPackageName,
+ attributionTag,
+ false /* foregroundOnly */,
+ Binder.getCallingUid(),
+ Binder.getCallingPid(),
UserHandle.getCallingUserId())) {
return false;
}
@@ -1093,12 +1125,15 @@ public class FingerprintService extends SystemService {
return provider.second.getEnrolledFingerprints(provider.first, userId);
}
- /**
- * Checks for public API invocations to ensure that permissions, etc are granted/correct.
- */
+ /** Checks for public API invocations to ensure that permissions, etc are granted/correct. */
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
- private boolean canUseFingerprint(String opPackageName, boolean requireForeground, int uid,
- int pid, int userId) {
+ private boolean canUseFingerprint(
+ String opPackageName,
+ String attributionTag,
+ boolean requireForeground,
+ int uid,
+ int pid,
+ int userId) {
if (getContext().checkCallingPermission(USE_FINGERPRINT)
!= PackageManager.PERMISSION_GRANTED) {
Utils.checkPermission(getContext(), USE_BIOMETRIC);
@@ -1114,7 +1149,7 @@ public class FingerprintService extends SystemService {
Slog.w(TAG, "Rejecting " + opPackageName + "; not a current user or profile");
return false;
}
- if (!checkAppOps(uid, opPackageName)) {
+ if (!checkAppOps(uid, opPackageName, attributionTag)) {
Slog.w(TAG, "Rejecting " + opPackageName + "; permission denied");
return false;
}
@@ -1125,12 +1160,13 @@ public class FingerprintService extends SystemService {
return true;
}
- private boolean checkAppOps(int uid, String opPackageName) {
+ private boolean checkAppOps(int uid, String opPackageName, String attributionTag) {
boolean appOpsOk = false;
- if (mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, uid, opPackageName)
+ if (mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, uid, opPackageName, attributionTag, null)
== AppOpsManager.MODE_ALLOWED) {
appOpsOk = true;
- } else if (mAppOps.noteOp(AppOpsManager.OP_USE_FINGERPRINT, uid, opPackageName)
+ } else if (mAppOps.noteOp(
+ AppOpsManager.OP_USE_FINGERPRINT, uid, opPackageName, attributionTag, null)
== AppOpsManager.MODE_ALLOWED) {
appOpsOk = true;
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index a6da4a6a4260..35217dba7c28 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2692,6 +2692,9 @@ public class Vpn {
return; // VPN has been shut down.
}
+ // Clear mInterface to prevent Ikev2VpnRunner being cleared when
+ // interfaceRemoved() is called.
+ mInterface = null;
// Without MOBIKE, we have no way to seamlessly migrate. Close on old
// (non-default) network, and start the new one.
resetIkeState();
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 50f5536d9f4b..436c434ccbca 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -47,8 +47,7 @@ import android.service.dreams.IDreamManager;
import android.util.Slog;
import android.view.Display;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.UiEvent;
+import com.android.internal.R;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.util.DumpUtils;
@@ -82,6 +81,7 @@ public final class DreamManagerService extends SystemService {
private final PowerManager.WakeLock mDozeWakeLock;
private final ActivityTaskManagerInternal mAtmInternal;
private final UiEventLogger mUiEventLogger;
+ private final DreamUiEventLogger mDreamUiEventLogger;
private final ComponentName mAmbientDisplayComponent;
private Binder mCurrentDreamToken;
@@ -99,26 +99,6 @@ public final class DreamManagerService extends SystemService {
private AmbientDisplayConfiguration mDozeConfig;
- @VisibleForTesting
- public enum DreamManagerEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "The screensaver has started.")
- DREAM_START(577),
-
- @UiEvent(doc = "The screensaver has stopped.")
- DREAM_STOP(578);
-
- private final int mId;
-
- DreamManagerEvent(int id) {
- mId = id;
- }
-
- @Override
- public int getId() {
- return mId;
- }
- }
-
public DreamManagerService(Context context) {
super(context);
mContext = context;
@@ -131,6 +111,8 @@ public final class DreamManagerService extends SystemService {
mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG);
mDozeConfig = new AmbientDisplayConfiguration(mContext);
mUiEventLogger = new UiEventLoggerImpl();
+ mDreamUiEventLogger = new DreamUiEventLoggerImpl(
+ mContext.getResources().getString(R.string.config_loggable_dream_prefix));
AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
}
@@ -417,7 +399,10 @@ public final class DreamManagerService extends SystemService {
mCurrentDreamUserId = userId;
if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
- mUiEventLogger.log(DreamManagerEvent.DREAM_START);
+ // TODO(b/213906448): Remove when metrics based on new atom are fully rolled out.
+ mUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_START);
+ mDreamUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_START,
+ mCurrentDreamName.flattenToString());
}
PowerManager.WakeLock wakeLock = mPowerManager
@@ -453,7 +438,10 @@ public final class DreamManagerService extends SystemService {
private void cleanupDreamLocked() {
if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
- mUiEventLogger.log(DreamManagerEvent.DREAM_STOP);
+ // TODO(b/213906448): Remove when metrics based on new atom are fully rolled out.
+ mUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_STOP);
+ mDreamUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_STOP,
+ mCurrentDreamName.flattenToString());
}
mCurrentDreamToken = null;
mCurrentDreamName = null;
diff --git a/services/core/java/com/android/server/dreams/DreamUiEventLogger.java b/services/core/java/com/android/server/dreams/DreamUiEventLogger.java
new file mode 100644
index 000000000000..5eb4d1c65b25
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/DreamUiEventLogger.java
@@ -0,0 +1,62 @@
+/*
+ * 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 com.android.server.dreams;
+
+import android.annotation.NonNull;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+/**
+ * Logging interface for Dream UI events. Normal implementation is DreamUiEventLoggerImpl.
+ *
+ * See DreamUiEventReported atom in atoms.proto for more context.
+ * @hide
+ */
+public interface DreamUiEventLogger {
+ /** Put your Event IDs in enums that implement this interface, and document them using the
+ * UiEventEnum annotation.
+ * Event IDs must be globally unique. This will be enforced by tooling (forthcoming).
+ * OEMs should use event IDs above 100000 and below 1000000 (1 million).
+ */
+ enum DreamUiEventEnum implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The screensaver has started.")
+ DREAM_START(577),
+
+ @UiEvent(doc = "The screensaver has stopped.")
+ DREAM_STOP(578);
+
+ private final int mId;
+
+ DreamUiEventEnum(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
+ /**
+ * Log a simple event with dream component name, with no package information. Does nothing if
+ * event.getId() <= 0.
+ * @param event an enum implementing UiEventEnum interface.
+ * @param dreamComponentName the component name of the dream in use.
+ */
+ void log(@NonNull UiEventLogger.UiEventEnum event, @NonNull String dreamComponentName);
+}
diff --git a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
new file mode 100644
index 000000000000..26ca74a8d808
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
@@ -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 com.android.server.dreams;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Standard implementation of DreamUiEventLogger, writing to FrameworkStatsLog.
+ *
+ * See DreamUiEventReported atom in atoms.proto for more context.
+ * @hide
+ */
+public class DreamUiEventLoggerImpl implements DreamUiEventLogger {
+ final String mLoggableDreamPrefix;
+
+ DreamUiEventLoggerImpl(String loggableDreamPrefix) {
+ mLoggableDreamPrefix = loggableDreamPrefix;
+ }
+
+ @Override
+ public void log(UiEventLogger.UiEventEnum event, String dreamComponentName) {
+ final int eventID = event.getId();
+ if (eventID <= 0) {
+ return;
+ }
+ final boolean isFirstPartyDream =
+ mLoggableDreamPrefix.isEmpty() ? false : dreamComponentName.startsWith(
+ mLoggableDreamPrefix);
+ FrameworkStatsLog.write(FrameworkStatsLog.DREAM_UI_EVENT_REPORTED,
+ /* uid = 1 */ 0,
+ /* event_id = 2 */ eventID,
+ /* instance_id = 3 */ 0,
+ /* dream_component_name = 4 */ isFirstPartyDream ? dreamComponentName : "other");
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 27f64ecbe99a..ccb27eef2075 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -214,6 +214,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
} else {
// We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3
getWakeLock().release();
+ mService.getHdmiCecNetwork().removeDevicesConnectedToPort(portId);
mDelayedStandbyHandler.removeCallbacksAndMessages(null);
mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(),
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index d249d578cf67..9212fb604721 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1245,6 +1245,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@ServiceThreadOnly
void onHotplug(int portId, boolean connected) {
assertRunOnServiceThread();
+
+ if (!connected) {
+ mService.getHdmiCecNetwork().removeCecSwitches(portId);
+ }
+
// Turning System Audio Mode off when the AVR is unlugged or standby.
// When the device is not unplugged but reawaken from standby, we check if the System
// Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index 185eaa92d511..8b6d16a171f5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -259,7 +259,10 @@ public class HdmiCecNetwork {
// The addition of a local device should not notify listeners
return;
}
- if (old == null) {
+ if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
+ // Don't notify listeners of devices that haven't reported their physical address yet
+ return;
+ } else if (old == null || old.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
invokeDeviceEventListener(info,
HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
} else if (!old.equals(info)) {
@@ -286,7 +289,10 @@ public class HdmiCecNetwork {
assertRunOnServiceThread();
HdmiDeviceInfo old = addDeviceInfo(info);
- if (old == null) {
+ if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
+ // Don't notify listeners of devices that haven't reported their physical address yet
+ return;
+ } else if (old == null || old.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
invokeDeviceEventListener(info,
HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
} else if (!old.equals(info)) {
@@ -360,10 +366,12 @@ public class HdmiCecNetwork {
// This only applies to TV devices.
// Returns true if the policy is set to true, and the device to check does not have
// a parent CEC device (which should be the CEC-enabled switch) in the list.
+ // Devices with an invalid physical address are assumed to NOT be connected to a legacy switch.
private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
return isLocalDeviceAddress(Constants.ADDR_TV)
&& HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
- && !isConnectedToCecSwitch(info.getPhysicalAddress(), getCecSwitches());
+ && !isConnectedToCecSwitch(info.getPhysicalAddress(), getCecSwitches())
+ && info.getPhysicalAddress() != HdmiDeviceInfo.PATH_INVALID;
}
/**
@@ -377,6 +385,10 @@ public class HdmiCecNetwork {
HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
localDevice.mCecMessageCache.flushMessagesFrom(address);
+ if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
+ // Don't notify listeners of devices that haven't reported their physical address yet
+ return;
+ }
invokeDeviceEventListener(info,
HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
}
@@ -489,6 +501,34 @@ public class HdmiCecNetwork {
}
/**
+ * Attempts to deduce the device type of a device given its logical address.
+ * If multiple types are possible, returns {@link HdmiDeviceInfo#DEVICE_RESERVED}.
+ */
+ private static int logicalAddressToDeviceType(int logicalAddress) {
+ switch (logicalAddress) {
+ case Constants.ADDR_TV:
+ return HdmiDeviceInfo.DEVICE_TV;
+ case Constants.ADDR_RECORDER_1:
+ case Constants.ADDR_RECORDER_2:
+ case Constants.ADDR_RECORDER_3:
+ return HdmiDeviceInfo.DEVICE_RECORDER;
+ case Constants.ADDR_TUNER_1:
+ case Constants.ADDR_TUNER_2:
+ case Constants.ADDR_TUNER_3:
+ case Constants.ADDR_TUNER_4:
+ return HdmiDeviceInfo.DEVICE_TUNER;
+ case Constants.ADDR_PLAYBACK_1:
+ case Constants.ADDR_PLAYBACK_2:
+ case Constants.ADDR_PLAYBACK_3:
+ return HdmiDeviceInfo.DEVICE_PLAYBACK;
+ case Constants.ADDR_AUDIO_SYSTEM:
+ return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+ default:
+ return HdmiDeviceInfo.DEVICE_RESERVED;
+ }
+ }
+
+ /**
* Passively listen to incoming CEC messages.
*
* This shall not result in any CEC messages being sent.
@@ -502,6 +542,7 @@ public class HdmiCecNetwork {
HdmiDeviceInfo newDevice = HdmiDeviceInfo.cecDeviceBuilder()
.setLogicalAddress(sourceAddress)
.setDisplayName(HdmiUtils.getDefaultDeviceName(sourceAddress))
+ .setDeviceType(logicalAddressToDeviceType(sourceAddress))
.build();
addCecDevice(newDevice);
}
@@ -699,7 +740,7 @@ public class HdmiCecNetwork {
return mCecSwitches;
}
- void removeDevicesConnectedToPort(int portId) {
+ void removeCecSwitches(int portId) {
Iterator<Integer> it = mCecSwitches.iterator();
while (it.hasNext()) {
int path = it.next();
@@ -708,6 +749,11 @@ public class HdmiCecNetwork {
it.remove();
}
}
+ }
+
+ void removeDevicesConnectedToPort(int portId) {
+ removeCecSwitches(portId);
+
List<Integer> toRemove = new ArrayList<>();
for (int i = 0; i < mDeviceInfos.size(); i++) {
int key = mDeviceInfos.keyAt(i);
@@ -816,7 +862,10 @@ public class HdmiCecNetwork {
public void clearDeviceList() {
assertRunOnServiceThread();
for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
- if (info.getPhysicalAddress() == getPhysicalAddress()) {
+ if (info.getPhysicalAddress() == getPhysicalAddress()
+ || info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
+ // Don't notify listeners of local devices or devices that haven't reported their
+ // physical address yet
continue;
}
invokeDeviceEventListener(info,
@@ -863,10 +912,13 @@ public class HdmiCecNetwork {
* on the current device.
*/
int physicalAddressToPortId(int path) {
+ int physicalAddress = getPhysicalAddress();
+ if (path == physicalAddress) {
+ // The local device isn't connected to any port; assign portId 0
+ return Constants.CEC_SWITCH_HOME;
+ }
int mask = 0xF000;
int finalMask = 0xF000;
- int physicalAddress;
- physicalAddress = getPhysicalAddress();
int maskedAddress = physicalAddress;
while (maskedAddress != 0) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 8ac233114b48..12380abd0d38 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1363,10 +1363,6 @@ public class HdmiControlService extends SystemService {
device.onHotplug(portId, connected);
}
- if (!connected) {
- mHdmiCecNetwork.removeDevicesConnectedToPort(portId);
- }
-
announceHotplugEvent(portId, connected);
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 07858c84712c..bd0e172064c0 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -660,6 +660,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
byte[] data = psdsDownloader.downloadPsdsData(psdsType);
if (data != null) {
mHandler.post(() -> {
+ FrameworkStatsLog.write(FrameworkStatsLog.GNSS_PSDS_DOWNLOAD_REPORTED,
+ psdsType);
if (DEBUG) Log.d(TAG, "calling native_inject_psds_data");
mGnssNative.injectPsdsData(data, data.length, psdsType);
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 152c74553d32..17dc403cf2c5 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -44,7 +44,6 @@ import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.SparseSetArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -52,7 +51,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.QuadFunction;
import com.android.server.FgThread;
-import com.android.server.LocalServices;
import com.android.server.compat.CompatChange;
import com.android.server.om.OverlayReferenceMapper;
import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -65,14 +63,18 @@ import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.component.ParsedProvider;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
-import com.android.server.utils.Snapshots;
import com.android.server.utils.Watchable;
import com.android.server.utils.WatchableImpl;
+import com.android.server.utils.Watched;
+import com.android.server.utils.WatchedArrayList;
import com.android.server.utils.WatchedArrayMap;
+import com.android.server.utils.WatchedArraySet;
import com.android.server.utils.WatchedSparseBooleanMatrix;
+import com.android.server.utils.WatchedSparseSetArray;
import com.android.server.utils.Watcher;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -85,7 +87,7 @@ import java.util.concurrent.Executor;
* manifests.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class AppsFilter implements Watchable, Snappable {
+public class AppsFilterImpl implements AppsFilterSnapshot, Watchable, Snappable {
private static final String TAG = "AppsFilter";
@@ -100,32 +102,48 @@ public class AppsFilter implements Watchable, Snappable {
* application B is implicitly allowed to query for application A; regardless of any manifest
* entries.
*/
- private final SparseSetArray<Integer> mImplicitlyQueryable = new SparseSetArray<>();
+ @GuardedBy("mLock")
+ @Watched
+ private final WatchedSparseSetArray<Integer> mImplicitlyQueryable;
+ private final SnapshotCache<WatchedSparseSetArray<Integer>> mImplicitQueryableSnapshot;
/**
* This contains a list of app UIDs that are implicitly queryable because another app explicitly
* interacted with it, but could keep across package updates. For example, if application A
* grants persistable uri permission to application B; regardless of any manifest entries.
*/
- private final SparseSetArray<Integer> mRetainedImplicitlyQueryable = new SparseSetArray<>();
+ @GuardedBy("mLock")
+ @Watched
+ private final WatchedSparseSetArray<Integer> mRetainedImplicitlyQueryable;
+ private final SnapshotCache<WatchedSparseSetArray<Integer>>
+ mRetainedImplicitlyQueryableSnapshot;
/**
* A mapping from the set of App IDs that query other App IDs via package name to the
* list of packages that they can see.
*/
- private final SparseSetArray<Integer> mQueriesViaPackage = new SparseSetArray<>();
+ @GuardedBy("mLock")
+ @Watched
+ private final WatchedSparseSetArray<Integer> mQueriesViaPackage;
+ private final SnapshotCache<WatchedSparseSetArray<Integer>> mQueriesViaPackageSnapshot;
/**
* A mapping from the set of App IDs that query others via component match to the list
* of packages that the they resolve to.
*/
- private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>();
+ @GuardedBy("mLock")
+ @Watched
+ private final WatchedSparseSetArray<Integer> mQueriesViaComponent;
+ private final SnapshotCache<WatchedSparseSetArray<Integer>> mQueriesViaComponentSnapshot;
/**
* A mapping from the set of App IDs that query other App IDs via library name to the
* list of packages that they can see.
*/
- private final SparseSetArray<Integer> mQueryableViaUsesLibrary = new SparseSetArray<>();
+ @GuardedBy("mLock")
+ @Watched
+ private final WatchedSparseSetArray<Integer> mQueryableViaUsesLibrary;
+ private final SnapshotCache<WatchedSparseSetArray<Integer>> mQueryableViaUsesLibrarySnapshot;
/**
* Executor for running reasonably short background tasks such as building the initial
@@ -145,7 +163,10 @@ public class AppsFilter implements Watchable, Snappable {
* A set of App IDs that are always queryable by any package, regardless of their manifest
* content.
*/
- private final ArraySet<Integer> mForceQueryable = new ArraySet<>();
+ @Watched
+ @GuardedBy("mLock")
+ private final WatchedArraySet<Integer> mForceQueryable;
+ private final SnapshotCache<WatchedArraySet<Integer>> mForceQueryableSnapshot;
/**
* The set of package names provided by the device that should be force queryable regardless of
@@ -155,14 +176,16 @@ public class AppsFilter implements Watchable, Snappable {
/** True if all system apps should be made queryable by default. */
private final boolean mSystemAppsQueryable;
-
private final FeatureConfig mFeatureConfig;
private final OverlayReferenceMapper mOverlayReferenceMapper;
private final StateProvider mStateProvider;
private final PackageManagerInternal mPmInternal;
-
private SigningDetails mSystemSigningDetails;
- private Set<String> mProtectedBroadcasts = new ArraySet<>();
+
+ @GuardedBy("mLock")
+ @Watched
+ private final WatchedArrayList<String> mProtectedBroadcasts;
+ private final SnapshotCache<WatchedArrayList<String>> mProtectedBroadcastsSnapshot;
private final Object mCacheLock = new Object();
@@ -171,29 +194,47 @@ public class AppsFilter implements Watchable, Snappable {
* filtered to the second. It's essentially a cache of the
* {@link #shouldFilterApplicationInternal(int, Object, PackageStateInternal, int)} call.
* NOTE: It can only be relied upon after the system is ready to avoid unnecessary update on
- * initial scam and is null until {@link #onSystemReady()} is called.
+ * initial scam and is empty until {@link #onSystemReady()} is called.
*/
@GuardedBy("mCacheLock")
- private volatile WatchedSparseBooleanMatrix mShouldFilterCache;
+ @NonNull
+ private final WatchedSparseBooleanMatrix mShouldFilterCache;
+ private final SnapshotCache<WatchedSparseBooleanMatrix> mShouldFilterCacheSnapshot;
+
+ /**
+ * Guards the accesses for the list/set fields except for {@link #mShouldFilterCache}
+ */
+ private final Object mLock = new Object();
/**
* A cached snapshot.
*/
- private final SnapshotCache<AppsFilter> mSnapshot;
+ private final SnapshotCache<AppsFilterImpl> mSnapshot;
- private SnapshotCache<AppsFilter> makeCache() {
- return new SnapshotCache<AppsFilter>(this, this) {
+ private SnapshotCache<AppsFilterImpl> makeCache() {
+ return new SnapshotCache<AppsFilterImpl>(this, this) {
@Override
- public AppsFilter createSnapshot() {
- AppsFilter s = new AppsFilter(mSource);
+ public AppsFilterImpl createSnapshot() {
+ AppsFilterImpl s = new AppsFilterImpl(mSource);
+ s.mWatchable.seal();
return s;
- }};
+ }
+ };
}
/**
* Watchable machinery
*/
private final WatchableImpl mWatchable = new WatchableImpl();
+ /**
+ * The observer that watches for changes from array members
+ */
+ private final Watcher mObserver = new Watcher() {
+ @Override
+ public void onChange(@Nullable Watchable what) {
+ AppsFilterImpl.this.dispatchChange(what);
+ }
+ };
/**
* Ensures an observer is in the list, exactly once. The observer cannot be null. The
@@ -219,6 +260,7 @@ public class AppsFilter implements Watchable, Snappable {
/**
* Return true if the {@link Watcher) is a registered observer.
+ *
* @param observer A {@link Watcher} that might be registered
* @return true if the observer is registered with this {@link Watchable}.
*/
@@ -247,7 +289,7 @@ public class AppsFilter implements Watchable, Snappable {
}
@VisibleForTesting(visibility = PRIVATE)
- AppsFilter(StateProvider stateProvider,
+ AppsFilterImpl(StateProvider stateProvider,
FeatureConfig featureConfig,
String[] forceQueryableList,
boolean systemAppsQueryable,
@@ -262,34 +304,70 @@ public class AppsFilter implements Watchable, Snappable {
mStateProvider = stateProvider;
mPmInternal = pmInternal;
mBackgroundExecutor = backgroundExecutor;
+ mImplicitlyQueryable = new WatchedSparseSetArray<>();
+ mImplicitQueryableSnapshot = new SnapshotCache.Auto<>(
+ mImplicitlyQueryable, mImplicitlyQueryable, "AppsFilter.mImplicitlyQueryable");
+ mRetainedImplicitlyQueryable = new WatchedSparseSetArray<>();
+ mRetainedImplicitlyQueryableSnapshot = new SnapshotCache.Auto<>(
+ mRetainedImplicitlyQueryable, mRetainedImplicitlyQueryable,
+ "AppsFilter.mRetainedImplicitlyQueryable");
+ mQueriesViaPackage = new WatchedSparseSetArray<>();
+ mQueriesViaPackageSnapshot = new SnapshotCache.Auto<>(
+ mQueriesViaPackage, mQueriesViaPackage, "AppsFilter.mQueriesViaPackage");
+ mQueriesViaComponent = new WatchedSparseSetArray<>();
+ mQueriesViaComponentSnapshot = new SnapshotCache.Auto<>(
+ mQueriesViaComponent, mQueriesViaComponent, "AppsFilter.mQueriesViaComponent");
+ mQueryableViaUsesLibrary = new WatchedSparseSetArray<>();
+ mQueryableViaUsesLibrarySnapshot = new SnapshotCache.Auto<>(
+ mQueryableViaUsesLibrary, mQueryableViaUsesLibrary,
+ "AppsFilter.mQueryableViaUsesLibrary");
+ mForceQueryable = new WatchedArraySet<>();
+ mForceQueryableSnapshot = new SnapshotCache.Auto<>(
+ mForceQueryable, mForceQueryable, "AppsFilter.mForceQueryable");
+ mProtectedBroadcasts = new WatchedArrayList<>();
+ mProtectedBroadcastsSnapshot = new SnapshotCache.Auto<>(
+ mProtectedBroadcasts, mProtectedBroadcasts, "AppsFilter.mProtectedBroadcasts");
+ mShouldFilterCache = new WatchedSparseBooleanMatrix();
+ mShouldFilterCacheSnapshot = new SnapshotCache.Auto<>(
+ mShouldFilterCache, mShouldFilterCache, "AppsFilter.mShouldFilterCache");
+
+ registerObservers();
+ Watchable.verifyWatchedAttributes(this, mObserver);
mSnapshot = makeCache();
}
/**
* The copy constructor is used by PackageManagerService to construct a snapshot.
- * Attributes are not deep-copied since these are supposed to be immutable.
- * TODO: deep-copy the attributes, if necessary.
*/
- private AppsFilter(AppsFilter orig) {
- Snapshots.copy(mImplicitlyQueryable, orig.mImplicitlyQueryable);
- Snapshots.copy(mRetainedImplicitlyQueryable, orig.mRetainedImplicitlyQueryable);
- Snapshots.copy(mQueriesViaPackage, orig.mQueriesViaPackage);
- Snapshots.copy(mQueriesViaComponent, orig.mQueriesViaComponent);
- Snapshots.copy(mQueryableViaUsesLibrary, orig.mQueryableViaUsesLibrary);
+ private AppsFilterImpl(AppsFilterImpl orig) {
+ synchronized (orig.mLock) {
+ mImplicitlyQueryable = orig.mImplicitQueryableSnapshot.snapshot();
+ mImplicitQueryableSnapshot = new SnapshotCache.Sealed<>();
+ mRetainedImplicitlyQueryable = orig.mRetainedImplicitlyQueryableSnapshot.snapshot();
+ mRetainedImplicitlyQueryableSnapshot = new SnapshotCache.Sealed<>();
+ mQueriesViaPackage = orig.mQueriesViaPackageSnapshot.snapshot();
+ mQueriesViaPackageSnapshot = new SnapshotCache.Sealed<>();
+ mQueriesViaComponent = orig.mQueriesViaComponentSnapshot.snapshot();
+ mQueriesViaComponentSnapshot = new SnapshotCache.Sealed<>();
+ mQueryableViaUsesLibrary = orig.mQueryableViaUsesLibrarySnapshot.snapshot();
+ mQueryableViaUsesLibrarySnapshot = new SnapshotCache.Sealed<>();
+ mForceQueryable = orig.mForceQueryableSnapshot.snapshot();
+ mForceQueryableSnapshot = new SnapshotCache.Sealed<>();
+ mProtectedBroadcasts = orig.mProtectedBroadcastsSnapshot.snapshot();
+ mProtectedBroadcastsSnapshot = new SnapshotCache.Sealed<>();
+ }
mQueriesViaComponentRequireRecompute = orig.mQueriesViaComponentRequireRecompute;
- mForceQueryable.addAll(orig.mForceQueryable);
- mForceQueryableByDevicePackageNames = orig.mForceQueryableByDevicePackageNames;
+ mForceQueryableByDevicePackageNames =
+ Arrays.copyOf(orig.mForceQueryableByDevicePackageNames,
+ orig.mForceQueryableByDevicePackageNames.length);
mSystemAppsQueryable = orig.mSystemAppsQueryable;
mFeatureConfig = orig.mFeatureConfig;
mOverlayReferenceMapper = orig.mOverlayReferenceMapper;
mStateProvider = orig.mStateProvider;
mSystemSigningDetails = orig.mSystemSigningDetails;
- mProtectedBroadcasts = orig.mProtectedBroadcasts;
- mShouldFilterCache = orig.mShouldFilterCache;
- if (mShouldFilterCache != null) {
- synchronized (orig.mCacheLock) {
- mShouldFilterCache = mShouldFilterCache.snapshot();
- }
+ synchronized (orig.mCacheLock) {
+ mShouldFilterCache = orig.mShouldFilterCacheSnapshot.snapshot();
+ mShouldFilterCacheSnapshot = new SnapshotCache.Sealed<>();
}
mBackgroundExecutor = null;
@@ -297,12 +375,24 @@ public class AppsFilter implements Watchable, Snappable {
mSnapshot = new SnapshotCache.Sealed<>();
}
+ @SuppressWarnings("GuardedBy")
+ private void registerObservers() {
+ mImplicitlyQueryable.registerObserver(mObserver);
+ mRetainedImplicitlyQueryable.registerObserver(mObserver);
+ mQueriesViaPackage.registerObserver(mObserver);
+ mQueriesViaComponent.registerObserver(mObserver);
+ mQueryableViaUsesLibrary.registerObserver(mObserver);
+ mForceQueryable.registerObserver(mObserver);
+ mProtectedBroadcasts.registerObserver(mObserver);
+ mShouldFilterCache.registerObserver(mObserver);
+ }
+
/**
* Return a snapshot. If the cached snapshot is null, build a new one. The logic in
* the function ensures that this function returns a valid snapshot even if a race
* condition causes the cached snapshot to be cleared asynchronously to this method.
*/
- public AppsFilter snapshot() {
+ public AppsFilterSnapshot snapshot() {
return mSnapshot.snapshot();
}
@@ -363,7 +453,7 @@ public class AppsFilter implements Watchable, Snappable {
@Nullable
private SparseBooleanArray mLoggingEnabled = null;
- private AppsFilter mAppsFilter;
+ private AppsFilterImpl mAppsFilter;
private FeatureConfigImpl(
PackageManagerInternal pmInternal, PackageManagerServiceInjector injector) {
@@ -371,7 +461,7 @@ public class AppsFilter implements Watchable, Snappable {
mInjector = injector;
}
- public void setAppsFilter(AppsFilter filter) {
+ public void setAppsFilter(AppsFilterImpl filter) {
mAppsFilter = filter;
}
@@ -475,8 +565,9 @@ public class AppsFilter implements Watchable, Snappable {
@Override
public void updatePackageState(PackageStateInternal setting, boolean removed) {
- final boolean enableLogging = setting.getPkg() != null &&
- !removed && (setting.getPkg().isTestOnly() || setting.getPkg().isDebuggable());
+ final boolean enableLogging = setting.getPkg() != null
+ && !removed && (setting.getPkg().isTestOnly()
+ || setting.getPkg().isDebuggable());
enableLogging(setting.getAppId(), enableLogging);
if (removed) {
mDisabledPackages.remove(setting.getPackageName());
@@ -487,7 +578,7 @@ public class AppsFilter implements Watchable, Snappable {
}
/** Builder method for an AppsFilter */
- public static AppsFilter create(@NonNull PackageManagerServiceInjector injector,
+ public static AppsFilterImpl create(@NonNull PackageManagerServiceInjector injector,
@NonNull PackageManagerInternal pmInt) {
final boolean forceSystemAppsQueryable =
injector.getContext().getResources()
@@ -511,7 +602,7 @@ public class AppsFilter implements Watchable, Snappable {
injector.getUserManagerInternal().getUserInfos());
}
};
- AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig,
+ AppsFilterImpl appsFilter = new AppsFilterImpl(stateProvider, featureConfig,
forcedQueryablePackageNames, forceSystemAppsQueryable, null,
injector.getBackgroundExecutor(), pmInt);
featureConfig.setAppsFilter(appsFilter);
@@ -524,7 +615,7 @@ public class AppsFilter implements Watchable, Snappable {
/** Returns true if the querying package may query for the potential target package */
private static boolean canQueryViaComponents(AndroidPackage querying,
- AndroidPackage potentialTarget, Set<String> protectedBroadcasts) {
+ AndroidPackage potentialTarget, WatchedArrayList<String> protectedBroadcasts) {
if (!querying.getQueriesIntents().isEmpty()) {
for (Intent intent : querying.getQueriesIntents()) {
if (matchesPackage(intent, potentialTarget, protectedBroadcasts)) {
@@ -596,7 +687,7 @@ public class AppsFilter implements Watchable, Snappable {
}
private static boolean matchesPackage(Intent intent, AndroidPackage potentialTarget,
- Set<String> protectedBroadcasts) {
+ WatchedArrayList<String> protectedBroadcasts) {
if (matchesAnyComponents(
intent, potentialTarget.getServices(), null /*protectedBroadcasts*/)) {
return true;
@@ -617,7 +708,7 @@ public class AppsFilter implements Watchable, Snappable {
private static boolean matchesAnyComponents(Intent intent,
List<? extends ParsedMainComponent> components,
- Set<String> protectedBroadcasts) {
+ WatchedArrayList<String> protectedBroadcasts) {
for (int i = ArrayUtils.size(components) - 1; i >= 0; i--) {
ParsedMainComponent component = components.get(i);
if (!component.isExported()) {
@@ -631,7 +722,7 @@ public class AppsFilter implements Watchable, Snappable {
}
private static boolean matchesAnyFilter(Intent intent, ParsedComponent component,
- Set<String> protectedBroadcasts) {
+ WatchedArrayList<String> protectedBroadcasts) {
List<ParsedIntentInfo> intents = component.getIntents();
for (int i = ArrayUtils.size(intents) - 1; i >= 0; i--) {
IntentFilter intentFilter = intents.get(i).getIntentFilter();
@@ -643,38 +734,38 @@ public class AppsFilter implements Watchable, Snappable {
}
private static boolean matchesIntentFilter(Intent intent, IntentFilter intentFilter,
- @Nullable Set<String> protectedBroadcasts) {
+ @Nullable WatchedArrayList<String> protectedBroadcasts) {
return intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
- intent.getData(), intent.getCategories(), "AppsFilter", true, protectedBroadcasts)
- > 0;
+ intent.getData(), intent.getCategories(), "AppsFilter", true,
+ protectedBroadcasts != null ? protectedBroadcasts.untrackedStorage() : null) > 0;
}
/**
* Grants access based on an interaction between a calling and target package, granting
* visibility of the caller from the target.
*
- * @param recipientUid the uid gaining visibility of the {@code visibleUid}.
- * @param visibleUid the uid becoming visible to the {@recipientUid}
- * @param retainOnUpdate if the implicit access retained across package updates.
+ * @param recipientUid the uid gaining visibility of the {@code visibleUid}.
+ * @param visibleUid the uid becoming visible to the {@recipientUid}
+ * @param retainOnUpdate if the implicit access retained across package updates.
* @return {@code true} if implicit access was not already granted.
*/
public boolean grantImplicitAccess(int recipientUid, int visibleUid, boolean retainOnUpdate) {
if (recipientUid == visibleUid) {
return false;
}
- final boolean changed = retainOnUpdate
- ? mRetainedImplicitlyQueryable.add(recipientUid, visibleUid)
- : mImplicitlyQueryable.add(recipientUid, visibleUid);
- if (changed && DEBUG_LOGGING) {
- Slog.i(TAG, (retainOnUpdate ? "retained " : "") + "implicit access granted: "
- + recipientUid + " -> " + visibleUid);
+ final boolean changed;
+ synchronized (mLock) {
+ changed = retainOnUpdate
+ ? mRetainedImplicitlyQueryable.add(recipientUid, visibleUid)
+ : mImplicitlyQueryable.add(recipientUid, visibleUid);
+ if (changed && DEBUG_LOGGING) {
+ Slog.i(TAG, (retainOnUpdate ? "retained " : "") + "implicit access granted: "
+ + recipientUid + " -> " + visibleUid);
+ }
}
synchronized (mCacheLock) {
- if (mShouldFilterCache != null) {
- // update the cache in a one-off manner since we've got all the information we
- // need.
- mShouldFilterCache.put(recipientUid, visibleUid, false);
- }
+ // update the cache in a one-off manner since we've got all the information we need.
+ mShouldFilterCache.put(recipientUid, visibleUid, false);
}
if (changed) {
onChanged();
@@ -694,7 +785,7 @@ public class AppsFilter implements Watchable, Snappable {
* Adds a package that should be considered when filtering visibility between apps.
*
* @param newPkgSetting the new setting being added
- * @param isReplace if the package is being replaced and may need extra cleanup.
+ * @param isReplace if the package is being replaced and may need extra cleanup.
*/
public void addPackage(PackageStateInternal newPkgSetting, boolean isReplace) {
if (DEBUG_TRACING) {
@@ -709,27 +800,25 @@ public class AppsFilter implements Watchable, Snappable {
ArraySet<String> additionalChangedPackages =
addPackageInternal(newPkgSetting, settings);
synchronized (mCacheLock) {
- if (mShouldFilterCache != null) {
- updateShouldFilterCacheForPackage(mShouldFilterCache, null, newPkgSetting,
- settings, users, USER_ALL, settings.size());
- if (additionalChangedPackages != null) {
- for (int index = 0; index < additionalChangedPackages.size(); index++) {
- String changedPackage = additionalChangedPackages.valueAt(index);
- PackageStateInternal changedPkgSetting =
- settings.get(changedPackage);
- if (changedPkgSetting == null) {
- // It's possible for the overlay mapper to know that an actor
- // package changed via an explicit reference, even if the actor
- // isn't installed, so skip if that's the case.
- continue;
- }
-
- updateShouldFilterCacheForPackage(mShouldFilterCache, null,
- changedPkgSetting, settings, users, USER_ALL,
- settings.size());
+ updateShouldFilterCacheForPackage(mShouldFilterCache, null, newPkgSetting,
+ settings, users, USER_ALL, settings.size());
+ if (additionalChangedPackages != null) {
+ for (int index = 0; index < additionalChangedPackages.size(); index++) {
+ String changedPackage = additionalChangedPackages.valueAt(index);
+ PackageStateInternal changedPkgSetting =
+ settings.get(changedPackage);
+ if (changedPkgSetting == null) {
+ // It's possible for the overlay mapper to know that an actor
+ // package changed via an explicit reference, even if the actor
+ // isn't installed, so skip if that's the case.
+ continue;
}
+
+ updateShouldFilterCacheForPackage(mShouldFilterCache, null,
+ changedPkgSetting, settings, users, USER_ALL,
+ settings.size());
}
- } // else, rebuild entire cache when system is ready
+ }
}
});
} finally {
@@ -752,9 +841,11 @@ public class AppsFilter implements Watchable, Snappable {
mSystemSigningDetails = newPkgSetting.getSigningDetails();
// and since we add overlays before we add the framework, let's revisit already added
// packages for signature matches
- for (PackageStateInternal setting : existingSettings.values()) {
- if (isSystemSigned(mSystemSigningDetails, setting)) {
- mForceQueryable.add(setting.getAppId());
+ synchronized (mLock) {
+ for (PackageStateInternal setting : existingSettings.values()) {
+ if (isSystemSigned(mSystemSigningDetails, setting)) {
+ mForceQueryable.add(setting.getAppId());
+ }
}
}
}
@@ -764,67 +855,74 @@ public class AppsFilter implements Watchable, Snappable {
return null;
}
- if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) {
- mQueriesViaComponentRequireRecompute = true;
- }
-
- final boolean newIsForceQueryable =
- mForceQueryable.contains(newPkgSetting.getAppId())
- /* shared user that is already force queryable */
- || newPkgSetting.isForceQueryableOverride() /* adb override */
- || (newPkgSetting.isSystem() && (mSystemAppsQueryable
- || newPkg.isForceQueryable()
- || ArrayUtils.contains(mForceQueryableByDevicePackageNames,
- newPkg.getPackageName())));
- if (newIsForceQueryable
- || (mSystemSigningDetails != null
- && isSystemSigned(mSystemSigningDetails, newPkgSetting))) {
- mForceQueryable.add(newPkgSetting.getAppId());
- }
+ synchronized (mLock) {
+ if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) {
+ mQueriesViaComponentRequireRecompute = true;
+ }
- for (int i = existingSettings.size() - 1; i >= 0; i--) {
- final PackageStateInternal existingSetting = existingSettings.valueAt(i);
- if (existingSetting.getAppId() == newPkgSetting.getAppId() || existingSetting.getPkg()
- == null) {
- continue;
+ final boolean newIsForceQueryable =
+ mForceQueryable.contains(newPkgSetting.getAppId())
+ /* shared user that is already force queryable */
+ || newPkgSetting.isForceQueryableOverride() /* adb override */
+ || (newPkgSetting.isSystem() && (mSystemAppsQueryable
+ || newPkg.isForceQueryable()
+ || ArrayUtils.contains(mForceQueryableByDevicePackageNames,
+ newPkg.getPackageName())));
+ if (newIsForceQueryable
+ || (mSystemSigningDetails != null
+ && isSystemSigned(mSystemSigningDetails, newPkgSetting))) {
+ mForceQueryable.add(newPkgSetting.getAppId());
}
- final AndroidPackage existingPkg = existingSetting.getPkg();
- // let's evaluate the ability of already added packages to see this new package
- if (!newIsForceQueryable) {
- if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(existingPkg,
- newPkg, mProtectedBroadcasts)) {
- mQueriesViaComponent.add(existingSetting.getAppId(), newPkgSetting.getAppId());
- }
- if (canQueryViaPackage(existingPkg, newPkg)
- || canQueryAsInstaller(existingSetting, newPkg)) {
- mQueriesViaPackage.add(existingSetting.getAppId(), newPkgSetting.getAppId());
+
+ for (int i = existingSettings.size() - 1; i >= 0; i--) {
+ final PackageStateInternal existingSetting = existingSettings.valueAt(i);
+ if (existingSetting.getAppId() == newPkgSetting.getAppId()
+ || existingSetting.getPkg()
+ == null) {
+ continue;
}
- if (canQueryViaUsesLibrary(existingPkg, newPkg)) {
- mQueryableViaUsesLibrary.add(existingSetting.getAppId(),
- newPkgSetting.getAppId());
+ final AndroidPackage existingPkg = existingSetting.getPkg();
+ // let's evaluate the ability of already added packages to see this new package
+ if (!newIsForceQueryable) {
+ if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(existingPkg,
+ newPkg, mProtectedBroadcasts)) {
+ mQueriesViaComponent.add(existingSetting.getAppId(),
+ newPkgSetting.getAppId());
+ }
+ if (canQueryViaPackage(existingPkg, newPkg)
+ || canQueryAsInstaller(existingSetting, newPkg)) {
+ mQueriesViaPackage.add(existingSetting.getAppId(),
+ newPkgSetting.getAppId());
+ }
+ if (canQueryViaUsesLibrary(existingPkg, newPkg)) {
+ mQueryableViaUsesLibrary.add(existingSetting.getAppId(),
+ newPkgSetting.getAppId());
+ }
}
- }
- // now we'll evaluate our new package's ability to see existing packages
- if (!mForceQueryable.contains(existingSetting.getAppId())) {
- if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(newPkg,
- existingPkg, mProtectedBroadcasts)) {
- mQueriesViaComponent.add(newPkgSetting.getAppId(), existingSetting.getAppId());
+ // now we'll evaluate our new package's ability to see existing packages
+ if (!mForceQueryable.contains(existingSetting.getAppId())) {
+ if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(newPkg,
+ existingPkg, mProtectedBroadcasts)) {
+ mQueriesViaComponent.add(newPkgSetting.getAppId(),
+ existingSetting.getAppId());
+ }
+ if (canQueryViaPackage(newPkg, existingPkg)
+ || canQueryAsInstaller(newPkgSetting, existingPkg)) {
+ mQueriesViaPackage.add(newPkgSetting.getAppId(),
+ existingSetting.getAppId());
+ }
+ if (canQueryViaUsesLibrary(newPkg, existingPkg)) {
+ mQueryableViaUsesLibrary.add(newPkgSetting.getAppId(),
+ existingSetting.getAppId());
+ }
}
- if (canQueryViaPackage(newPkg, existingPkg)
- || canQueryAsInstaller(newPkgSetting, existingPkg)) {
+ // if either package instruments the other, mark both as visible to one another
+ if (newPkgSetting.getPkg() != null && existingSetting.getPkg() != null
+ && (pkgInstruments(newPkgSetting.getPkg(), existingSetting.getPkg())
+ || pkgInstruments(existingSetting.getPkg(), newPkgSetting.getPkg()))) {
mQueriesViaPackage.add(newPkgSetting.getAppId(), existingSetting.getAppId());
+ mQueriesViaPackage.add(existingSetting.getAppId(), newPkgSetting.getAppId());
}
- if (canQueryViaUsesLibrary(newPkg, existingPkg)) {
- mQueryableViaUsesLibrary.add(newPkgSetting.getAppId(),
- existingSetting.getAppId());
- }
- }
- // if either package instruments the other, mark both as visible to one another
- if (newPkgSetting.getPkg() != null && existingSetting.getPkg() != null
- && (pkgInstruments(newPkgSetting.getPkg(), existingSetting.getPkg())
- || pkgInstruments(existingSetting.getPkg(), newPkgSetting.getPkg()))) {
- mQueriesViaPackage.add(newPkgSetting.getAppId(), existingSetting.getAppId());
- mQueriesViaPackage.add(existingSetting.getAppId(), newPkgSetting.getAppId());
}
}
@@ -845,19 +943,17 @@ public class AppsFilter implements Watchable, Snappable {
return changedPackages;
}
- @GuardedBy("mCacheLock")
private void removeAppIdFromVisibilityCache(int appId) {
- if (mShouldFilterCache == null) {
- return;
- }
- for (int i = 0; i < mShouldFilterCache.size(); i++) {
- if (UserHandle.getAppId(mShouldFilterCache.keyAt(i)) == appId) {
- mShouldFilterCache.removeAt(i);
- // The key was deleted so the list of keys has shifted left. That means i
- // is now pointing at the next key to be examined. The decrement here and
- // the loop increment together mean that i will be unchanged in the need
- // iteration and will correctly point to the next key to be examined.
- i--;
+ synchronized (mCacheLock) {
+ for (int i = 0; i < mShouldFilterCache.size(); i++) {
+ if (UserHandle.getAppId(mShouldFilterCache.keyAt(i)) == appId) {
+ mShouldFilterCache.removeAt(i);
+ // The key was deleted so the list of keys has shifted left. That means i
+ // is now pointing at the next key to be examined. The decrement here and
+ // the loop increment together mean that i will be unchanged in the need
+ // iteration and will correctly point to the next key to be examined.
+ i--;
+ }
}
}
}
@@ -883,11 +979,12 @@ public class AppsFilter implements Watchable, Snappable {
WatchedSparseBooleanMatrix cache =
updateEntireShouldFilterCacheInner(settings, users, userId);
synchronized (mCacheLock) {
- mShouldFilterCache = cache;
+ mShouldFilterCache.copyFrom(cache);
}
});
}
+ @NonNull
private WatchedSparseBooleanMatrix updateEntireShouldFilterCacheInner(
ArrayMap<String, ? extends PackageStateInternal> settings, UserInfo[] users,
int subjectUserId) {
@@ -948,36 +1045,25 @@ public class AppsFilter implements Watchable, Snappable {
}
} else {
synchronized (mCacheLock) {
- mShouldFilterCache = cache;
+ mShouldFilterCache.copyFrom(cache);
}
}
});
}
public void onUserCreated(int newUserId) {
- synchronized (mCacheLock) {
- if (mShouldFilterCache != null) {
- updateEntireShouldFilterCache(newUserId);
- onChanged();
- }
- }
+ updateEntireShouldFilterCache(newUserId);
+ onChanged();
}
public void onUserDeleted(@UserIdInt int userId) {
- synchronized (mCacheLock) {
- if (mShouldFilterCache != null) {
- removeShouldFilterCacheForUser(userId);
- onChanged();
- }
- }
+ removeShouldFilterCacheForUser(userId);
+ onChanged();
}
private void updateShouldFilterCacheForPackage(String packageName) {
mStateProvider.runWithState((settings, users) -> {
synchronized (mCacheLock) {
- if (mShouldFilterCache == null) {
- return;
- }
updateShouldFilterCacheForPackage(mShouldFilterCache, null /* skipPackage */,
settings.get(packageName), settings, users, USER_ALL,
settings.size() /*maxIndex*/);
@@ -985,7 +1071,7 @@ public class AppsFilter implements Watchable, Snappable {
});
}
- private void updateShouldFilterCacheForPackage(WatchedSparseBooleanMatrix cache,
+ private void updateShouldFilterCacheForPackage(@NonNull WatchedSparseBooleanMatrix cache,
@Nullable String skipPackageName, PackageStateInternal subjectSetting, ArrayMap<String,
? extends PackageStateInternal> allSettings, UserInfo[] allUsers, int subjectUserId,
int maxIndex) {
@@ -1011,7 +1097,7 @@ public class AppsFilter implements Watchable, Snappable {
}
}
- private void updateShouldFilterCacheForUser(WatchedSparseBooleanMatrix cache,
+ private void updateShouldFilterCacheForUser(@NonNull WatchedSparseBooleanMatrix cache,
PackageStateInternal subjectSetting, UserInfo[] allUsers,
PackageStateInternal otherSetting, int subjectUserId) {
for (int ou = 0; ou < allUsers.length; ou++) {
@@ -1027,27 +1113,28 @@ public class AppsFilter implements Watchable, Snappable {
}
}
- @GuardedBy("mCacheLock")
private void removeShouldFilterCacheForUser(int userId) {
- // Sorted uids with the ascending order
- final int[] cacheUids = mShouldFilterCache.keys();
- final int size = cacheUids.length;
- int pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId, 0));
- final int fromIndex = (pos >= 0 ? pos : ~pos);
- if (fromIndex >= size || UserHandle.getUserId(cacheUids[fromIndex]) != userId) {
- Slog.w(TAG, "Failed to remove should filter cache for user " + userId
- + ", fromIndex=" + fromIndex);
- return;
- }
- pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId + 1, 0) - 1);
- final int toIndex = (pos >= 0 ? pos + 1 : ~pos);
- if (fromIndex >= toIndex || UserHandle.getUserId(cacheUids[toIndex - 1]) != userId) {
- Slog.w(TAG, "Failed to remove should filter cache for user " + userId
- + ", fromIndex=" + fromIndex + ", toIndex=" + toIndex);
- return;
+ synchronized (mCacheLock) {
+ // Sorted uids with the ascending order
+ final int[] cacheUids = mShouldFilterCache.keys();
+ final int size = cacheUids.length;
+ int pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId, 0));
+ final int fromIndex = (pos >= 0 ? pos : ~pos);
+ if (fromIndex >= size || UserHandle.getUserId(cacheUids[fromIndex]) != userId) {
+ Slog.w(TAG, "Failed to remove should filter cache for user " + userId
+ + ", fromIndex=" + fromIndex);
+ return;
+ }
+ pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId + 1, 0) - 1);
+ final int toIndex = (pos >= 0 ? pos + 1 : ~pos);
+ if (fromIndex >= toIndex || UserHandle.getUserId(cacheUids[toIndex - 1]) != userId) {
+ Slog.w(TAG, "Failed to remove should filter cache for user " + userId
+ + ", fromIndex=" + fromIndex + ", toIndex=" + toIndex);
+ return;
+ }
+ mShouldFilterCache.removeRange(fromIndex, toIndex);
+ mShouldFilterCache.compact();
}
- mShouldFilterCache.removeRange(fromIndex, toIndex);
- mShouldFilterCache.compact();
}
private static boolean isSystemSigned(@NonNull SigningDetails sysSigningDetails,
@@ -1056,28 +1143,30 @@ public class AppsFilter implements Watchable, Snappable {
&& pkgSetting.getSigningDetails().signaturesMatchExactly(sysSigningDetails);
}
- private ArraySet<String> collectProtectedBroadcasts(
+ private void collectProtectedBroadcasts(
ArrayMap<String, ? extends PackageStateInternal> existingSettings,
@Nullable String excludePackage) {
- ArraySet<String> ret = new ArraySet<>();
- for (int i = existingSettings.size() - 1; i >= 0; i--) {
- PackageStateInternal setting = existingSettings.valueAt(i);
- if (setting.getPkg() == null || setting.getPkg().getPackageName().equals(
- excludePackage)) {
- continue;
- }
- final List<String> protectedBroadcasts = setting.getPkg().getProtectedBroadcasts();
- if (!protectedBroadcasts.isEmpty()) {
- ret.addAll(protectedBroadcasts);
+ synchronized (mLock) {
+ mProtectedBroadcasts.clear();
+ for (int i = existingSettings.size() - 1; i >= 0; i--) {
+ PackageStateInternal setting = existingSettings.valueAt(i);
+ if (setting.getPkg() == null || setting.getPkg().getPackageName().equals(
+ excludePackage)) {
+ continue;
+ }
+ final List<String> protectedBroadcasts = setting.getPkg().getProtectedBroadcasts();
+ if (!protectedBroadcasts.isEmpty()) {
+ mProtectedBroadcasts.addAll(protectedBroadcasts);
+ }
}
}
- return ret;
}
/**
* This method recomputes all component / intent-based visibility and is intended to match the
* relevant logic of {@link #addPackageInternal(PackageStateInternal, ArrayMap)}
*/
+ @GuardedBy("mLock")
private void recomputeComponentVisibility(
ArrayMap<String, ? extends PackageStateInternal> existingSettings) {
mQueriesViaComponent.clear();
@@ -1105,24 +1194,16 @@ public class AppsFilter implements Watchable, Snappable {
}
/**
- * Fetches all app Ids that a given setting is currently visible to, per provided user. This
- * only includes UIDs >= {@link Process#FIRST_APPLICATION_UID} as all other UIDs can already see
- * all applications.
- *
- * If the setting is visible to all UIDs, null is returned. If an app is not visible to any
- * applications, the int array will be empty.
- *
- * @param users the set of users that should be evaluated for this calculation
- * @param existingSettings the set of all package settings that currently exist on device
- * @return a SparseArray mapping userIds to a sorted int array of appIds that may view the
- * provided setting or null if the app is visible to all and no allow list should be
- * applied.
+ * See {@link AppsFilterSnapshot#getVisibilityAllowList(PackageStateInternal, int[], ArrayMap)}
*/
+ @Override
@Nullable
public SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
ArrayMap<String, ? extends PackageStateInternal> existingSettings) {
- if (mForceQueryable.contains(setting.getAppId())) {
- return null;
+ synchronized (mLock) {
+ if (mForceQueryable.contains(setting.getAppId())) {
+ return null;
+ }
}
// let's reserve max memory to limit the number of allocations
SparseArray<int[]> result = new SparseArray<>(users.length);
@@ -1171,7 +1252,8 @@ public class AppsFilter implements Watchable, Snappable {
/**
* Equivalent to calling {@link #addPackage(PackageStateInternal, boolean)} with
* {@code isReplace} equal to {@code false}.
- * @see AppsFilter#addPackage(PackageStateInternal, boolean)
+ *
+ * @see AppsFilterImpl#addPackage(PackageStateInternal, boolean)
*/
public void addPackage(PackageStateInternal newPkgSetting) {
addPackage(newPkgSetting, false /* isReplace */);
@@ -1180,62 +1262,68 @@ public class AppsFilter implements Watchable, Snappable {
/**
* Removes a package for consideration when filtering visibility between apps.
*
- * @param setting the setting of the package being removed.
+ * @param setting the setting of the package being removed.
* @param isReplace if the package is being replaced.
*/
public void removePackage(PackageStateInternal setting, boolean isReplace) {
mStateProvider.runWithState((settings, users) -> {
- final int userCount = users.length;
- for (int u = 0; u < userCount; u++) {
- final int userId = users[u].id;
- final int removingUid = UserHandle.getUid(userId, setting.getAppId());
- mImplicitlyQueryable.remove(removingUid);
- for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
- mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), removingUid);
- }
+ final ArraySet<String> additionalChangedPackages;
+ synchronized (mLock) {
+ final int userCount = users.length;
+ for (int u = 0; u < userCount; u++) {
+ final int userId = users[u].id;
+ final int removingUid = UserHandle.getUid(userId, setting.getAppId());
+ mImplicitlyQueryable.remove(removingUid);
+ for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
+ mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), removingUid);
+ }
- if (isReplace) {
- continue;
- }
+ if (isReplace) {
+ continue;
+ }
- mRetainedImplicitlyQueryable.remove(removingUid);
- for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
- mRetainedImplicitlyQueryable.remove(
- mRetainedImplicitlyQueryable.keyAt(i), removingUid);
+ mRetainedImplicitlyQueryable.remove(removingUid);
+ for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
+ mRetainedImplicitlyQueryable.remove(
+ mRetainedImplicitlyQueryable.keyAt(i), removingUid);
+ }
}
- }
- if (!mQueriesViaComponentRequireRecompute) {
- mQueriesViaComponent.remove(setting.getAppId());
- for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) {
- mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.getAppId());
+ if (!mQueriesViaComponentRequireRecompute) {
+ mQueriesViaComponent.remove(setting.getAppId());
+ for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) {
+ mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i),
+ setting.getAppId());
+ }
+ }
+ mQueriesViaPackage.remove(setting.getAppId());
+ for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
+ mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.getAppId());
+ }
+ mQueryableViaUsesLibrary.remove(setting.getAppId());
+ for (int i = mQueryableViaUsesLibrary.size() - 1; i >= 0; i--) {
+ mQueryableViaUsesLibrary.remove(mQueryableViaUsesLibrary.keyAt(i),
+ setting.getAppId());
}
- }
- mQueriesViaPackage.remove(setting.getAppId());
- for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
- mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.getAppId());
- }
- mQueryableViaUsesLibrary.remove(setting.getAppId());
- for (int i = mQueryableViaUsesLibrary.size() - 1; i >= 0; i--) {
- mQueryableViaUsesLibrary.remove(mQueryableViaUsesLibrary.keyAt(i),
- setting.getAppId());
- }
- mForceQueryable.remove(setting.getAppId());
+ mForceQueryable.remove(setting.getAppId());
- if (setting.getPkg() != null && !setting.getPkg().getProtectedBroadcasts().isEmpty()) {
- final String removingPackageName = setting.getPkg().getPackageName();
- final Set<String> protectedBroadcasts = mProtectedBroadcasts;
- mProtectedBroadcasts = collectProtectedBroadcasts(settings, removingPackageName);
- if (!mProtectedBroadcasts.containsAll(protectedBroadcasts)) {
- mQueriesViaComponentRequireRecompute = true;
+ if (setting.getPkg() != null
+ && !setting.getPkg().getProtectedBroadcasts().isEmpty()) {
+ final String removingPackageName = setting.getPkg().getPackageName();
+ final ArrayList<String> protectedBroadcasts = new ArrayList<>();
+ protectedBroadcasts.addAll(mProtectedBroadcasts.untrackedStorage());
+ collectProtectedBroadcasts(settings, removingPackageName);
+ if (!mProtectedBroadcasts.containsAll(protectedBroadcasts)) {
+ mQueriesViaComponentRequireRecompute = true;
+ }
}
- }
- ArraySet<String> additionalChangedPackages =
- mOverlayReferenceMapper.removePkg(setting.getPackageName());
+ additionalChangedPackages =
+ mOverlayReferenceMapper.removePkg(setting.getPackageName());
- mFeatureConfig.updatePackageState(setting, true /*removed*/);
+ mFeatureConfig.updatePackageState(setting, true /*removed*/);
+ }
// After removing all traces of the package, if it's part of a shared user, re-add other
// shared user members to re-establish visibility between them and other packages.
@@ -1253,56 +1341,49 @@ public class AppsFilter implements Watchable, Snappable {
}
}
- synchronized (mCacheLock) {
- removeAppIdFromVisibilityCache(setting.getAppId());
- if (mShouldFilterCache != null && setting.hasSharedUser()) {
- final ArraySet<PackageStateInternal> sharedUserPackages =
- mPmInternal.getSharedUserPackages(setting.getSharedUserAppId());
- for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
- PackageStateInternal siblingSetting =
- sharedUserPackages.valueAt(i);
- if (siblingSetting == setting) {
- continue;
- }
+ removeAppIdFromVisibilityCache(setting.getAppId());
+ if (setting.hasSharedUser()) {
+ final ArraySet<PackageStateInternal> sharedUserPackages =
+ mPmInternal.getSharedUserPackages(setting.getSharedUserAppId());
+ for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
+ PackageStateInternal siblingSetting =
+ sharedUserPackages.valueAt(i);
+ if (siblingSetting == setting) {
+ continue;
+ }
+ synchronized (mCacheLock) {
updateShouldFilterCacheForPackage(mShouldFilterCache,
setting.getPackageName(), siblingSetting, settings, users,
USER_ALL, settings.size());
}
}
+ }
- if (mShouldFilterCache != null) {
- if (additionalChangedPackages != null) {
- for (int index = 0; index < additionalChangedPackages.size(); index++) {
- String changedPackage = additionalChangedPackages.valueAt(index);
- PackageStateInternal changedPkgSetting = settings.get(changedPackage);
- if (changedPkgSetting == null) {
- // It's possible for the overlay mapper to know that an actor
- // package changed via an explicit reference, even if the actor
- // isn't installed, so skip if that's the case.
- continue;
- }
-
- updateShouldFilterCacheForPackage(mShouldFilterCache, null,
- changedPkgSetting, settings, users, USER_ALL, settings.size());
- }
+ if (additionalChangedPackages != null) {
+ for (int index = 0; index < additionalChangedPackages.size(); index++) {
+ String changedPackage = additionalChangedPackages.valueAt(index);
+ PackageStateInternal changedPkgSetting = settings.get(changedPackage);
+ if (changedPkgSetting == null) {
+ // It's possible for the overlay mapper to know that an actor
+ // package changed via an explicit reference, even if the actor
+ // isn't installed, so skip if that's the case.
+ continue;
+ }
+ synchronized (mCacheLock) {
+ updateShouldFilterCacheForPackage(mShouldFilterCache, null,
+ changedPkgSetting, settings, users, USER_ALL, settings.size());
}
}
-
- onChanged();
}
+ onChanged();
});
}
/**
- * Returns true if the calling package should not be able to see the target package, false if no
- * filtering should be done.
- *
- * @param callingUid the uid of the caller attempting to access a package
- * @param callingSetting the setting attempting to access a package or null if it could not be
- * found
- * @param targetPkgSetting the package being accessed
- * @param userId the user in which this access is being attempted
+ * See {@link AppsFilterSnapshot#shouldFilterApplication(int, Object, PackageStateInternal,
+ * int)}
*/
+ @Override
public boolean shouldFilterApplication(int callingUid, @Nullable Object callingSetting,
PackageStateInternal targetPkgSetting, int userId) {
if (DEBUG_TRACING) {
@@ -1315,31 +1396,22 @@ public class AppsFilter implements Watchable, Snappable {
|| callingAppId == targetPkgSetting.getAppId()) {
return false;
}
+ final boolean shouldUseCache;
synchronized (mCacheLock) {
- if (mShouldFilterCache != null) { // use cache
- final int callingIndex = mShouldFilterCache.indexOfKey(callingUid);
- if (callingIndex < 0) {
- Slog.wtf(TAG, "Encountered calling uid with no cached rules: "
- + callingUid);
- return true;
- }
- final int targetUid = UserHandle.getUid(userId, targetPkgSetting.getAppId());
- final int targetIndex = mShouldFilterCache.indexOfKey(targetUid);
- if (targetIndex < 0) {
- Slog.w(TAG, "Encountered calling -> target with no cached rules: "
- + callingUid + " -> " + targetUid);
- return true;
- }
- if (!mShouldFilterCache.valueAt(callingIndex, targetIndex)) {
- return false;
- }
- } else {
- if (!shouldFilterApplicationInternal(
- callingUid, callingSetting, targetPkgSetting, userId)) {
- return false;
- }
+ shouldUseCache = mShouldFilterCache.size() != 0;
+ }
+ if (shouldUseCache) { // use cache
+ if (!shouldFilterApplicationUsingCache(callingUid, targetPkgSetting.getAppId(),
+ userId)) {
+ return false;
+ }
+ } else {
+ if (!shouldFilterApplicationInternal(
+ callingUid, callingSetting, targetPkgSetting, userId)) {
+ return false;
}
}
+
if (DEBUG_LOGGING || mFeatureConfig.isLoggingEnabled(callingAppId)) {
log(callingSetting, targetPkgSetting, "BLOCKED");
}
@@ -1351,6 +1423,25 @@ public class AppsFilter implements Watchable, Snappable {
}
}
+ private boolean shouldFilterApplicationUsingCache(int callingUid, int appId, int userId) {
+ synchronized (mCacheLock) {
+ final int callingIndex = mShouldFilterCache.indexOfKey(callingUid);
+ if (callingIndex < 0) {
+ Slog.wtf(TAG, "Encountered calling uid with no cached rules: "
+ + callingUid);
+ return true;
+ }
+ final int targetUid = UserHandle.getUid(userId, appId);
+ final int targetIndex = mShouldFilterCache.indexOfKey(targetUid);
+ if (targetIndex < 0) {
+ Slog.w(TAG, "Encountered calling -> target with no cached rules: "
+ + callingUid + " -> " + targetUid);
+ return true;
+ }
+ return mShouldFilterCache.valueAt(callingIndex, targetIndex);
+ }
+ }
+
private boolean shouldFilterApplicationInternal(int callingUid, Object callingSetting,
PackageStateInternal targetPkgSetting, int targetUserId) {
if (DEBUG_TRACING) {
@@ -1437,10 +1528,10 @@ public class AppsFilter implements Watchable, Snappable {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "requestsQueryAllPackages");
}
if (callingPkgSetting != null) {
- if (callingPkgSetting.getPkg() != null
- && requestsQueryAllPackages(callingPkgSetting.getPkg())) {
- return false;
- }
+ if (callingPkgSetting.getPkg() != null
+ && requestsQueryAllPackages(callingPkgSetting.getPkg())) {
+ return false;
+ }
} else {
for (int i = callingSharedPkgSettings.size() - 1; i >= 0; i--) {
AndroidPackage pkg = callingSharedPkgSettings.valueAt(i).getPkg();
@@ -1469,142 +1560,147 @@ public class AppsFilter implements Watchable, Snappable {
return false;
}
- try {
- if (DEBUG_TRACING) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mForceQueryable");
- }
- if (mForceQueryable.contains(targetAppId)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "force queryable");
+ synchronized (mLock) {
+ try {
+ if (DEBUG_TRACING) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mForceQueryable");
}
- return false;
- }
- } finally {
- if (DEBUG_TRACING) {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
- }
- try {
- if (DEBUG_TRACING) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaPackage");
- }
- if (mQueriesViaPackage.contains(callingAppId, targetAppId)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "queries package");
+ if (mForceQueryable.contains(targetAppId)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "force queryable");
+ }
+ return false;
+ }
+ } finally {
+ if (DEBUG_TRACING) {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- return false;
- }
- } finally {
- if (DEBUG_TRACING) {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
- }
- try {
- if (DEBUG_TRACING) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaComponent");
- }
- if (mQueriesViaComponentRequireRecompute) {
- mStateProvider.runWithState((settings, users) -> {
- recomputeComponentVisibility(settings);
- });
}
- if (mQueriesViaComponent.contains(callingAppId, targetAppId)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "queries component");
+ try {
+ if (DEBUG_TRACING) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaPackage");
+ }
+ if (mQueriesViaPackage.contains(callingAppId, targetAppId)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "queries package");
+ }
+ return false;
+ }
+ } finally {
+ if (DEBUG_TRACING) {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- return false;
}
- } finally {
- if (DEBUG_TRACING) {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ try {
+ if (DEBUG_TRACING) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaComponent");
+ }
+ if (mQueriesViaComponentRequireRecompute) {
+ mStateProvider.runWithState((settings, users) -> {
+ synchronized (mLock) {
+ recomputeComponentVisibility(settings);
+ }
+ });
+ }
+ if (mQueriesViaComponent.contains(callingAppId, targetAppId)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "queries component");
+ }
+ return false;
+ }
+ } finally {
+ if (DEBUG_TRACING) {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
}
- }
- try {
- if (DEBUG_TRACING) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mImplicitlyQueryable");
- }
- final int targetUid = UserHandle.getUid(targetUserId, targetAppId);
- if (mImplicitlyQueryable.contains(callingUid, targetUid)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "implicitly queryable for user");
+ try {
+ if (DEBUG_TRACING) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mImplicitlyQueryable");
+ }
+ final int targetUid = UserHandle.getUid(targetUserId, targetAppId);
+ if (mImplicitlyQueryable.contains(callingUid, targetUid)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "implicitly queryable for user");
+ }
+ return false;
+ }
+ } finally {
+ if (DEBUG_TRACING) {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- return false;
- }
- } finally {
- if (DEBUG_TRACING) {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- }
- try {
- if (DEBUG_TRACING) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mRetainedImplicitlyQueryable");
- }
- final int targetUid = UserHandle.getUid(targetUserId, targetAppId);
- if (mRetainedImplicitlyQueryable.contains(callingUid, targetUid)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting,
- "retained implicitly queryable for user");
+ try {
+ if (DEBUG_TRACING) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mRetainedImplicitlyQueryable");
+ }
+ final int targetUid = UserHandle.getUid(targetUserId, targetAppId);
+ if (mRetainedImplicitlyQueryable.contains(callingUid, targetUid)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting,
+ "retained implicitly queryable for user");
+ }
+ return false;
+ }
+ } finally {
+ if (DEBUG_TRACING) {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- return false;
- }
- } finally {
- if (DEBUG_TRACING) {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- }
- try {
- if (DEBUG_TRACING) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mOverlayReferenceMapper");
- }
- final String targetName = targetPkg.getPackageName();
- if (callingSharedPkgSettings != null) {
- int size = callingSharedPkgSettings.size();
- for (int index = 0; index < size; index++) {
- PackageStateInternal pkgSetting = callingSharedPkgSettings.valueAt(index);
+ try {
+ if (DEBUG_TRACING) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mOverlayReferenceMapper");
+ }
+ final String targetName = targetPkg.getPackageName();
+ if (callingSharedPkgSettings != null) {
+ int size = callingSharedPkgSettings.size();
+ for (int index = 0; index < size; index++) {
+ PackageStateInternal pkgSetting = callingSharedPkgSettings.valueAt(
+ index);
+ if (mOverlayReferenceMapper.isValidActor(targetName,
+ pkgSetting.getPackageName())) {
+ if (DEBUG_LOGGING) {
+ log(callingPkgSetting, targetPkgSetting,
+ "matches shared user of package that acts on target of "
+ + "overlay");
+ }
+ return false;
+ }
+ }
+ } else {
if (mOverlayReferenceMapper.isValidActor(targetName,
- pkgSetting.getPackageName())) {
+ callingPkgSetting.getPackageName())) {
if (DEBUG_LOGGING) {
log(callingPkgSetting, targetPkgSetting,
- "matches shared user of package that acts on target of "
- + "overlay");
+ "acts on target of overlay");
}
return false;
}
}
- } else {
- if (mOverlayReferenceMapper.isValidActor(targetName,
- callingPkgSetting.getPackageName())) {
+ } finally {
+ if (DEBUG_TRACING) {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
+ try {
+ if (DEBUG_TRACING) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueryableViaUsesLibrary");
+ }
+ if (mQueryableViaUsesLibrary.contains(callingAppId, targetAppId)) {
if (DEBUG_LOGGING) {
- log(callingPkgSetting, targetPkgSetting, "acts on target of overlay");
+ log(callingSetting, targetPkgSetting, "queryable for library users");
}
return false;
}
- }
- } finally {
- if (DEBUG_TRACING) {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
- }
-
- try {
- if (DEBUG_TRACING) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueryableViaUsesLibrary");
- }
- if (mQueryableViaUsesLibrary.contains(callingAppId, targetAppId)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "queryable for library users");
+ } finally {
+ if (DEBUG_TRACING) {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- return false;
- }
- } finally {
- if (DEBUG_TRACING) {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
-
return true;
} finally {
if (DEBUG_TRACING) {
@@ -1613,7 +1709,11 @@ public class AppsFilter implements Watchable, Snappable {
}
}
- boolean canQueryPackage(@NonNull AndroidPackage querying, String potentialTarget) {
+ /**
+ * See {@link AppsFilterSnapshot#canQueryPackage(AndroidPackage, String)}
+ */
+ @Override
+ public boolean canQueryPackage(@NonNull AndroidPackage querying, String potentialTarget) {
int appId = UserHandle.getAppId(querying.getUid());
if (appId < Process.FIRST_APPLICATION_UID) {
return true;
@@ -1668,6 +1768,11 @@ public class AppsFilter implements Watchable, Snappable {
+ targetPkgSetting + " " + description);
}
+ /**
+ * See {@link AppsFilterSnapshot#dumpQueries(PrintWriter, Integer, DumpState, int[],
+ * QuadFunction)}
+ */
+ @Override
public void dumpQueries(
PrintWriter pw, @Nullable Integer filteringAppId, DumpState dumpState, int[] users,
QuadFunction<Integer, Integer, Integer, Boolean, String[]> getPackagesForUid) {
@@ -1702,27 +1807,30 @@ public class AppsFilter implements Watchable, Snappable {
}
}
pw.println(" system apps queryable: " + mSystemAppsQueryable);
- dumpPackageSet(pw, filteringAppId, mForceQueryable, "forceQueryable", " ", expandPackages);
- pw.println(" queries via package name:");
- dumpQueriesMap(pw, filteringAppId, mQueriesViaPackage, " ", expandPackages);
- pw.println(" queries via component:");
- dumpQueriesMap(pw, filteringAppId, mQueriesViaComponent, " ", expandPackages);
- pw.println(" queryable via interaction:");
- for (int user : users) {
- pw.append(" User ").append(Integer.toString(user)).println(":");
- dumpQueriesMap(pw,
- filteringAppId == null ? null : UserHandle.getUid(user, filteringAppId),
- mImplicitlyQueryable, " ", expandPackages);
- dumpQueriesMap(pw,
- filteringAppId == null ? null : UserHandle.getUid(user, filteringAppId),
- mRetainedImplicitlyQueryable, " ", expandPackages);
+ synchronized (mLock) {
+ dumpPackageSet(pw, filteringAppId, mForceQueryable.untrackedStorage(),
+ "forceQueryable", " ", expandPackages);
+ pw.println(" queries via package name:");
+ dumpQueriesMap(pw, filteringAppId, mQueriesViaPackage, " ", expandPackages);
+ pw.println(" queries via component:");
+ dumpQueriesMap(pw, filteringAppId, mQueriesViaComponent, " ", expandPackages);
+ pw.println(" queryable via interaction:");
+ for (int user : users) {
+ pw.append(" User ").append(Integer.toString(user)).println(":");
+ dumpQueriesMap(pw,
+ filteringAppId == null ? null : UserHandle.getUid(user, filteringAppId),
+ mImplicitlyQueryable, " ", expandPackages);
+ dumpQueriesMap(pw,
+ filteringAppId == null ? null : UserHandle.getUid(user, filteringAppId),
+ mRetainedImplicitlyQueryable, " ", expandPackages);
+ }
+ pw.println(" queryable via uses-library:");
+ dumpQueriesMap(pw, filteringAppId, mQueryableViaUsesLibrary, " ", expandPackages);
}
- pw.println(" queryable via uses-library:");
- dumpQueriesMap(pw, filteringAppId, mQueryableViaUsesLibrary, " ", expandPackages);
}
private static void dumpQueriesMap(PrintWriter pw, @Nullable Integer filteringId,
- SparseSetArray<Integer> queriesMap, String spacing,
+ WatchedSparseSetArray<Integer> queriesMap, String spacing,
@Nullable ToString<Integer> toString) {
for (int i = 0; i < queriesMap.size(); i++) {
Integer callingId = queriesMap.keyAt(i);
@@ -1750,7 +1858,7 @@ public class AppsFilter implements Watchable, Snappable {
}
private static <T> void dumpPackageSet(PrintWriter pw, @Nullable T filteringId,
- Set<T> targetPkgSet, String subTitle, String spacing,
+ ArraySet<T> targetPkgSet, String subTitle, String spacing,
@Nullable ToString<T> toString) {
if (targetPkgSet != null && targetPkgSet.size() > 0
&& (filteringId == null || targetPkgSet.contains(filteringId))) {
diff --git a/services/core/java/com/android/server/pm/AppsFilterSnapshot.java b/services/core/java/com/android/server/pm/AppsFilterSnapshot.java
new file mode 100644
index 000000000000..cb8c649ded00
--- /dev/null
+++ b/services/core/java/com/android/server/pm/AppsFilterSnapshot.java
@@ -0,0 +1,86 @@
+/*
+ * 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 com.android.server.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.internal.util.function.QuadFunction;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import java.io.PrintWriter;
+
+/**
+ * Read-only interface used by computer and snapshots to query the visibility of packages
+ */
+public interface AppsFilterSnapshot {
+ /**
+ * Fetches all app Ids that a given setting is currently visible to, per provided user. This
+ * only includes UIDs >= {@link Process#FIRST_APPLICATION_UID} as all other UIDs can already see
+ * all applications.
+ *
+ * If the setting is visible to all UIDs, null is returned. If an app is not visible to any
+ * applications, the int array will be empty.
+ *
+ * @param users the set of users that should be evaluated for this calculation
+ * @param existingSettings the set of all package settings that currently exist on device
+ * @return a SparseArray mapping userIds to a sorted int array of appIds that may view the
+ * provided setting or null if the app is visible to all and no allow list should be
+ * applied.
+ */
+ SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
+ ArrayMap<String, ? extends PackageStateInternal> existingSettings);
+
+ /**
+ * Returns true if the calling package should not be able to see the target package, false if no
+ * filtering should be done.
+ *
+ * @param callingUid the uid of the caller attempting to access a package
+ * @param callingSetting the setting attempting to access a package or null if it could not be
+ * found
+ * @param targetPkgSetting the package being accessed
+ * @param userId the user in which this access is being attempted
+ */
+ boolean shouldFilterApplication(int callingUid, @Nullable Object callingSetting,
+ PackageStateInternal targetPkgSetting, int userId);
+
+ /**
+ * Returns whether the querying package is allowed to see the target package.
+ *
+ * @param querying the querying package
+ * @param potentialTarget the package name of the target package
+ */
+ boolean canQueryPackage(@NonNull AndroidPackage querying, String potentialTarget);
+
+ /**
+ * Dump the packages that are queryable by the querying package.
+ *
+ * @param pw the output print writer
+ * @param filteringAppId the querying package's app ID
+ * @param dumpState the state of the dumping
+ * @param users the users for which the packages are installed
+ * @param getPackagesForUid the function that produces the package names for given uids
+ */
+ void dumpQueries(PrintWriter pw, @Nullable Integer filteringAppId, DumpState dumpState,
+ int[] users,
+ QuadFunction<Integer, Integer, Integer, Boolean, String[]> getPackagesForUid);
+
+}
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index f1394d403bf7..bf1196d6b969 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -320,17 +320,6 @@ public final class BroadcastHelper {
broadcastAllowlist, null);
}
- public void sendDistractingPackagesChanged(String[] pkgList, int[] uidList, int userId,
- int distractionFlags) {
- final Bundle extras = new Bundle(3);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
- extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
- sendPackageBroadcast(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null, extras,
- Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null, new int[]{userId}, null, null,
- null);
- }
-
public void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
int[] userIds, int[] instantUserIds) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 8432f48e269e..8ac3dca1991d 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -380,7 +380,7 @@ public class ComputerEngine implements Computer {
private final ResolveInfo mInstantAppInstallerInfo;
private final InstantAppRegistry mInstantAppRegistry;
private final ApplicationInfo mLocalAndroidApplication;
- private final AppsFilter mAppsFilter;
+ private final AppsFilterSnapshot mAppsFilter;
private final WatchedArrayMap<String, Integer> mFrozenPackages;
// Immutable service attribute
diff --git a/services/core/java/com/android/server/pm/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
new file mode 100644
index 000000000000..7dc45b58a773
--- /dev/null
+++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
@@ -0,0 +1,198 @@
+/*
+ * 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 com.android.server.pm;
+
+import static android.content.pm.PackageManager.RESTRICTION_NONE;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.content.pm.PackageManager.DistractionRestriction;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mark, unmark, or remove any {@link DistractionRestriction restrictions} set on given packages.
+ */
+public final class DistractingPackageHelper {
+
+ // TODO(b/198166813): remove PMS dependency
+ private final PackageManagerService mPm;
+ private final PackageManagerServiceInjector mInjector;
+ private final BroadcastHelper mBroadcastHelper;
+ private final SuspendPackageHelper mSuspendPackageHelper;
+
+ /**
+ * Constructor for {@link PackageManagerService}.
+ */
+ DistractingPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector,
+ BroadcastHelper broadcastHelper, SuspendPackageHelper suspendPackageHelper) {
+ mPm = pm;
+ mInjector = injector;
+ mBroadcastHelper = broadcastHelper;
+ mSuspendPackageHelper = suspendPackageHelper;
+ }
+
+ /**
+ * Mark or unmark the given packages as distracting to the given user.
+ *
+ * @param packageNames Packages to mark as distracting.
+ * @param restrictionFlags Any combination of restrictions to impose on the given packages.
+ * {@link DistractionRestriction#RESTRICTION_NONE} can be used to
+ * clear any existing restrictions.
+ * @param userId the user for which changes are taking place.
+ * @param callingUid The caller's uid.
+ *
+ * @return A list of packages that could not have the {@code restrictionFlags} set. The system
+ * may prevent restricting critical packages to preserve normal device function.
+ */
+ String[] setDistractingPackageRestrictionsAsUser(@NonNull Computer snapshot,
+ String[] packageNames, int restrictionFlags, int userId, int callingUid) {
+ if (ArrayUtils.isEmpty(packageNames)) {
+ return packageNames;
+ }
+ if (restrictionFlags != RESTRICTION_NONE
+ && !mSuspendPackageHelper.isSuspendAllowedForUser(snapshot, userId, callingUid)) {
+ Slog.w(PackageManagerService.TAG,
+ "Cannot restrict packages due to restrictions on user " + userId);
+ return packageNames;
+ }
+
+ final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
+ final IntArray changedUids = new IntArray(packageNames.length);
+ final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
+
+ final ArraySet<String> changesToCommit = new ArraySet<>();
+ final boolean[] canRestrict = (restrictionFlags != RESTRICTION_NONE)
+ ? mSuspendPackageHelper.canSuspendPackageForUser(snapshot, packageNames, userId,
+ callingUid) : null;
+ for (int i = 0; i < packageNames.length; i++) {
+ final String packageName = packageNames[i];
+ final PackageStateInternal packageState =
+ snapshot.getPackageStateInternal(packageName);
+ if (packageState == null
+ || snapshot.shouldFilterApplication(packageState, callingUid, userId)) {
+ Slog.w(PackageManagerService.TAG,
+ "Could not find package setting for package: " + packageName
+ + ". Skipping...");
+ unactionedPackages.add(packageName);
+ continue;
+ }
+ if (canRestrict != null && !canRestrict[i]) {
+ unactionedPackages.add(packageName);
+ continue;
+ }
+ final int oldDistractionFlags = packageState.getUserStateOrDefault(userId)
+ .getDistractionFlags();
+ if (restrictionFlags != oldDistractionFlags) {
+ changedPackagesList.add(packageName);
+ changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ changesToCommit.add(packageName);
+ }
+ }
+
+ mPm.commitPackageStateMutation(null /* initialState */, mutator -> {
+ final int size = changesToCommit.size();
+ for (int index = 0; index < size; index++) {
+ mutator.forPackage(changesToCommit.valueAt(index))
+ .userState(userId)
+ .setDistractionFlags(restrictionFlags);
+ }
+ });
+
+ if (!changedPackagesList.isEmpty()) {
+ final String[] changedPackages = changedPackagesList.toArray(
+ new String[changedPackagesList.size()]);
+ sendDistractingPackagesChanged(changedPackages, changedUids.toArray(), userId,
+ restrictionFlags);
+ mPm.scheduleWritePackageRestrictions(userId);
+ }
+ return unactionedPackages.toArray(new String[0]);
+ }
+
+ /**
+ * Removes any {@link DistractionRestriction restrictions} set on given packages.
+ *
+ * <p> Caller must flush package restrictions if it cares about immediate data consistency.
+ *
+ * @param packagesToChange The packages on which restrictions are to be removed.
+ * @param userId the user for which changes are taking place.
+ */
+ void removeDistractingPackageRestrictions(@NonNull Computer snapshot,
+ String[] packagesToChange, int userId) {
+ if (ArrayUtils.isEmpty(packagesToChange)) {
+ return;
+ }
+ final List<String> changedPackages = new ArrayList<>(packagesToChange.length);
+ final IntArray changedUids = new IntArray(packagesToChange.length);
+ for (int i = 0; i < packagesToChange.length; i++) {
+ final String packageName = packagesToChange[i];
+ final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
+ if (ps != null && ps.getUserStateOrDefault(userId).getDistractionFlags()
+ != RESTRICTION_NONE) {
+ changedPackages.add(ps.getPackageName());
+ changedUids.add(UserHandle.getUid(userId, ps.getAppId()));
+ }
+ }
+ mPm.commitPackageStateMutation(null /* initialState */, mutator -> {
+ for (int index = 0; index < changedPackages.size(); index++) {
+ mutator.forPackage(changedPackages.get(index))
+ .userState(userId)
+ .setDistractionFlags(RESTRICTION_NONE);
+ }
+ });
+
+ if (!changedPackages.isEmpty()) {
+ final String[] packageArray = changedPackages.toArray(
+ new String[changedPackages.size()]);
+ sendDistractingPackagesChanged(packageArray, changedUids.toArray(), userId,
+ RESTRICTION_NONE);
+ mPm.scheduleWritePackageRestrictions(userId);
+ }
+ }
+
+ /**
+ * Send broadcast intents for packages distracting changes.
+ *
+ * @param pkgList The names of packages which have suspension changes.
+ * @param uidList The uids of packages which have suspension changes.
+ * @param userId The user where packages reside.
+ */
+ void sendDistractingPackagesChanged(@NonNull String[] pkgList,
+ int[] uidList, int userId, int distractionFlags) {
+ final Bundle extras = new Bundle(3);
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+ extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
+
+ final Handler handler = mInjector.getHandler();
+ handler.post(() -> mBroadcastHelper.sendPackageBroadcast(
+ Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */, extras,
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+ null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+ null /* allowList */, null /* bOptions */));
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 2b733754685e..2fe7913342a2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -83,6 +83,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal {
@NonNull protected abstract PackageObserverHelper getPackageObserverHelper();
@NonNull protected abstract ResolveIntentHelper getResolveIntentHelper();
@NonNull protected abstract SuspendPackageHelper getSuspendPackageHelper();
+ @NonNull protected abstract DistractingPackageHelper getDistractingPackageHelper();
@NonNull protected abstract ProtectedPackages getProtectedPackages();
@NonNull protected abstract UserNeedsBadgingCache getUserNeedsBadging();
@NonNull protected abstract InstantAppRegistry getInstantAppRegistry();
@@ -248,8 +249,8 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal {
@Override
@Deprecated
public final void removeDistractingPackageRestrictions(String packageName, int userId) {
- mService.removeDistractingPackageRestrictions(snapshot(), new String[]{packageName},
- userId);
+ getDistractingPackageHelper().removeDistractingPackageRestrictions(snapshot(),
+ new String[]{packageName}, userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6cf63d10f1f1..4f152d6c0819 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -159,7 +159,6 @@ import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.ExceptionUtils;
-import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -721,7 +720,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
@Watched
- final AppsFilter mAppsFilter;
+ final AppsFilterImpl mAppsFilter;
final PackageParser2.Callback mPackageParserCallback;
@@ -939,6 +938,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
private final ResolveIntentHelper mResolveIntentHelper;
private final DexOptHelper mDexOptHelper;
private final SuspendPackageHelper mSuspendPackageHelper;
+ private final DistractingPackageHelper mDistractingPackageHelper;
private final IntentResolverInterceptor mIntentResolverInterceptor;
private final StorageEventHelper mStorageEventHelper;
@@ -979,7 +979,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
public final InstantAppRegistry instantAppRegistry;
public final ApplicationInfo androidApplication;
public final String appPredictionServicePackage;
- public final AppsFilter appsFilter;
+ public final AppsFilterSnapshot appsFilter;
public final ComponentResolverApi componentResolver;
public final PackageManagerService service;
public final WatchedArrayMap<String, Integer> frozenPackages;
@@ -1431,7 +1431,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
RuntimePermissionsPersistence.createInstance(),
i.getPermissionManagerServiceInternal(),
domainVerificationService, lock),
- (i, pm) -> AppsFilter.create(i, i.getLocalService(PackageManagerInternal.class)),
+ (i, pm) -> AppsFilterImpl.create(i,
+ i.getLocalService(PackageManagerInternal.class)),
(i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"),
(i, pm) -> SystemConfig.getInstance(),
(i, pm) -> new PackageDexOptimizer(i.getInstaller(), i.getInstallLock(),
@@ -1683,6 +1684,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mResolveIntentHelper = testParams.resolveIntentHelper;
mDexOptHelper = testParams.dexOptHelper;
mSuspendPackageHelper = testParams.suspendPackageHelper;
+ mDistractingPackageHelper = testParams.distractingPackageHelper;
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
@@ -1842,6 +1844,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mProtectedPackages);
mStorageEventHelper = new StorageEventHelper(this, mDeletePackageHelper,
mRemovePackageHelper);
+ mDistractingPackageHelper = new DistractingPackageHelper(this, mInjector, mBroadcastHelper,
+ mSuspendPackageHelper);
synchronized (mLock) {
// Create the computer as soon as the state objects have been installed. The
@@ -3065,43 +3069,19 @@ public class PackageManagerService implements PackageSender, TestUtilityService
void removeAllDistractingPackageRestrictions(@NonNull Computer snapshot, int userId) {
final String[] allPackages = snapshot.getAllAvailablePackageNames();
- removeDistractingPackageRestrictions(snapshot, allPackages, userId);
+ mDistractingPackageHelper.removeDistractingPackageRestrictions(snapshot, allPackages,
+ userId);
}
- /**
- * Removes any {@link android.content.pm.PackageManager.DistractionRestriction restrictions}
- * set on given packages.
- *
- * <p> Caller must flush package restrictions if it cares about immediate data consistency.
- *
- * @param packagesToChange The packages on which restrictions are to be removed.
- * @param userId the user for which changes are taking place.
- */
- void removeDistractingPackageRestrictions(@NonNull Computer snapshot,
- String[] packagesToChange, int userId) {
- final List<String> changedPackages = new ArrayList<>();
- final IntArray changedUids = new IntArray();
- for (String packageName : packagesToChange) {
- final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
- if (ps != null && ps.getUserStateOrDefault(userId).getDistractionFlags() != 0) {
- changedPackages.add(ps.getPackageName());
- changedUids.add(UserHandle.getUid(userId, ps.getAppId()));
- }
- }
- commitPackageStateMutation(null, mutator -> {
- for (int index = 0; index < changedPackages.size(); index++) {
- mutator.forPackage(changedPackages.get(index))
- .userState(userId)
- .setDistractionFlags(0);
- }
- });
+ private void enforceCanSetDistractingPackageRestrictionsAsUser(@NonNull Computer snapshot,
+ int callingUid, int userId, String callingMethod) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
+ callingMethod);
- if (!changedPackages.isEmpty()) {
- final String[] packageArray = changedPackages.toArray(
- new String[changedPackages.size()]);
- mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged(
- packageArray, changedUids.toArray(), userId, 0));
- scheduleWritePackageRestrictions(userId);
+ if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID
+ && UserHandle.getUserId(callingUid) != userId) {
+ throw new SecurityException("Calling uid " + callingUid + " cannot call for user "
+ + userId);
}
}
@@ -5591,73 +5571,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService
@Override
public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames,
int restrictionFlags, int userId) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
- "setDistractingPackageRestrictionsAsUser");
-
final int callingUid = Binder.getCallingUid();
- if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID
- && UserHandle.getUserId(callingUid) != userId) {
- throw new SecurityException("Calling uid " + callingUid + " cannot call for user "
- + userId);
- }
- Objects.requireNonNull(packageNames, "packageNames cannot be null");
final Computer snapshot = snapshotComputer();
- if (restrictionFlags != 0
- && !mSuspendPackageHelper.isSuspendAllowedForUser(snapshot, userId,
- callingUid)) {
- Slog.w(PackageManagerService.TAG, "Cannot restrict packages due to restrictions on user " + userId);
- return packageNames;
- }
-
- final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
- final IntArray changedUids = new IntArray(packageNames.length);
- final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
-
- ArraySet<String> changesToCommit = new ArraySet<>();
- final boolean[] canRestrict = (restrictionFlags != 0)
- ? mSuspendPackageHelper.canSuspendPackageForUser(snapshot, packageNames, userId,
- callingUid) : null;
- for (int i = 0; i < packageNames.length; i++) {
- final String packageName = packageNames[i];
- final PackageStateInternal packageState =
- snapshot.getPackageStateInternal(packageName);
- if (packageState == null
- || snapshot.shouldFilterApplication(packageState, callingUid, userId)) {
- Slog.w(PackageManagerService.TAG, "Could not find package setting for package: " + packageName
- + ". Skipping...");
- unactionedPackages.add(packageName);
- continue;
- }
- if (canRestrict != null && !canRestrict[i]) {
- unactionedPackages.add(packageName);
- continue;
- }
- final int oldDistractionFlags = packageState.getUserStateOrDefault(userId)
- .getDistractionFlags();
- if (restrictionFlags != oldDistractionFlags) {
- changedPackagesList.add(packageName);
- changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
- changesToCommit.add(packageName);
- }
- }
-
- commitPackageStateMutation(null, mutator -> {
- final int size = changesToCommit.size();
- for (int index = 0; index < size; index++) {
- mutator.forPackage(changesToCommit.valueAt(index))
- .userState(userId)
- .setDistractionFlags(restrictionFlags);
- }
- });
-
- if (!changedPackagesList.isEmpty()) {
- final String[] changedPackages = changedPackagesList.toArray(
- new String[changedPackagesList.size()]);
- mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged(
- changedPackages, changedUids.toArray(), userId, restrictionFlags));
- scheduleWritePackageRestrictions(userId);
- }
- return unactionedPackages.toArray(new String[0]);
+ enforceCanSetDistractingPackageRestrictionsAsUser(snapshot, callingUid, userId,
+ "setDistractingPackageRestrictionsAsUser");
+ Objects.requireNonNull(packageNames, "packageNames cannot be null");
+ return mDistractingPackageHelper.setDistractingPackageRestrictionsAsUser(snapshot,
+ packageNames, restrictionFlags, userId, callingUid);
}
@Override
@@ -6121,6 +6041,12 @@ public class PackageManagerService implements PackageSender, TestUtilityService
@NonNull
@Override
+ protected DistractingPackageHelper getDistractingPackageHelper() {
+ return mDistractingPackageHelper;
+ }
+
+ @NonNull
+ @Override
protected ProtectedPackages getProtectedPackages() {
return mProtectedPackages;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index a02237fa90ce..396994b04514 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -99,7 +99,7 @@ public class PackageManagerServiceInjector {
private final Singleton<UserManagerService>
mUserManagerProducer;
private final Singleton<Settings> mSettingsProducer;
- private final Singleton<AppsFilter> mAppsFilterProducer;
+ private final Singleton<AppsFilterImpl> mAppsFilterProducer;
private final Singleton<PlatformCompat>
mPlatformCompatProducer;
private final Singleton<SystemConfig> mSystemConfigProducer;
@@ -148,7 +148,7 @@ public class PackageManagerServiceInjector {
Producer<PermissionManagerServiceInternal> permissionManagerServiceProducer,
Producer<UserManagerService> userManagerProducer,
Producer<Settings> settingsProducer,
- Producer<AppsFilter> appsFilterProducer,
+ Producer<AppsFilterImpl> appsFilterProducer,
Producer<PlatformCompat> platformCompatProducer,
Producer<SystemConfig> systemConfigProducer,
Producer<PackageDexOptimizer> packageDexOptimizerProducer,
@@ -282,7 +282,7 @@ public class PackageManagerServiceInjector {
return mSettingsProducer.get(this, mPackageManager);
}
- public AppsFilter getAppsFilter() {
+ public AppsFilterImpl getAppsFilter() {
return mAppsFilterProducer.get(this, mPackageManager);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 144231c95f75..16829e0e8f6c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -116,4 +116,5 @@ public final class PackageManagerServiceTestParams {
public DexOptHelper dexOptHelper;
public SuspendPackageHelper suspendPackageHelper;
public StorageEventHelper storageEventHelper;
+ public DistractingPackageHelper distractingPackageHelper;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index f3b06e8f918c..e142ed631092 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -128,6 +128,7 @@ import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -829,7 +830,7 @@ class PackageManagerShellCommand extends ShellCommand {
boolean showVersionCode = false;
boolean listApexOnly = false;
int uid = -1;
- int userId = UserHandle.USER_SYSTEM;
+ int defaultUserId = UserHandle.USER_ALL;
try {
String opt;
while ((opt = getNextOption()) != null) {
@@ -873,7 +874,7 @@ class PackageManagerShellCommand extends ShellCommand {
listApexOnly = true;
break;
case "--user":
- userId = UserHandle.parseUserArg(getNextArgRequired());
+ defaultUserId = UserHandle.parseUserArg(getNextArgRequired());
break;
case "--uid":
showUid = true;
@@ -891,84 +892,104 @@ class PackageManagerShellCommand extends ShellCommand {
final String filter = getNextArg();
- if (userId == UserHandle.USER_ALL) {
- getFlags |= PackageManager.MATCH_KNOWN_PACKAGES;
+ int[] userIds = {defaultUserId};
+ if (defaultUserId == UserHandle.USER_ALL) {
+ final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ userIds = umi.getUserIds();
}
if (showSdks) {
getFlags |= PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES;
}
- final int translatedUserId =
- translateUserId(userId, UserHandle.USER_SYSTEM, "runListPackages");
- @SuppressWarnings("unchecked")
- final ParceledListSlice<PackageInfo> slice =
- mInterface.getInstalledPackages(getFlags, translatedUserId);
- final List<PackageInfo> packages = slice.getList();
-
- final int count = packages.size();
- for (int p = 0; p < count; p++) {
- final PackageInfo info = packages.get(p);
- if (filter != null && !info.packageName.contains(filter)) {
- continue;
- }
- final boolean isApex = info.isApex;
- if (uid != -1 && !isApex && info.applicationInfo.uid != uid) {
- continue;
- }
- final boolean isSystem = !isApex &&
- (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- final boolean isEnabled = !isApex && info.applicationInfo.enabled;
- if ((listDisabled && isEnabled) ||
- (listEnabled && !isEnabled) ||
- (listSystem && !isSystem) ||
- (listThirdParty && isSystem) ||
- (listApexOnly && !isApex)) {
- continue;
- }
-
- String name = null;
- if (showSdks) {
- final ParceledListSlice<SharedLibraryInfo> libsSlice =
- mInterface.getDeclaredSharedLibraries(info.packageName, getFlags, userId);
- if (libsSlice == null) {
+ // Build a map of packages to a list of corresponding uids. Keys are strings containing
+ // the sdk or package name along with optional additional information based on opt.
+ final Map<String, List<String>> out = new HashMap<>();
+ for (int userId : userIds) {
+ final int translatedUserId =
+ translateUserId(userId, UserHandle.USER_SYSTEM, "runListPackages");
+ @SuppressWarnings("unchecked") final ParceledListSlice<PackageInfo> slice =
+ mInterface.getInstalledPackages(getFlags, translatedUserId);
+ final List<PackageInfo> packages = slice.getList();
+
+ final int count = packages.size();
+ for (int p = 0; p < count; p++) {
+ final PackageInfo info = packages.get(p);
+ final StringBuilder stringBuilder = new StringBuilder();
+ if (filter != null && !info.packageName.contains(filter)) {
continue;
}
- final List<SharedLibraryInfo> libs = libsSlice.getList();
- for (int l = 0, lsize = libs.size(); l < lsize; ++l) {
- SharedLibraryInfo lib = libs.get(l);
- if (lib.getType() == SharedLibraryInfo.TYPE_SDK_PACKAGE) {
- name = lib.getName() + ":" + lib.getLongVersion();
- break;
- }
+ final boolean isApex = info.isApex;
+ if (uid != -1 && !isApex && info.applicationInfo.uid != uid) {
+ continue;
}
- if (name == null) {
+
+ final boolean isSystem = !isApex
+ && (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ final boolean isEnabled = !isApex && info.applicationInfo.enabled;
+ if ((listDisabled && isEnabled)
+ || (listEnabled && !isEnabled)
+ || (listSystem && !isSystem)
+ || (listThirdParty && isSystem)
+ || (listApexOnly && !isApex)) {
continue;
}
- } else {
- name = info.packageName;
- }
- pw.print(prefix);
- if (showSourceDir) {
- pw.print(info.applicationInfo.sourceDir);
- pw.print("=");
- }
- pw.print(name);
- if (showVersionCode) {
- pw.print(" versionCode:");
- if (info.applicationInfo != null) {
- pw.print(info.applicationInfo.longVersionCode);
+ String name = null;
+ if (showSdks) {
+ final ParceledListSlice<SharedLibraryInfo> libsSlice =
+ mInterface.getDeclaredSharedLibraries(
+ info.packageName, getFlags, userId
+ );
+ if (libsSlice == null) {
+ continue;
+ }
+ final List<SharedLibraryInfo> libs = libsSlice.getList();
+ for (int l = 0, lsize = libs.size(); l < lsize; ++l) {
+ SharedLibraryInfo lib = libs.get(l);
+ if (lib.getType() == SharedLibraryInfo.TYPE_SDK_PACKAGE) {
+ name = lib.getName() + ":" + lib.getLongVersion();
+ break;
+ }
+ }
+ if (name == null) {
+ continue;
+ }
} else {
- pw.print(info.getLongVersionCode());
+ name = info.packageName;
+ }
+
+ stringBuilder.append(prefix);
+ if (showSourceDir) {
+ stringBuilder.append(info.applicationInfo.sourceDir);
+ stringBuilder.append("=");
+ }
+ stringBuilder.append(name);
+ if (showVersionCode) {
+ stringBuilder.append(" versionCode:");
+ if (info.applicationInfo != null) {
+ stringBuilder.append(info.applicationInfo.longVersionCode);
+ } else {
+ stringBuilder.append(info.getLongVersionCode());
+ }
+ }
+ if (listInstaller) {
+ stringBuilder.append(" installer=");
+ stringBuilder.append(mInterface.getInstallerPackageName(info.packageName));
+ }
+ List<String> uids = out.computeIfAbsent(
+ stringBuilder.toString(), k -> new ArrayList<>()
+ );
+ if (showUid && !isApex) {
+ uids.add(String.valueOf(info.applicationInfo.uid));
}
}
- if (listInstaller) {
- pw.print(" installer=");
- pw.print(mInterface.getInstallerPackageName(info.packageName));
- }
- if (showUid && !isApex) {
+ }
+ for (Map.Entry<String, List<String>> entry : out.entrySet()) {
+ pw.print(entry.getKey());
+ List<String> uids = entry.getValue();
+ if (!uids.isEmpty()) {
pw.print(" uid:");
- pw.print(info.applicationInfo.uid);
+ pw.print(String.join(",", uids));
}
pw.println();
}
diff --git a/services/core/java/com/android/server/utils/WatchedArrayList.java b/services/core/java/com/android/server/utils/WatchedArrayList.java
index bb0ba1329d86..6059f9675e34 100644
--- a/services/core/java/com/android/server/utils/WatchedArrayList.java
+++ b/services/core/java/com/android/server/utils/WatchedArrayList.java
@@ -273,6 +273,13 @@ public class WatchedArrayList<E> extends WatchableImpl
}
/**
+ * Return true if all the objects in the given collection are in this array list.
+ */
+ public boolean containsAll(Collection<?> c) {
+ return mStorage.containsAll(c);
+ }
+
+ /**
* Ensure capacity.
*/
public void ensureCapacity(int min) {
diff --git a/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java b/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
index 25ae00004b3e..c43e7f9babe6 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
@@ -18,6 +18,7 @@ package com.android.server.utils;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
@@ -168,12 +169,19 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
* A copy constructor that can be used for snapshotting.
*/
private WatchedSparseBooleanMatrix(WatchedSparseBooleanMatrix r) {
- mOrder = r.mOrder;
- mSize = r.mSize;
- mKeys = r.mKeys.clone();
- mMap = r.mMap.clone();
- mInUse = r.mInUse.clone();
- mValues = r.mValues.clone();
+ copyFrom(r);
+ }
+
+ /**
+ * Copy from src to this.
+ */
+ public void copyFrom(@NonNull WatchedSparseBooleanMatrix src) {
+ mOrder = src.mOrder;
+ mSize = src.mSize;
+ mKeys = src.mKeys.clone();
+ mMap = src.mMap.clone();
+ mInUse = src.mInUse.clone();
+ mValues = src.mValues.clone();
}
/**
diff --git a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
new file mode 100644
index 000000000000..05db12e49a13
--- /dev/null
+++ b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
@@ -0,0 +1,177 @@
+/*
+ * 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 com.android.server.utils;
+
+import android.annotation.NonNull;
+import android.util.ArraySet;
+import android.util.SparseSetArray;
+
+
+/**
+ * A watched variant of SparseSetArray. Changes to the array are notified to
+ * registered {@link Watcher}s.
+ * @param <T> The element type, stored in the SparseSetArray.
+ */
+public class WatchedSparseSetArray<T> extends WatchableImpl implements Snappable {
+ // The storage
+ private final SparseSetArray mStorage;
+
+ // A private convenience function
+ private void onChanged() {
+ dispatchChange(this);
+ }
+
+ public WatchedSparseSetArray() {
+ mStorage = new SparseSetArray();
+ }
+
+ /**
+ * Creates a new WatchedSparseSetArray from an existing WatchedSparseSetArray and copy its data
+ */
+ public WatchedSparseSetArray(@NonNull WatchedSparseSetArray<T> watchedSparseSetArray) {
+ mStorage = new SparseSetArray(watchedSparseSetArray.untrackedStorage());
+ }
+
+ /**
+ * Return the underlying storage. This breaks the wrapper but is necessary when
+ * passing the array to distant methods.
+ */
+ public SparseSetArray<T> untrackedStorage() {
+ return mStorage;
+ }
+
+ /**
+ * Add a value for key n.
+ * @return FALSE when the value already existed for the given key, TRUE otherwise.
+ */
+ public boolean add(int n, T value) {
+ final boolean res = mStorage.add(n, value);
+ onChanged();
+ return res;
+ }
+
+ /**
+ * Removes all mappings from this SparseSetArray.
+ */
+ public void clear() {
+ mStorage.clear();
+ onChanged();
+ }
+
+ /**
+ * @return whether the value exists for the key n.
+ */
+ public boolean contains(int n, T value) {
+ return mStorage.contains(n, value);
+ }
+
+ /**
+ * @return the set of items of key n
+ */
+ public ArraySet<T> get(int n) {
+ return mStorage.get(n);
+ }
+
+ /**
+ * Remove a value for key n.
+ * @return TRUE when the value existed for the given key and removed, FALSE otherwise.
+ */
+ public boolean remove(int n, T value) {
+ if (mStorage.remove(n, value)) {
+ onChanged();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Remove all values for key n.
+ */
+ public void remove(int n) {
+ mStorage.remove(n);
+ onChanged();
+ }
+
+ /**
+ * Return the size of the SparseSetArray.
+ */
+ public int size() {
+ return mStorage.size();
+ }
+
+ /**
+ * Return the key stored at the given index.
+ */
+ public int keyAt(int index) {
+ return mStorage.keyAt(index);
+ }
+
+ /**
+ * Return the size of the array at the given index.
+ */
+ public int sizeAt(int index) {
+ return mStorage.sizeAt(index);
+ }
+
+ /**
+ * Return the value in the SetArray at the given key index and value index.
+ */
+ public T valueAt(int intIndex, int valueIndex) {
+ return (T) mStorage.valueAt(intIndex, valueIndex);
+ }
+
+ @NonNull
+ @Override
+ public Object snapshot() {
+ WatchedSparseSetArray l = new WatchedSparseSetArray(this);
+ l.seal();
+ return l;
+ }
+
+ /**
+ * Make <this> a snapshot of the argument. Note that <this> is immutable when the
+ * method returns. <this> must be empty when the function is called.
+ * @param r The source array, which is copied into <this>
+ */
+ public void snapshot(@NonNull WatchedSparseSetArray<T> r) {
+ snapshot(this, r);
+ }
+
+ /**
+ * Make the destination a copy of the source. If the element is a subclass of Snapper then the
+ * copy contains snapshots of the elements. Otherwise the copy contains references to the
+ * elements. The destination must be initially empty. Upon return, the destination is
+ * immutable.
+ * @param dst The destination array. It must be empty.
+ * @param src The source array. It is not modified.
+ */
+ public static void snapshot(@NonNull WatchedSparseSetArray dst,
+ @NonNull WatchedSparseSetArray src) {
+ if (dst.size() != 0) {
+ throw new IllegalArgumentException("snapshot destination is not empty");
+ }
+ final int arraySize = src.size();
+ for (int i = 0; i < arraySize; i++) {
+ final ArraySet set = src.get(i);
+ final int setSize = set.size();
+ for (int j = 0; j < setSize; j++) {
+ dst.add(src.keyAt(i), set.valueAt(j));
+ }
+ }
+ dst.seal();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3200291ad263..654c4602fe39 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5635,6 +5635,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return false;
}
+ // We don't need to set the window to be relatively above IME if the IME is not visible.
+ // In case seeing the window is animating above the app transition layer because its
+ // relative layer is above the IME container on the display area but actually not necessary.
+ if (!getDisplayContent().getImeContainer().isVisible()) {
+ return false;
+ }
+
if (isChildWindow()) {
// If we are a child of the input method target we need this promotion.
if (getParentWindow().isImeLayeringTarget()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7d3c784e4f16..2b64be283b36 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1798,6 +1798,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@VisibleForTesting
DevicePolicyManagerService(Injector injector) {
+ DevicePolicyManager.disableGetKeyguardDisabledFeaturesCache();
+
mInjector = injector;
mContext = Objects.requireNonNull(injector.mContext);
mHandler = new Handler(Objects.requireNonNull(injector.getMyLooper()));
@@ -1889,6 +1891,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
performPolicyVersionUpgrade();
mDeviceManagementResourcesProvider.load();
+
+ // The binder caches are not enabled until the first invalidation.
+ invalidateBinderCaches();
+ }
+
+ /**
+ * Invalidate the binder API caches. The invalidation itself does not require any
+ * locking, but this specific call should be protected by getLockObject() to ensure
+ * that the invalidation is synchronous with cached queries, for those queries that
+ * are served under getLockObject().
+ */
+ static void invalidateBinderCaches() {
+ DevicePolicyManager.invalidateBinderCaches();
}
/**
@@ -3065,6 +3080,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
!mInjector.storageManagerIsFileBasedEncryptionEnabled())) {
sendChangedNotification(userHandle);
}
+ invalidateBinderCaches();
}
private void sendChangedNotification(int userHandle) {
@@ -3386,6 +3402,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (owner != null) {
mDeviceAdminServiceController.startServiceForOwner(
owner.getPackageName(), userId, actionForLog);
+ invalidateBinderCaches();
}
}
@@ -8411,6 +8428,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
/**
* Gets the disabled state for features in keyguard for the given admin,
* or the aggregate of all active admins if who is null.
+ * This API is cached: invalidate with invalidateBinderCaches().
*/
@Override
public int getKeyguardDisabledFeatures(ComponentName who, int userHandle, boolean parent) {
@@ -8620,6 +8638,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return true;
}
+ /**
+ * This API is cached: invalidate with invalidateBinderCaches().
+ */
@Override
public boolean hasDeviceOwner() {
final CallerIdentity caller = getCallerIdentity();
@@ -9395,6 +9416,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
});
}
+ /**
+ * This API is cached: invalidate with invalidateBinderCaches().
+ */
@Override
public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
@NonNull UserHandle userHandle) {
@@ -9460,6 +9484,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return UserHandle.USER_NULL;
}
+ /**
+ * This API is cached: invalidate with invalidateBinderCaches().
+ */
@Override
public boolean isOrganizationOwnedDeviceWithManagedProfile() {
if (!mHasFeature) {
@@ -13045,6 +13072,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
mConstants = loadConstants();
+ invalidateBinderCaches();
mInjector.binderWithCleanCallingIdentity(() -> {
final Intent intent = new Intent(
@@ -13205,6 +13233,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
.createEvent(DevicePolicyEnums.SEPARATE_PROFILE_CHALLENGE_CHANGED)
.setBoolean(isSeparateProfileChallengeEnabled(userId))
.write();
+ invalidateBinderCaches();
}
@Override
@@ -14628,6 +14657,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
+ /**
+ * This API is cached: invalidate with invalidateBinderCaches().
+ */
@Override
public CharSequence getDeviceOwnerOrganizationName() {
if (!mHasFeature) {
@@ -14642,6 +14674,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
+ /**
+ * This API is cached: invalidate with invalidateBinderCaches().
+ */
@Override
public CharSequence getOrganizationNameForUser(int userHandle) {
if (!mHasFeature) {
@@ -15727,6 +15762,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// available after next boot.
}
+ /**
+ * This API is cached: invalidate with invalidateBinderCaches().
+ */
@Override
public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin,
@NonNull String packageName) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index b0fdd723347f..37da7b428148 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -217,10 +217,9 @@ class Owners {
Slog.w(TAG, String.format("User %d has both DO and PO, which is not supported",
getDeviceOwnerUserId()));
}
- pushToPackageManagerLocked();
+
+ notifyChangeLocked();
pushToActivityTaskManagerLocked();
- pushToActivityManagerLocked();
- pushToAppOpsLocked();
for (ArrayMap.Entry<String, List<String>> entry :
mDeviceOwnerProtectedPackages.entrySet()) {
@@ -230,6 +229,21 @@ class Owners {
}
}
+ // Notify interested parties that things have changed. This does not notify the
+ // ActivityTaskManager.
+ private void notifyChangeLocked() {
+ pushToDevicePolicyManager();
+ pushToPackageManagerLocked();
+ pushToActivityManagerLocked();
+ pushToAppOpsLocked();
+ }
+
+ private void pushToDevicePolicyManager() {
+ // Not every change here must invalidate the DPM caches, but there is no harm in
+ // invalidating the caches unnecessarily, provided the invalidation is infrequent.
+ DevicePolicyManagerService.invalidateBinderCaches();
+ }
+
private void pushToPackageManagerLocked() {
final SparseArray<String> po = new SparseArray<>();
for (int i = mProfileOwners.size() - 1; i >= 0; i--) {
@@ -344,10 +358,8 @@ class Owners {
mDeviceOwnerUserId = userId;
mUserManagerInternal.setDeviceManaged(true);
- pushToPackageManagerLocked();
+ notifyChangeLocked();
pushToActivityTaskManagerLocked();
- pushToActivityManagerLocked();
- pushToAppOpsLocked();
}
}
@@ -364,10 +376,8 @@ class Owners {
mDeviceOwnerUserId = UserHandle.USER_NULL;
mUserManagerInternal.setDeviceManaged(false);
- pushToPackageManagerLocked();
+ notifyChangeLocked();
pushToActivityTaskManagerLocked();
- pushToActivityManagerLocked();
- pushToAppOpsLocked();
}
}
@@ -378,9 +388,7 @@ class Owners {
/* userRestrictionsMigrated =*/ true, /* remoteBugreportUri =*/ null,
/* remoteBugreportHash =*/ null, /* isOrganizationOwnedDevice =*/ false));
mUserManagerInternal.setUserManaged(userId, true);
- pushToPackageManagerLocked();
- pushToActivityManagerLocked();
- pushToAppOpsLocked();
+ notifyChangeLocked();
}
}
@@ -388,9 +396,7 @@ class Owners {
synchronized (mLock) {
mProfileOwners.remove(userId);
mUserManagerInternal.setUserManaged(userId, false);
- pushToPackageManagerLocked();
- pushToActivityManagerLocked();
- pushToAppOpsLocked();
+ notifyChangeLocked();
}
}
@@ -402,9 +408,7 @@ class Owners {
ownerInfo.remoteBugreportHash, /* isOrganizationOwnedDevice =*/
ownerInfo.isOrganizationOwnedDevice);
mProfileOwners.put(userId, newOwnerInfo);
- pushToPackageManagerLocked();
- pushToActivityManagerLocked();
- pushToAppOpsLocked();
+ notifyChangeLocked();
}
}
@@ -430,10 +434,8 @@ class Owners {
mDeviceOwnerProtectedPackages.put(
mDeviceOwner.packageName, previousProtectedPackages);
}
- pushToPackageManagerLocked();
+ notifyChangeLocked();
pushToActivityTaskManagerLocked();
- pushToActivityManagerLocked();
- pushToAppOpsLocked();
}
}
@@ -765,6 +767,7 @@ class Owners {
if (DEBUG) {
Log.d(TAG, "Writing to device owner file");
}
+ pushToDevicePolicyManager();
new DeviceOwnerReadWriter().writeToFileLocked();
}
}
@@ -774,6 +777,7 @@ class Owners {
if (DEBUG) {
Log.d(TAG, "Writing to profile owner file for user " + userId);
}
+ pushToDevicePolicyManager();
new ProfileOwnerReadWriter(userId).writeToFileLocked();
}
}
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 7017440a86bb..7b152247eb9c 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -34,7 +34,6 @@ import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverr
import com.android.server.testutils.TestHandler
import com.android.server.testutils.mock
import com.android.server.testutils.mockThrowOnUnmocked
-import com.android.server.testutils.spy
import com.android.server.testutils.whenever
import com.android.server.wm.ActivityTaskManagerInternal
import com.google.common.truth.Truth.assertThat
@@ -46,13 +45,9 @@ import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.intThat
-import org.mockito.Mockito.never
import org.mockito.Mockito.same
-import org.mockito.Mockito.verify
import org.testng.Assert.assertThrows
import java.io.File
import java.util.UUID
@@ -365,7 +360,7 @@ class PackageManagerComponentLabelIconOverrideTest {
val mockActivityTaskManager: ActivityTaskManagerInternal = mockThrowOnUnmocked {
whenever(this.isCallerRecents(anyInt())) { false }
}
- val mockAppsFilter: AppsFilter = mockThrowOnUnmocked {
+ val mockAppsFilter: AppsFilterImpl = mockThrowOnUnmocked {
whenever(this.shouldFilterApplication(anyInt(), any<PackageSetting>(),
any<PackageSetting>(), anyInt())) { false }
whenever(this.snapshot()) { this@mockThrowOnUnmocked }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
new file mode 100644
index 000000000000..cf6c82f23730
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -0,0 +1,195 @@
+/*
+ * 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 com.android.server.pm
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Binder
+import com.android.server.testutils.any
+import com.android.server.testutils.eq
+import com.android.server.testutils.nullable
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(JUnit4::class)
+class DistractingPackageHelperTest : PackageHelperTestBase() {
+
+ lateinit var distractingPackageHelper: DistractingPackageHelper
+
+ override fun setup() {
+ super.setup()
+ distractingPackageHelper = DistractingPackageHelper(
+ pms, rule.mocks().injector, broadcastHelper, suspendPackageHelper)
+ }
+
+ @Test
+ fun setDistractingPackageRestrictionsAsUser() {
+ val unactionedPackages = distractingPackageHelper.setDistractingPackageRestrictionsAsUser(
+ pms.snapshotComputer(), packagesToChange,
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, deviceOwnerUid)
+ testHandler.flush()
+
+ verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
+ verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
+ nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
+ nullable(), nullable(), nullable())
+
+ val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
+ assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ assertThat(distractionFlags).isEqualTo(PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
+ assertThat(unactionedPackages).isEmpty()
+ }
+
+ @Test
+ fun setDistractingPackageRestrictionsAsUser_setSameDistractionRestrictionTwice() {
+ distractingPackageHelper.setDistractingPackageRestrictionsAsUser(
+ pms.snapshotComputer(), packagesToChange,
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, deviceOwnerUid)
+ testHandler.flush()
+ clearInvocations(pms)
+ clearInvocations(broadcastHelper)
+
+ val unactionedPackages = distractingPackageHelper.setDistractingPackageRestrictionsAsUser(
+ pms.snapshotComputer(), packagesToChange,
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, deviceOwnerUid)
+ testHandler.flush()
+ verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
+ verify(broadcastHelper, never()).sendPackageBroadcast(
+ eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ assertThat(unactionedPackages).isEmpty()
+ }
+
+ @Test
+ fun setDistractingPackageRestrictionsAsUser_emptyPackageName() {
+ var unactionedPackages = distractingPackageHelper.setDistractingPackageRestrictionsAsUser(
+ pms.snapshotComputer(), null /* packageNames */,
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, deviceOwnerUid)
+ assertThat(unactionedPackages).isNull()
+
+ unactionedPackages = distractingPackageHelper.setDistractingPackageRestrictionsAsUser(
+ pms.snapshotComputer(), arrayOfNulls(0) /* packageNames */,
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, deviceOwnerUid)
+ assertThat(unactionedPackages).isEmpty()
+ }
+
+ @Test
+ fun setDistractingPackageRestrictionsAsUser_callerIsNotAllowed() {
+ val unactionedPackages = distractingPackageHelper.setDistractingPackageRestrictionsAsUser(
+ pms.snapshotComputer(), arrayOf(TEST_PACKAGE_1),
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, Binder.getCallingUid())
+
+ assertThat(unactionedPackages).asList().hasSize(1)
+ assertThat(unactionedPackages).asList().contains(TEST_PACKAGE_1)
+ }
+
+ @Test
+ fun setDistractingPackageRestrictionsAsUser_setCallerItself() {
+ val unactionedPackages = distractingPackageHelper.setDistractingPackageRestrictionsAsUser(
+ pms.snapshotComputer(), arrayOf(DEVICE_OWNER_PACKAGE),
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, Binder.getCallingUid())
+
+ assertThat(unactionedPackages).asList().hasSize(1)
+ assertThat(unactionedPackages).asList().contains(DEVICE_OWNER_PACKAGE)
+ }
+
+ @Test
+ fun setDistractingPackageRestrictionsAsUser_nonexistentPackage() {
+ val unactionedPackages = distractingPackageHelper.setDistractingPackageRestrictionsAsUser(
+ pms.snapshotComputer(), arrayOf(NONEXISTENT_PACKAGE),
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, deviceOwnerUid)
+
+ assertThat(unactionedPackages).asList().hasSize(1)
+ assertThat(unactionedPackages).asList().contains(NONEXISTENT_PACKAGE)
+ }
+
+ @Test
+ fun setDistractingPackageRestrictionsAsUser_setKnownPackages() {
+ val knownPackages = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE,
+ INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE,
+ PERMISSION_CONTROLLER_PACKAGE)
+ val unactionedPackages = distractingPackageHelper.setDistractingPackageRestrictionsAsUser(
+ pms.snapshotComputer(), knownPackages,
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, deviceOwnerUid)
+
+ assertThat(unactionedPackages.size).isEqualTo(knownPackages.size)
+ for (pkg in knownPackages) {
+ assertThat(unactionedPackages).asList().contains(pkg)
+ }
+ }
+
+ @Test
+ fun removeDistractingPackageRestrictions() {
+ distractingPackageHelper.setDistractingPackageRestrictionsAsUser(
+ pms.snapshotComputer(), packagesToChange,
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, deviceOwnerUid)
+ testHandler.flush()
+ clearInvocations(pms)
+ clearInvocations(broadcastHelper)
+
+ distractingPackageHelper.removeDistractingPackageRestrictions(pms.snapshotComputer(),
+ packagesToChange, TEST_USER_ID)
+ testHandler.flush()
+
+ verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
+ verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
+ nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
+ nullable(), nullable(), nullable())
+ val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
+ assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ assertThat(distractionFlags).isEqualTo(PackageManager.RESTRICTION_NONE)
+ }
+
+ @Test
+ fun removeDistractingPackageRestrictions_notDistractingPackage() {
+ distractingPackageHelper.removeDistractingPackageRestrictions(pms.snapshotComputer(),
+ arrayOf(TEST_PACKAGE_1), TEST_USER_ID)
+ testHandler.flush()
+
+ verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
+ verify(broadcastHelper, never()).sendPackageBroadcast(eq(
+ Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
+ nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ }
+
+ @Test
+ fun removeDistractingPackageRestrictions_emptyPackageName() {
+ distractingPackageHelper.removeDistractingPackageRestrictions(pms.snapshotComputer(),
+ null /* packagesToChange */, TEST_USER_ID)
+ testHandler.flush()
+ verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
+ verify(broadcastHelper, never()).sendPackageBroadcast(eq(
+ Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
+ nullable(), nullable(), any(), nullable(), nullable(), nullable())
+
+ distractingPackageHelper.removeDistractingPackageRestrictions(pms.snapshotComputer(),
+ arrayOfNulls(0), TEST_USER_ID)
+ testHandler.flush()
+ verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
+ verify(broadcastHelper, never()).sendPackageBroadcast(eq(
+ Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
+ nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ }
+} \ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 0567f58b1bbe..353c8e22cceb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -194,7 +194,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
val packageParser: PackageParser2 = mock()
val keySetManagerService: KeySetManagerService = mock()
val packageAbiHelper: PackageAbiHelper = mock()
- val appsFilter: AppsFilter = mock {
+ val appsFilter: AppsFilterImpl = mock {
whenever(snapshot()) { this@mock }
}
val dexManager: DexManager = mock()
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
new file mode 100644
index 000000000000..bd012fc7837e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
@@ -0,0 +1,141 @@
+/*
+ * 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 com.android.server.pm
+
+import android.os.Build
+import android.os.Bundle
+import android.os.UserHandle
+import android.os.UserManager
+import com.android.server.pm.pkg.PackageStateInternal
+import com.android.server.testutils.TestHandler
+import com.android.server.testutils.any
+import com.android.server.testutils.eq
+import com.android.server.testutils.whenever
+import org.junit.Before
+import org.junit.Rule
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.spy
+import org.mockito.MockitoAnnotations
+
+open class PackageHelperTestBase {
+
+ companion object {
+ const val TEST_PACKAGE_1 = "com.android.test.package1"
+ const val TEST_PACKAGE_2 = "com.android.test.package2"
+ const val DEVICE_OWNER_PACKAGE = "com.android.test.owner"
+ const val NONEXISTENT_PACKAGE = "com.android.test.nonexistent"
+ const val DEVICE_ADMIN_PACKAGE = "com.android.test.known.device.admin"
+ const val DEFAULT_HOME_PACKAGE = "com.android.test.known.home"
+ const val DIALER_PACKAGE = "com.android.test.known.dialer"
+ const val INSTALLER_PACKAGE = "com.android.test.known.installer"
+ const val UNINSTALLER_PACKAGE = "com.android.test.known.uninstaller"
+ const val VERIFIER_PACKAGE = "com.android.test.known.verifier"
+ const val PERMISSION_CONTROLLER_PACKAGE = "com.android.test.known.permission"
+ const val TEST_USER_ID = 0
+ }
+
+ lateinit var pms: PackageManagerService
+ lateinit var suspendPackageHelper: SuspendPackageHelper
+ lateinit var testHandler: TestHandler
+ lateinit var defaultAppProvider: DefaultAppProvider
+ lateinit var packageSetting1: PackageStateInternal
+ lateinit var packageSetting2: PackageStateInternal
+ lateinit var ownerSetting: PackageStateInternal
+ lateinit var packagesToChange: Array<String>
+ lateinit var uidsToChange: IntArray
+
+ @Mock
+ lateinit var broadcastHelper: BroadcastHelper
+ @Mock
+ lateinit var protectedPackages: ProtectedPackages
+
+ @Captor
+ lateinit var bundleCaptor: ArgumentCaptor<Bundle>
+
+ @Rule
+ @JvmField
+ val rule = MockSystemRule()
+ var deviceOwnerUid = 0
+
+ @Before
+ @Throws(Exception::class)
+ open fun setup() {
+ MockitoAnnotations.initMocks(this)
+ rule.system().stageNominalSystemState()
+ pms = spy(createPackageManagerService(
+ TEST_PACKAGE_1, TEST_PACKAGE_2, DEVICE_OWNER_PACKAGE, DEVICE_ADMIN_PACKAGE,
+ DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, INSTALLER_PACKAGE, UNINSTALLER_PACKAGE,
+ VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE))
+ suspendPackageHelper = SuspendPackageHelper(
+ pms, rule.mocks().injector, broadcastHelper, protectedPackages)
+ defaultAppProvider = rule.mocks().defaultAppProvider
+ testHandler = rule.mocks().handler
+ packageSetting1 = pms.snapshotComputer().getPackageStateInternal(TEST_PACKAGE_1)!!
+ packageSetting2 = pms.snapshotComputer().getPackageStateInternal(TEST_PACKAGE_2)!!
+ ownerSetting = pms.snapshotComputer().getPackageStateInternal(DEVICE_OWNER_PACKAGE)!!
+ deviceOwnerUid = UserHandle.getUid(TEST_USER_ID, ownerSetting.appId)
+ packagesToChange = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ uidsToChange = intArrayOf(packageSetting1.appId, packageSetting2.appId)
+
+ whenever(protectedPackages.getDeviceOwnerOrProfileOwnerPackage(eq(TEST_USER_ID)))
+ .thenReturn(DEVICE_OWNER_PACKAGE)
+ whenever(rule.mocks().userManagerService.hasUserRestriction(
+ eq(UserManager.DISALLOW_APPS_CONTROL), eq(TEST_USER_ID))).thenReturn(true)
+ whenever(rule.mocks().userManagerService.hasUserRestriction(
+ eq(UserManager.DISALLOW_UNINSTALL_APPS), eq(TEST_USER_ID))).thenReturn(true)
+ mockKnownPackages(pms)
+ }
+
+ private fun mockKnownPackages(pms: PackageManagerService) {
+ Mockito.doAnswer { it.arguments[0] == DEVICE_ADMIN_PACKAGE }.`when`(pms)
+ .isPackageDeviceAdmin(any(), any())
+ Mockito.doReturn(DEFAULT_HOME_PACKAGE).`when`(defaultAppProvider)
+ .getDefaultHome(eq(TEST_USER_ID))
+ Mockito.doReturn(DIALER_PACKAGE).`when`(defaultAppProvider)
+ .getDefaultDialer(eq(TEST_USER_ID))
+ Mockito.doReturn(arrayOf(INSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
+ any(), eq(KnownPackages.PACKAGE_INSTALLER), eq(TEST_USER_ID))
+ Mockito.doReturn(arrayOf(UNINSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
+ any(), eq(KnownPackages.PACKAGE_UNINSTALLER), eq(TEST_USER_ID))
+ Mockito.doReturn(arrayOf(VERIFIER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
+ any(), eq(KnownPackages.PACKAGE_VERIFIER), eq(TEST_USER_ID))
+ Mockito.doReturn(arrayOf(PERMISSION_CONTROLLER_PACKAGE)).`when`(pms)
+ .getKnownPackageNamesInternal(any(),
+ eq(KnownPackages.PACKAGE_PERMISSION_CONTROLLER), eq(TEST_USER_ID))
+ }
+
+ private fun createPackageManagerService(vararg stageExistingPackages: String):
+ PackageManagerService {
+ stageExistingPackages.forEach {
+ rule.system().stageScanExistingPackage(it, 1L,
+ rule.system().dataAppDirectory)
+ }
+ var pms = PackageManagerService(rule.mocks().injector,
+ false /* coreOnly */,
+ false /* factoryTest */,
+ MockSystem.DEFAULT_VERSION_INFO.fingerprint,
+ false /* isEngBuild */,
+ false /* isUserDebugBuild */,
+ Build.VERSION_CODES.CUR_DEVELOPMENT,
+ Build.VERSION.INCREMENTAL)
+ rule.system().validateFinalState()
+ return pms
+ }
+} \ No newline at end of file
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 9774af31950a..3ba9ca591fb3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -19,106 +19,25 @@ package com.android.server.pm
import android.content.Intent
import android.content.pm.SuspendDialogInfo
import android.os.Binder
-import android.os.Build
-import android.os.Bundle
import android.os.PersistableBundle
-import android.os.UserHandle
-import android.os.UserManager
import android.util.ArrayMap
import android.util.SparseArray
-import com.android.server.pm.KnownPackages
import com.android.server.pm.pkg.PackageStateInternal
-import com.android.server.testutils.TestHandler
import com.android.server.testutils.any
import com.android.server.testutils.eq
import com.android.server.testutils.nullable
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.argThat
-import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
-class SuspendPackageHelperTest {
-
- companion object {
- const val TEST_PACKAGE_1 = "com.android.test.package1"
- const val TEST_PACKAGE_2 = "com.android.test.package2"
- const val DEVICE_OWNER_PACKAGE = "com.android.test.owner"
- const val NONEXISTENT_PACKAGE = "com.android.test.nonexistent"
- const val DEVICE_ADMIN_PACKAGE = "com.android.test.known.device.admin"
- const val DEFAULT_HOME_PACKAGE = "com.android.test.known.home"
- const val DIALER_PACKAGE = "com.android.test.known.dialer"
- const val INSTALLER_PACKAGE = "com.android.test.known.installer"
- const val UNINSTALLER_PACKAGE = "com.android.test.known.uninstaller"
- const val VERIFIER_PACKAGE = "com.android.test.known.verifier"
- const val PERMISSION_CONTROLLER_PACKAGE = "com.android.test.known.permission"
- const val TEST_USER_ID = 0
- }
-
- lateinit var pms: PackageManagerService
- lateinit var suspendPackageHelper: SuspendPackageHelper
- lateinit var testHandler: TestHandler
- lateinit var defaultAppProvider: DefaultAppProvider
- lateinit var packageSetting1: PackageStateInternal
- lateinit var packageSetting2: PackageStateInternal
- lateinit var ownerSetting: PackageStateInternal
- lateinit var packagesToSuspend: Array<String>
- lateinit var uidsToSuspend: IntArray
-
- @Mock
- lateinit var broadcastHelper: BroadcastHelper
- @Mock
- lateinit var protectedPackages: ProtectedPackages
-
- @Captor
- lateinit var bundleCaptor: ArgumentCaptor<Bundle>
-
- @Rule
- @JvmField
- val rule = MockSystemRule()
- var deviceOwnerUid = 0
-
- @Before
- @Throws(Exception::class)
- fun setup() {
- MockitoAnnotations.initMocks(this)
- rule.system().stageNominalSystemState()
- pms = spy(createPackageManagerService(
- TEST_PACKAGE_1, TEST_PACKAGE_2, DEVICE_OWNER_PACKAGE, DEVICE_ADMIN_PACKAGE,
- DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, INSTALLER_PACKAGE, UNINSTALLER_PACKAGE,
- VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE))
- suspendPackageHelper = SuspendPackageHelper(
- pms, rule.mocks().injector, broadcastHelper, protectedPackages)
- defaultAppProvider = rule.mocks().defaultAppProvider
- testHandler = rule.mocks().handler
- packageSetting1 = pms.snapshotComputer().getPackageStateInternal(TEST_PACKAGE_1)!!
- packageSetting2 = pms.snapshotComputer().getPackageStateInternal(TEST_PACKAGE_2)!!
- ownerSetting = pms.snapshotComputer().getPackageStateInternal(DEVICE_OWNER_PACKAGE)!!
- deviceOwnerUid = UserHandle.getUid(TEST_USER_ID, ownerSetting.appId)
- packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
- uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId)
-
- whenever(protectedPackages.getDeviceOwnerOrProfileOwnerPackage(eq(TEST_USER_ID)))
- .thenReturn(DEVICE_OWNER_PACKAGE)
- whenever(rule.mocks().userManagerService.hasUserRestriction(
- eq(UserManager.DISALLOW_APPS_CONTROL), eq(TEST_USER_ID))).thenReturn(true)
- whenever(rule.mocks().userManagerService.hasUserRestriction(
- eq(UserManager.DISALLOW_UNINSTALL_APPS), eq(TEST_USER_ID))).thenReturn(true)
- mockKnownPackages(pms)
- }
+class SuspendPackageHelperTest : PackageHelperTestBase() {
@Test
fun setPackagesSuspended() {
@@ -388,7 +307,7 @@ class SuspendPackageHelperTest {
mockAllowList(packageSetting2, allowList(10001, 10002, 10003))
suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENDED, packagesToSuspend, uidsToSuspend, TEST_USER_ID)
+ Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
@@ -407,7 +326,7 @@ class SuspendPackageHelperTest {
mockAllowList(packageSetting2, allowList(10001, 10002, 10007))
suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENDED, packagesToSuspend, uidsToSuspend, TEST_USER_ID)
+ Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper, times(2)).sendPackageBroadcast(
any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
@@ -430,7 +349,7 @@ class SuspendPackageHelperTest {
mockAllowList(packageSetting2, null)
suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENDED, packagesToSuspend, uidsToSuspend, TEST_USER_ID)
+ Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper, times(2)).sendPackageBroadcast(
any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
@@ -450,7 +369,7 @@ class SuspendPackageHelperTest {
@Throws(Exception::class)
fun sendPackagesSuspendModifiedForUser() {
suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToSuspend, uidsToSuspend,
+ Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange,
TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(
@@ -475,40 +394,4 @@ class SuspendPackageHelperTest {
))
.thenReturn(list)
}
-
- private fun mockKnownPackages(pms: PackageManagerService) {
- Mockito.doAnswer { it.arguments[0] == DEVICE_ADMIN_PACKAGE }.`when`(pms)
- .isPackageDeviceAdmin(any(), any())
- Mockito.doReturn(DEFAULT_HOME_PACKAGE).`when`(defaultAppProvider)
- .getDefaultHome(eq(TEST_USER_ID))
- Mockito.doReturn(DIALER_PACKAGE).`when`(defaultAppProvider)
- .getDefaultDialer(eq(TEST_USER_ID))
- Mockito.doReturn(arrayOf(INSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
- any(), eq(KnownPackages.PACKAGE_INSTALLER), eq(TEST_USER_ID))
- Mockito.doReturn(arrayOf(UNINSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
- any(), eq(KnownPackages.PACKAGE_UNINSTALLER), eq(TEST_USER_ID))
- Mockito.doReturn(arrayOf(VERIFIER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
- any(), eq(KnownPackages.PACKAGE_VERIFIER), eq(TEST_USER_ID))
- Mockito.doReturn(arrayOf(PERMISSION_CONTROLLER_PACKAGE)).`when`(pms)
- .getKnownPackageNamesInternal(any(),
- eq(KnownPackages.PACKAGE_PERMISSION_CONTROLLER), eq(TEST_USER_ID))
- }
-
- private fun createPackageManagerService(vararg stageExistingPackages: String):
- PackageManagerService {
- stageExistingPackages.forEach {
- rule.system().stageScanExistingPackage(it, 1L,
- rule.system().dataAppDirectory)
- }
- var pms = PackageManagerService(rule.mocks().injector,
- false /*coreOnly*/,
- false /*factoryTest*/,
- MockSystem.DEFAULT_VERSION_INFO.fingerprint,
- false /*isEngBuild*/,
- false /*isUserDebugBuild*/,
- Build.VERSION_CODES.CUR_DEVELOPMENT,
- Build.VERSION.INCREMENTAL)
- rule.system().validateFinalState()
- return pms
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
index edacc165016f..9b56303244c0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
@@ -573,6 +573,18 @@ public class AccessibilitySecurityPolicyTest {
}
@Test
+ public void canRegisterService_isExternalService_returnFalse() {
+ final ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.applicationInfo = new ApplicationInfo();
+ serviceInfo.packageName = PACKAGE_NAME;
+ serviceInfo.name = AccessibilitySecurityPolicyTest.class.getSimpleName();
+ serviceInfo.permission = android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE;
+ serviceInfo.flags |= ServiceInfo.FLAG_EXTERNAL_SERVICE;
+
+ assertFalse(mA11ySecurityPolicy.canRegisterService(serviceInfo));
+ }
+
+ @Test
public void checkAccessibilityAccess_shouldCheckAppOps() {
final AccessibilityServiceInfo mockServiceInfo = Mockito.mock(
AccessibilityServiceInfo.class);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index a2b1c1cb1a49..f834b3438aa3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -40,6 +40,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
+import android.os.IpcDataCache;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -75,6 +76,10 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
@Before
public void setUp() throws Exception {
+ // Disable caches in this test process. This must happen early, since some of the
+ // following initialization steps invalidate caches.
+ IpcDataCache.disableForTestMode();
+
mContext = getContext();
// Make createContextAsUser to work.
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ef7aaca203f7..ea136da5f323 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -128,6 +128,7 @@ import android.net.wifi.WifiSsid;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
+import android.os.IpcDataCache;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -261,6 +262,10 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Before
public void setUp() throws Exception {
+ // Disable caches in this test process. This must happen early, since some of the
+ // following initialization steps invalidate caches.
+ IpcDataCache.disableForTestMode();
+
mContext = getContext();
mServiceContext = mContext;
mServiceContext.binder.callingUid = DpmMockContext.CALLER_UID;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index 743b25f5c2b4..69aaf010d0e0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -32,6 +32,7 @@ import android.app.admin.DnsEvent;
import android.app.admin.NetworkEvent;
import android.content.Intent;
import android.os.Bundle;
+import android.os.IpcDataCache;
import android.os.Message;
import android.os.Parcel;
import android.os.SystemClock;
@@ -56,6 +57,10 @@ public class NetworkEventTest extends DpmTestBase {
@Before
public void setUp() throws Exception {
+ // Disable caches in this test process. This must happen early, since some of the
+ // following initialization steps invalidate caches.
+ IpcDataCache.disableForTestMode();
+
mSpiedDpmMockContext = spy(mMockContext);
mSpiedDpmMockContext.callerPermissions.add(
android.Manifest.permission.MANAGE_DEVICE_ADMINS);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 9a5254d3e00d..d1706f857c13 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import android.content.ComponentName;
+import android.os.IpcDataCache;
import android.os.UserHandle;
import android.test.suitebuilder.annotation.SmallTest;
@@ -33,6 +34,7 @@ import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.Owners
import com.google.android.collect.Lists;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,6 +55,13 @@ public class OwnersTest extends DpmTestBase {
private static final List<String> DEVICE_OWNER_PROTECTED_PACKAGES =
Lists.newArrayList("package_1", "package_2");
+ @Before
+ public void setUp() throws Exception {
+ // Disable caches in this test process. This must happen early, since some of the
+ // following initialization steps invalidate caches.
+ IpcDataCache.disableForTestMode();
+ }
+
@Test
public void testUpgrade01() throws Exception {
getServices().addUsers(10, 11, 20, 21);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
index b41a5311c89f..834a514b978a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
@@ -28,6 +28,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.os.IpcDataCache;
import android.os.Parcel;
import android.os.UserHandle;
import android.util.TypedXmlPullParser;
@@ -128,6 +129,10 @@ public class PolicyVersionUpgraderTest extends DpmTestBase {
@Before
public void setUp() {
+ // Disable caches in this test process. This must happen early, since some of the
+ // following initialization steps invalidate caches.
+ IpcDataCache.disableForTestMode();
+
mProvider = new FakePolicyUpgraderDataProvider();
mUpgrader = new PolicyVersionUpgrader(mProvider);
mDataDir = new File(mRealTestContext.getCacheDir(), "test-data");
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 6fc3354f07e7..86130daf4aac 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -2033,4 +2033,32 @@ public class HdmiCecLocalDevicePlaybackTest {
mTestLooper.dispatchAll();
assertThat(mPowerManager.isInteractive()).isFalse();
}
+
+ @Test
+ public void onHotplugClearsDevices() {
+ mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .isEmpty();
+ // Add a device to the network and assert that this device is included in the list of
+ // devices.
+ HdmiDeviceInfo infoPlayback = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(Constants.ADDR_PLAYBACK_3)
+ .setPhysicalAddress(0x1000)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+ .setVendorId(0x1000)
+ .setDisplayName("Playback 3")
+ .build();
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .hasSize(1);
+
+ // HAL detects a hotplug out. Assert that this device gets removed from the list of devices.
+ mHdmiControlService.onHotplug(PORT_1, false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .isEmpty();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index d104871f488a..df4aa5dac9df 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -19,6 +19,7 @@ import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_RECORDER_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -53,6 +54,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.TimeUnit;
@SmallTest
@@ -61,6 +63,7 @@ import java.util.concurrent.TimeUnit;
/** Tests for {@link HdmiCecLocalDeviceTv} class. */
public class HdmiCecLocalDeviceTvTest {
private static final int TIMEOUT_MS = HdmiConfig.TIMEOUT_MS + 1;
+ private static final int PORT_1 = 1;
private static final String[] SADS_NOT_TO_QUERY = new String[]{
HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1,
@@ -90,6 +93,25 @@ public class HdmiCecLocalDeviceTvTest {
private int mTvPhysicalAddress;
private int mTvLogicalAddress;
private boolean mWokenUp;
+ private List<DeviceEventListener> mDeviceEventListeners = new ArrayList<>();
+
+ private class DeviceEventListener {
+ private HdmiDeviceInfo mDevice;
+ private int mStatus;
+
+ DeviceEventListener(HdmiDeviceInfo device, int status) {
+ this.mDevice = device;
+ this.mStatus = status;
+ }
+
+ int getStatus() {
+ return mStatus;
+ }
+
+ HdmiDeviceInfo getDeviceInfo() {
+ return mDevice;
+ }
+ }
@Mock
private AudioManager mAudioManager;
@@ -133,6 +155,11 @@ public class HdmiCecLocalDeviceTvTest {
AudioManager getAudioManager() {
return mAudioManager;
}
+
+ @Override
+ void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
+ mDeviceEventListeners.add(new DeviceEventListener(device, status));
+ }
};
mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService);
@@ -609,4 +636,147 @@ public class HdmiCecLocalDeviceTvTest {
ADDR_TV, ADDR_PLAYBACK_1);
assertThat(mNativeWrapper.getResultMessages()).contains(givePhysicalAddress);
}
+
+ @Test
+ public void hotplugDetectionActionClearsDevices() {
+ mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .isEmpty();
+ // Add a device to the network and assert that this device is included in the list of
+ // devices.
+ HdmiDeviceInfo infoPlayback = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(Constants.ADDR_PLAYBACK_2)
+ .setPhysicalAddress(0x1000)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+ .setVendorId(0x1000)
+ .setDisplayName("Playback 2")
+ .build();
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .hasSize(1);
+ mDeviceEventListeners.clear();
+ assertThat(mDeviceEventListeners.size()).isEqualTo(0);
+
+ // HAL detects a hotplug out. Assert that this device stays in the list of devices.
+ mHdmiControlService.onHotplug(PORT_1, false);
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .hasSize(1);
+ assertThat(mDeviceEventListeners).isEmpty();
+ mTestLooper.dispatchAll();
+ // Make the device not acknowledge the poll message sent by the HotplugDetectionAction.
+ // Assert that this device is removed from the list of devices.
+ mNativeWrapper.setPollAddressResponse(Constants.ADDR_PLAYBACK_2, SendMessageResult.NACK);
+ for (int pollCount = 0; pollCount < HotplugDetectionAction.TIMEOUT_COUNT; pollCount++) {
+ mTestLooper.moveTimeForward(HotplugDetectionAction.POLLING_INTERVAL_MS_FOR_TV);
+ mTestLooper.dispatchAll();
+ }
+
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .isEmpty();
+ assertThat(mDeviceEventListeners.size()).isEqualTo(1);
+ assertThat(mDeviceEventListeners.get(0).getStatus())
+ .isEqualTo(HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ HdmiDeviceInfo removedDeviceInfo = mDeviceEventListeners.get(0).getDeviceInfo();
+ assertThat(removedDeviceInfo.getPortId()).isEqualTo(PORT_1);
+ assertThat(removedDeviceInfo.getLogicalAddress()).isEqualTo(Constants.ADDR_PLAYBACK_2);
+ assertThat(removedDeviceInfo.getPhysicalAddress()).isEqualTo(0x1000);
+ assertThat(removedDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_PLAYBACK);
+ }
+
+ @Test
+ public void hotplugDetectionActionClearsDevices_AudioSystem() {
+ mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .isEmpty();
+ // Add a device to the network and assert that this device is included in the list of
+ // devices.
+ HdmiDeviceInfo infoAudioSystem = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(ADDR_AUDIO_SYSTEM)
+ .setPhysicalAddress(0x1000)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+ .setVendorId(0x1000)
+ .setDisplayName("Audio System")
+ .build();
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoAudioSystem);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .hasSize(1);
+ mDeviceEventListeners.clear();
+ assertThat(mDeviceEventListeners.size()).isEqualTo(0);
+
+ // HAL detects a hotplug out. Assert that this device stays in the list of devices.
+ mHdmiControlService.onHotplug(PORT_1, false);
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .hasSize(1);
+ assertThat(mDeviceEventListeners).isEmpty();
+ mTestLooper.dispatchAll();
+ // Make the device not acknowledge the poll message sent by the HotplugDetectionAction.
+ // Assert that this device is removed from the list of devices.
+ mNativeWrapper.setPollAddressResponse(ADDR_AUDIO_SYSTEM, SendMessageResult.NACK);
+ for (int pollCount = 0; pollCount < HotplugDetectionAction.TIMEOUT_COUNT; pollCount++) {
+ mTestLooper.moveTimeForward(HotplugDetectionAction.POLLING_INTERVAL_MS_FOR_TV);
+ mTestLooper.dispatchAll();
+ }
+
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .isEmpty();
+ assertThat(mDeviceEventListeners.size()).isEqualTo(1);
+ assertThat(mDeviceEventListeners.get(0).getStatus())
+ .isEqualTo(HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ HdmiDeviceInfo removedDeviceInfo = mDeviceEventListeners.get(0).getDeviceInfo();
+ assertThat(removedDeviceInfo.getPortId()).isEqualTo(PORT_1);
+ assertThat(removedDeviceInfo.getLogicalAddress()).isEqualTo(Constants.ADDR_AUDIO_SYSTEM);
+ assertThat(removedDeviceInfo.getPhysicalAddress()).isEqualTo(0x1000);
+ assertThat(removedDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ }
+
+ @Test
+ public void listenerInvokedIfPhysicalAddressReported() {
+ mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .isEmpty();
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .hasSize(1);
+ assertThat(mDeviceEventListeners.size()).isEqualTo(1);
+ assertThat(mDeviceEventListeners.get(0).getStatus())
+ .isEqualTo(HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ }
+
+ @Test
+ public void listenerNotInvokedIfPhysicalAddressUnknown() {
+ mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .isEmpty();
+ HdmiCecMessage setOsdName = HdmiCecMessageBuilder.buildSetOsdNameCommand(
+ ADDR_PLAYBACK_2, ADDR_TV, "Playback 2");
+ mNativeWrapper.onCecMessage(setOsdName);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .hasSize(1);
+ assertThat(mDeviceEventListeners).isEmpty();
+
+ // When the device reports its physical address, the listener eventually is invoked.
+ HdmiCecMessage reportPhysicalAddress =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .hasSize(1);
+ assertThat(mDeviceEventListeners.size()).isEqualTo(1);
+ assertThat(mDeviceEventListeners.get(0).getStatus())
+ .isEqualTo(HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index 42fa32cabc06..03532ae1cb1f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -102,6 +102,8 @@ public class HdmiCecNetworkTest {
new HdmiPortInfo(5, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
mNativeWrapper.setPortInfo(mHdmiPortInfo);
mHdmiCecNetwork.initPortInfo();
+
+ mHdmiCecNetwork = mHdmiControlService.getHdmiCecNetwork();
}
@Test
@@ -142,7 +144,24 @@ public class HdmiCecNetworkTest {
}
@Test
+ public void physicalAddressToPort_localDevice_weAreSourceDevice() {
+ mNativeWrapper.setPhysicalAddress(0x2000);
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2000))
+ .isEqualTo(Constants.CEC_SWITCH_HOME);
+ }
+
+ @Test
+ public void physicalAddressToPort_localDevice_weAreTv() {
+ mNativeWrapper.setPhysicalAddress(0x0000);
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x0000))
+ .isEqualTo(Constants.CEC_SWITCH_HOME);
+ }
+
+ @Test
public void localDevices_verifyOne_tv() {
+ mHdmiCecNetwork.clearLocalDevices();
mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_TV,
new HdmiCecLocalDeviceTv(mHdmiControlService));
@@ -155,6 +174,7 @@ public class HdmiCecNetworkTest {
@Test
public void localDevices_verifyOne_playback() {
+ mHdmiCecNetwork.clearLocalDevices();
mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK,
new HdmiCecLocalDevicePlayback(mHdmiControlService));
@@ -177,27 +197,26 @@ public class HdmiCecNetworkTest {
assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
Constants.INVALID_PHYSICAL_ADDRESS);
- assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_PLAYBACK);
assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
HdmiUtils.getDefaultDeviceName(logicalAddress));
assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN);
assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_UNKNOWN);
- assertThat(mDeviceEventListenerStatuses).containsExactly(
- HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ assertThat(mDeviceEventListenerStatuses).isEmpty();
}
@Test
public void cecDevices_tracking_logicalAddressOnly_doesntNotifyAgain() throws Exception {
int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int physicalAddress = 0x1000;
mHdmiCecNetwork.handleCecMessage(
- HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, physicalAddress));
mHdmiCecNetwork.handleCecMessage(
- HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, physicalAddress));
- assertThat(mDeviceEventListenerStatuses).containsExactly(
- HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ assertThat(mDeviceEventListenerStatuses).isEmpty();
}
@Test
@@ -221,6 +240,9 @@ public class HdmiCecNetworkTest {
assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN);
assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_UNKNOWN);
+
+ assertThat(mDeviceEventListenerStatuses).containsExactly(
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
}
@Test
@@ -238,11 +260,10 @@ public class HdmiCecNetworkTest {
physicalAddress, type));
- // ADD for logical address first detected
- // UPDATE for updating device with physical address
+ // ADD for physical address first detected
+ // no UPDATE, since physical address didn't change
assertThat(mDeviceEventListenerStatuses).containsExactly(
- HdmiControlManager.DEVICE_EVENT_ADD_DEVICE,
- HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
}
@Test
@@ -259,11 +280,13 @@ public class HdmiCecNetworkTest {
assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
Constants.INVALID_PHYSICAL_ADDRESS);
- assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_PLAYBACK);
assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN);
assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
HdmiUtils.getDefaultDeviceName(logicalAddress));
assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
+
+ assertThat(mDeviceEventListenerStatuses).isEmpty();
}
@Test
@@ -280,11 +303,13 @@ public class HdmiCecNetworkTest {
assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
Constants.INVALID_PHYSICAL_ADDRESS);
- assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_PLAYBACK);
assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN);
assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName);
assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_UNKNOWN);
+
+ assertThat(mDeviceEventListenerStatuses).isEmpty();
}
@Test
@@ -300,12 +325,14 @@ public class HdmiCecNetworkTest {
assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
Constants.INVALID_PHYSICAL_ADDRESS);
- assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_PLAYBACK);
assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
HdmiUtils.getDefaultDeviceName(logicalAddress));
assertThat(cecDeviceInfo.getVendorId()).isEqualTo(vendorId);
assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_UNKNOWN);
+
+ assertThat(mDeviceEventListenerStatuses).isEmpty();
}
@Test
@@ -364,12 +391,10 @@ public class HdmiCecNetworkTest {
assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(updatedPhysicalAddress);
assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type);
- // ADD for logical address first detected
- // UPDATE for updating device with physical address
+ // ADD for physical address first detected
// UPDATE for updating device with new physical address
assertThat(mDeviceEventListenerStatuses).containsExactly(
HdmiControlManager.DEVICE_EVENT_ADD_DEVICE,
- HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE,
HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
}
@@ -431,7 +456,7 @@ public class HdmiCecNetworkTest {
assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
Constants.INVALID_PHYSICAL_ADDRESS);
- assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_PLAYBACK);
assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
HdmiUtils.getDefaultDeviceName(logicalAddress));
assertThat(cecDeviceInfo.getVendorId()).isEqualTo(updatedVendorId);
@@ -453,9 +478,8 @@ public class HdmiCecNetworkTest {
assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).isEmpty();
- assertThat(mDeviceEventListenerStatuses).containsExactly(
- HdmiControlManager.DEVICE_EVENT_ADD_DEVICE,
- HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ // Physical address never got reported, so no listeners are triggered
+ assertThat(mDeviceEventListenerStatuses).isEmpty();
}
@Test
@@ -472,7 +496,7 @@ public class HdmiCecNetworkTest {
assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
Constants.INVALID_PHYSICAL_ADDRESS);
- assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_PLAYBACK);
assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN);
assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
HdmiUtils.getDefaultDeviceName(logicalAddress));
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
index b72b8d2ec6e8..d8f4349b95bf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
@@ -77,7 +77,7 @@ import java.util.concurrent.Executor;
@Presubmit
@RunWith(JUnit4.class)
-public class AppsFilterTest {
+public class AppsFilterImplTest {
private static final int DUMMY_CALLING_APPID = 10345;
private static final int DUMMY_TARGET_APPID = 10556;
@@ -98,9 +98,9 @@ public class AppsFilterTest {
}
@Mock
- AppsFilter.FeatureConfig mFeatureConfigMock;
+ AppsFilterImpl.FeatureConfig mFeatureConfigMock;
@Mock
- AppsFilter.StateProvider mStateProvider;
+ AppsFilterImpl.StateProvider mStateProvider;
@Mock
Executor mMockExecutor;
@Mock
@@ -204,11 +204,11 @@ public class AppsFilterTest {
MockitoAnnotations.initMocks(this);
doAnswer(invocation -> {
- ((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
+ ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
.currentState(mExisting, USER_INFO_LIST);
return new Object();
}).when(mStateProvider)
- .runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
+ .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
@@ -218,14 +218,14 @@ public class AppsFilterTest {
when(mFeatureConfigMock.isGloballyEnabled()).thenReturn(true);
when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))).thenAnswer(
(Answer<Boolean>) invocation ->
- ((AndroidPackage)invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion()
+ ((AndroidPackage) invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion()
>= Build.VERSION_CODES.R);
}
@Test
public void testSystemReadyPropogates() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -236,8 +236,8 @@ public class AppsFilterTest {
@Test
public void testQueriesAction_FilterMatches() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -259,8 +259,8 @@ public class AppsFilterTest {
}
@Test
public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -308,8 +308,8 @@ public class AppsFilterTest {
@Test
public void testQueriesProvider_FilterMatches() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -333,8 +333,8 @@ public class AppsFilterTest {
@Test
public void testOnUserUpdated_FilterMatches() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
@@ -356,11 +356,11 @@ public class AppsFilterTest {
// adds new user
doAnswer(invocation -> {
- ((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
+ ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
.currentState(mExisting, USER_INFO_LIST_WITH_ADDED);
return new Object();
}).when(mStateProvider)
- .runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
+ .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
appsFilter.onUserCreated(ADDED_USER);
for (int subjectUserId : USER_ARRAY_WITH_ADDED) {
@@ -373,11 +373,11 @@ public class AppsFilterTest {
// delete user
doAnswer(invocation -> {
- ((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
+ ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
.currentState(mExisting, USER_INFO_LIST);
return new Object();
}).when(mStateProvider)
- .runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
+ .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
appsFilter.onUserDeleted(ADDED_USER);
for (int subjectUserId : USER_ARRAY) {
@@ -391,8 +391,8 @@ public class AppsFilterTest {
@Test
public void testQueriesDifferentProvider_Filters() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -416,8 +416,8 @@ public class AppsFilterTest {
@Test
public void testQueriesProviderWithSemiColon_FilterMatches() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -435,8 +435,8 @@ public class AppsFilterTest {
@Test
public void testQueriesAction_NoMatchingAction_Filters() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -452,8 +452,8 @@ public class AppsFilterTest {
@Test
public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -473,8 +473,8 @@ public class AppsFilterTest {
@Test
public void testNoQueries_Filters() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -490,7 +490,7 @@ public class AppsFilterTest {
@Test
public void testNoUsesLibrary_Filters() throws Exception {
- final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
+ final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor, mMockPmInternal);
@@ -516,7 +516,7 @@ public class AppsFilterTest {
@Test
public void testUsesLibrary_DoesntFilter() throws Exception {
- final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
+ final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor, mMockPmInternal);
@@ -543,7 +543,7 @@ public class AppsFilterTest {
@Test
public void testUsesOptionalLibrary_DoesntFilter() throws Exception {
- final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
+ final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor, mMockPmInternal);
@@ -570,7 +570,7 @@ public class AppsFilterTest {
@Test
public void testUsesLibrary_ShareUid_DoesntFilter() throws Exception {
- final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
+ final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor, mMockPmInternal);
@@ -602,8 +602,8 @@ public class AppsFilterTest {
@Test
public void testForceQueryable_SystemDoesntFilter() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -621,8 +621,8 @@ public class AppsFilterTest {
@Test
public void testForceQueryable_NonSystemFilters() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -638,9 +638,10 @@ public class AppsFilterTest {
@Test
public void testForceQueryableByDevice_SystemCaller_DoesntFilter() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"},
- false, null, mMockExecutor, mMockPmInternal);
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+ new String[]{"com.some.package"}, false, null,
+ mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -657,8 +658,8 @@ public class AppsFilterTest {
@Test
public void testSystemSignedTarget_DoesntFilter() throws CertificateException {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
appsFilter.onSystemReady();
@@ -686,9 +687,10 @@ public class AppsFilterTest {
@Test
public void testForceQueryableByDevice_NonSystemCaller_Filters() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"},
- false, null, mMockExecutor, mMockPmInternal);
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+ new String[]{"com.some.package"}, false, null,
+ mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -704,8 +706,8 @@ public class AppsFilterTest {
@Test
public void testSystemQueryable_DoesntFilter() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{},
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{},
true /* system force queryable */, null, mMockExecutor,
mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
@@ -723,8 +725,8 @@ public class AppsFilterTest {
@Test
public void testQueriesPackage_DoesntFilter() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -742,8 +744,8 @@ public class AppsFilterTest {
public void testNoQueries_FeatureOff_DoesntFilter() throws Exception {
when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
.thenReturn(false);
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -759,8 +761,8 @@ public class AppsFilterTest {
@Test
public void testSystemUid_DoesntFilter() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -775,8 +777,8 @@ public class AppsFilterTest {
@Test
public void testSystemUidSecondaryUser_DoesntFilter() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -792,8 +794,8 @@ public class AppsFilterTest {
@Test
public void testNonSystemUid_NoCallingSetting_Filters() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -807,8 +809,8 @@ public class AppsFilterTest {
@Test
public void testNoTargetPackage_filters() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -838,7 +840,7 @@ public class AppsFilterTest {
.setOverlayTargetOverlayableName("overlayableName");
ParsingPackage actor = pkg("com.some.package.actor");
- final AppsFilter appsFilter = new AppsFilter(
+ final AppsFilterImpl appsFilter = new AppsFilterImpl(
mStateProvider,
mFeatureConfigMock,
new String[]{},
@@ -933,7 +935,7 @@ public class AppsFilterTest {
when(mMockPmInternal.getSharedUserPackages(any(Integer.class))).thenReturn(
actorSharedSettingPackages
);
- final AppsFilter appsFilter = new AppsFilter(
+ final AppsFilterImpl appsFilter = new AppsFilterImpl(
mStateProvider,
mFeatureConfigMock,
new String[]{},
@@ -985,8 +987,8 @@ public class AppsFilterTest {
@Test
public void testInitiatingApp_DoesntFilter() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -1003,8 +1005,8 @@ public class AppsFilterTest {
@Test
public void testUninstalledInitiatingApp_Filters() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -1021,8 +1023,8 @@ public class AppsFilterTest {
@Test
public void testOriginatingApp_Filters() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -1046,8 +1048,8 @@ public class AppsFilterTest {
@Test
public void testInstallingApp_DoesntFilter() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -1071,8 +1073,8 @@ public class AppsFilterTest {
@Test
public void testInstrumentation_DoesntFilter() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -1100,8 +1102,8 @@ public class AppsFilterTest {
@Test
public void testWhoCanSee() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -1173,8 +1175,8 @@ public class AppsFilterTest {
@Test
public void testOnChangeReport() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -1246,8 +1248,8 @@ public class AppsFilterTest {
@Test
public void testOnChangeReportedFilter() throws Exception {
- final AppsFilter appsFilter =
- new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -1270,6 +1272,53 @@ public class AppsFilterTest {
watcher.verifyNoChangeReported("shouldFilterApplication");
}
+ @Test
+ public void testAppsFilterRead() throws Exception {
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ mMockExecutor, mMockPmInternal);
+ simulateAddBasicAndroid(appsFilter);
+ appsFilter.onSystemReady();
+
+ PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+ DUMMY_TARGET_APPID);
+ PackageSetting instrumentation = simulateAddPackage(appsFilter,
+ pkgWithInstrumentation("com.some.other.package", "com.some.package"),
+ DUMMY_CALLING_APPID);
+
+ final int hasProviderAppId = Process.FIRST_APPLICATION_UID + 1;
+ final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2;
+ PackageSetting queriesProvider = simulateAddPackage(appsFilter,
+ pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"),
+ queriesProviderAppId);
+ appsFilter.grantImplicitAccess(
+ hasProviderAppId, queriesProviderAppId, false /* retainOnUpdate */);
+
+ AppsFilterSnapshot snapshot = appsFilter.snapshot();
+ assertFalse(
+ snapshot.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
+ SYSTEM_USER));
+ assertFalse(
+ snapshot.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation,
+ SYSTEM_USER));
+
+ SparseArray<int[]> queriesProviderFilter =
+ snapshot.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
+ assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)), contains(queriesProviderAppId));
+ assertTrue(snapshot.canQueryPackage(instrumentation.getPkg(),
+ target.getPackageName()));
+
+ // New changes don't affect the snapshot
+ appsFilter.removePackage(target, false);
+ assertTrue(
+ appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
+ SYSTEM_USER));
+ assertFalse(
+ snapshot.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
+ SYSTEM_USER));
+
+ }
+
private List<Integer> toList(int[] array) {
ArrayList<Integer> ret = new ArrayList<>(array.length);
for (int i = 0; i < array.length; i++) {
@@ -1282,7 +1331,7 @@ public class AppsFilterTest {
PackageSettingBuilder withBuilder(PackageSettingBuilder builder);
}
- private void simulateAddBasicAndroid(AppsFilter appsFilter) throws Exception {
+ private void simulateAddBasicAndroid(AppsFilterImpl appsFilter) throws Exception {
final Signature frameworkSignature = Mockito.mock(Signature.class);
final SigningDetails frameworkSigningDetails =
new SigningDetails(new Signature[]{frameworkSignature}, 1);
@@ -1291,17 +1340,17 @@ public class AppsFilterTest {
b -> b.setSigningDetails(frameworkSigningDetails));
}
- private PackageSetting simulateAddPackage(AppsFilter filter,
+ private PackageSetting simulateAddPackage(AppsFilterImpl filter,
ParsingPackage newPkgBuilder, int appId) {
return simulateAddPackage(filter, newPkgBuilder, appId, null /*settingBuilder*/);
}
- private PackageSetting simulateAddPackage(AppsFilter filter,
+ private PackageSetting simulateAddPackage(AppsFilterImpl filter,
ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action) {
return simulateAddPackage(filter, newPkgBuilder, appId, action, null /*sharedUserSetting*/);
}
- private PackageSetting simulateAddPackage(AppsFilter filter,
+ private PackageSetting simulateAddPackage(AppsFilterImpl filter,
ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action,
@Nullable SharedUserSetting sharedUserSetting) {
final PackageSetting setting =
@@ -1324,7 +1373,7 @@ public class AppsFilterTest {
return setting;
}
- private void simulateAddPackage(PackageSetting setting, AppsFilter filter,
+ private void simulateAddPackage(PackageSetting setting, AppsFilterImpl filter,
@Nullable SharedUserSetting sharedUserSetting) {
mExisting.put(setting.getPackageName(), setting);
if (sharedUserSetting != null) {
diff --git a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
index 4ed4c236535f..37c95f735d89 100644
--- a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
@@ -17,6 +17,7 @@
package com.android.server.utils;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -860,6 +861,54 @@ public class WatcherTest {
}
}
+ @Test
+ public void testWatchedSparseSetArray() {
+ final String name = "WatchedSparseSetArray";
+ WatchableTester tester;
+
+ // Test WatchedSparseSetArray
+ WatchedSparseSetArray array = new WatchedSparseSetArray();
+ tester = new WatchableTester(array, name);
+ tester.verify(0, "Initial array - no registration");
+ array.add(INDEX_A, 1);
+ tester.verify(0, "Updates with no registration");
+ tester.register();
+ tester.verify(0, "Updates with no registration");
+ array.add(INDEX_B, 2);
+ tester.verify(1, "Updates with registration");
+ array.add(INDEX_B, 4);
+ array.add(INDEX_C, 5);
+ tester.verify(3, "Updates with registration");
+ // Special methods
+ assertTrue(array.remove(INDEX_C, 5));
+ tester.verify(4, "Removed 5 from key 3");
+ array.remove(INDEX_B);
+ tester.verify(5, "Removed everything for key 2");
+
+ // Snapshot
+ {
+ WatchedSparseSetArray arraySnap = (WatchedSparseSetArray) array.snapshot();
+ tester.verify(5, "Generate snapshot");
+ // Verify that the snapshot is a proper copy of the source.
+ assertEquals("WatchedSparseSetArray snap same size",
+ array.size(), arraySnap.size());
+ for (int i = 0; i < array.size(); i++) {
+ ArraySet set = array.get(array.keyAt(i));
+ ArraySet setSnap = arraySnap.get(arraySnap.keyAt(i));
+ assertNotNull(set);
+ assertTrue(set.equals(setSnap));
+ }
+ array.add(INDEX_D, 9);
+ tester.verify(6, "Tick after snapshot");
+ // Verify that the array is sealed
+ verifySealed(name, ()->arraySnap.add(INDEX_D, 10));
+ assertTrue(!array.isSealed());
+ assertTrue(arraySnap.isSealed());
+ }
+ array.clear();
+ tester.verify(7, "Cleared all entries");
+ }
+
private static class IndexGenerator {
private final int mSeed;
private final Random mRandom;
@@ -1084,6 +1133,18 @@ public class WatcherTest {
assertEquals(a.equals(s), true);
a.put(rowIndex, colIndex, !a.get(rowIndex, colIndex));
assertEquals(a.equals(s), false);
+
+ // Verify copy-in/out
+ {
+ final String msg = name + " copy";
+ WatchedSparseBooleanMatrix copy = new WatchedSparseBooleanMatrix();
+ copy.copyFrom(matrix);
+ final int end = copy.size();
+ assertTrue(msg + " size mismatch " + end + " " + matrix.size(), end == matrix.size());
+ for (int i = 0; i < end; i++) {
+ assertEquals(copy.keyAt(i), keys[i]);
+ }
+ }
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index c81e7a25d183..b6998d84afa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -854,12 +854,13 @@ public class WindowStateTests extends WindowTestsBase {
assertTrue(mAtm.mActiveUids.hasNonAppVisibleWindow(uid));
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
@Test
public void testNeedsRelativeLayeringToIme_notAttached() {
WindowState sameTokenWindow = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken,
"SameTokenWindow");
mDisplayContent.setImeLayeringTarget(mAppWindow);
+ makeWindowVisible(mImeWindow);
sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
assertTrue(sameTokenWindow.needsRelativeLayeringToIme());
sameTokenWindow.removeImmediately();
@@ -872,6 +873,7 @@ public class WindowStateTests extends WindowTestsBase {
WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING,
mAppWindow.mToken, "SameTokenWindow");
mDisplayContent.setImeLayeringTarget(mAppWindow);
+ makeWindowVisible(mImeWindow);
sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 0f223ca037ee..2df1d23c0497 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -274,6 +274,7 @@ public class ZOrderingTests extends WindowTestsBase {
"imeAppTargetChildBelowWindow");
mDisplayContent.setImeLayeringTarget(imeAppTarget);
+ makeWindowVisible(mImeWindow);
mDisplayContent.assignChildLayers(mTransaction);
// Ime should be above all app windows except for child windows that are z-ordered above it
@@ -599,6 +600,7 @@ public class ZOrderingTests extends WindowTestsBase {
WINDOWING_MODE_MULTI_WINDOW);
mDisplayContent.setImeLayeringTarget(mAppWindow);
mDisplayContent.setImeInputTarget(mAppWindow);
+ makeWindowVisible(mImeWindow);
// Create a popupWindow
assertWindowHigher(mImeWindow, mAppWindow);