Merge "Update transition type to TRANSIT_TO_FRONT in test" into main
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 56a69b4..e5e0ad3 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -86,6 +86,9 @@
droidstubs {
name: "system-api-stubs-docs-non-updatable",
+ srcs: [
+ ":framework-minus-apex-aconfig-srcjars",
+ ],
defaults: [
"android-non-updatable-stubs-defaults",
"module-classpath-stubs-defaults",
@@ -126,6 +129,9 @@
droidstubs {
name: "test-api-stubs-docs-non-updatable",
+ srcs: [
+ ":framework-minus-apex-aconfig-srcjars",
+ ],
defaults: [
"android-non-updatable-stubs-defaults",
"module-classpath-stubs-defaults",
@@ -173,6 +179,9 @@
droidstubs {
name: "module-lib-api-stubs-docs-non-updatable",
+ srcs: [
+ ":framework-minus-apex-aconfig-srcjars",
+ ],
defaults: [
"android-non-updatable-stubs-defaults",
"module-classpath-stubs-defaults",
diff --git a/core/api/current.txt b/core/api/current.txt
index d8e3abf..955858b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9682,6 +9682,7 @@
method public int describeContents();
method public int getDeviceId();
method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @NonNull public int[] getDisplayIds();
+ method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public CharSequence getDisplayName();
method @Nullable public String getName();
method @Nullable public String getPersistentDeviceId();
method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomSensorSupport();
@@ -39008,6 +39009,7 @@
method @Nullable public java.util.Date getKeyValidityStart();
method @NonNull public String getKeystoreAlias();
method public int getMaxUsageCount();
+ method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
method public int getUserAuthenticationType();
@@ -39015,6 +39017,7 @@
method public boolean isDevicePropertiesAttestationIncluded();
method @NonNull public boolean isDigestsSpecified();
method public boolean isInvalidatedByBiometricEnrollment();
+ method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified();
method public boolean isRandomizedEncryptionRequired();
method public boolean isStrongBoxBacked();
method public boolean isUnlockedDeviceRequired();
@@ -39046,6 +39049,7 @@
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int);
+ method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@Nullable java.lang.String...);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean);
@@ -39150,12 +39154,14 @@
method @Nullable public java.util.Date getKeyValidityForOriginationEnd();
method @Nullable public java.util.Date getKeyValidityStart();
method public int getMaxUsageCount();
+ method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
method public int getUserAuthenticationType();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isDigestsSpecified();
method public boolean isInvalidatedByBiometricEnrollment();
+ method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUnlockedDeviceRequired();
method public boolean isUserAuthenticationRequired();
@@ -39177,6 +39183,7 @@
method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date);
method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date);
method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int);
+ method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...);
method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 9b8d2b4..052d614 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -556,24 +556,24 @@
package android.se.omapi {
- @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public class SeFrameworkInitializer {
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public static android.se.omapi.SeServiceManager getSeServiceManager();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public static void setSeServiceManager(@NonNull android.se.omapi.SeServiceManager);
+ @FlaggedApi("android.nfc.enable_nfc_mainline") public class SeFrameworkInitializer {
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public static android.se.omapi.SeServiceManager getSeServiceManager();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public static void setSeServiceManager(@NonNull android.se.omapi.SeServiceManager);
}
- @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public class SeServiceManager {
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.se.omapi.SeServiceManager.ServiceRegisterer getSeManagerServiceRegisterer();
+ @FlaggedApi("android.nfc.enable_nfc_mainline") public class SeServiceManager {
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.se.omapi.SeServiceManager.ServiceRegisterer getSeManagerServiceRegisterer();
}
- @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public static class SeServiceManager.ServiceNotFoundException extends java.lang.Exception {
- ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public SeServiceManager.ServiceNotFoundException(@NonNull String);
+ @FlaggedApi("android.nfc.enable_nfc_mainline") public static class SeServiceManager.ServiceNotFoundException extends java.lang.Exception {
+ ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public SeServiceManager.ServiceNotFoundException(@NonNull String);
}
- @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public static final class SeServiceManager.ServiceRegisterer {
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public android.os.IBinder get();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.os.IBinder getOrThrow() throws android.se.omapi.SeServiceManager.ServiceNotFoundException;
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void register(@NonNull android.os.IBinder);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public android.os.IBinder tryGet();
+ @FlaggedApi("android.nfc.enable_nfc_mainline") public static final class SeServiceManager.ServiceRegisterer {
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public android.os.IBinder get();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.os.IBinder getOrThrow() throws android.se.omapi.SeServiceManager.ServiceNotFoundException;
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void register(@NonNull android.os.IBinder);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public android.os.IBinder tryGet();
}
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 220482d..1d88e00 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -540,7 +540,7 @@
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int);
method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void forceStopPackage(String);
method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS_FULL"}) public static int getCurrentUser();
- method @FlaggedApi(Flags.FLAG_APP_START_INFO) @NonNull @RequiresPermission(android.Manifest.permission.DUMP) public java.util.List<android.app.ApplicationStartInfo> getExternalHistoricalProcessStartReasons(@NonNull String, @IntRange(from=0) int);
+ method @FlaggedApi("android.app.app_start_info") @NonNull @RequiresPermission(android.Manifest.permission.DUMP) public java.util.List<android.app.ApplicationStartInfo> getExternalHistoricalProcessStartReasons(@NonNull String, @IntRange(from=0) int);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getPackageImportance(String);
method @NonNull public java.util.Collection<java.util.Locale> getSupportedLocales();
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidImportance(int);
@@ -3193,7 +3193,7 @@
public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
- method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName);
+ method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName);
method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
method @NonNull public android.content.Context createContext();
@@ -3214,9 +3214,9 @@
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
- method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
+ method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
- method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
+ method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
}
@@ -3232,7 +3232,7 @@
method public int getDefaultActivityPolicy();
method public int getDefaultNavigationPolicy();
method public int getDevicePolicy(int);
- method @FlaggedApi(Flags.FLAG_VDM_CUSTOM_HOME) @Nullable public android.content.ComponentName getHomeComponent();
+ method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent();
method public int getLockState();
method @Nullable public String getName();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
@@ -3247,7 +3247,7 @@
field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
- field @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
+ field @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
@@ -3264,7 +3264,7 @@
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
- method @FlaggedApi(Flags.FLAG_VDM_CUSTOM_HOME) @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
+ method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
@@ -3805,8 +3805,8 @@
public class PackageInstaller {
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
- method @FlaggedApi(Flags.FLAG_ARCHIVING) @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
- method @FlaggedApi(Flags.FLAG_ARCHIVING) @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3817,8 +3817,8 @@
field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
field public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH";
- field @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
- field @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+ field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+ field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
field public static final int LOCATION_DATA_APP = 0; // 0x0
field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
@@ -3879,7 +3879,7 @@
method public static void forceSafeLabels();
method @Deprecated @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager);
method @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager, @FloatRange(from=0) float, int);
- field @FlaggedApi(Flags.FLAG_ARCHIVING) public boolean isArchived;
+ field @FlaggedApi("android.content.pm.archiving") public boolean isArchived;
}
public abstract class PackageManager {
@@ -3929,7 +3929,7 @@
method @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public void setHarmfulAppWarning(@NonNull String, @Nullable CharSequence);
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable String);
method @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo);
- method @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int);
+ method @FlaggedApi("android.content.pm.quarantined_enabled") @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int);
method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
method public void setSystemAppState(@NonNull String, int);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
@@ -3980,7 +3980,7 @@
field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 512; // 0x200
field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 256; // 0x100
field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1
- field @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) public static final int FLAG_SUSPEND_QUARANTINED = 1; // 0x1
+ field @FlaggedApi("android.content.pm.quarantined_enabled") public static final int FLAG_SUSPEND_QUARANTINED = 1; // 0x1
field public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; // 0xffffffff
field public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; // 0xfffffff3
field public static final int INSTALL_FAILED_CONTAINER_ERROR = -18; // 0xffffffee
@@ -9632,67 +9632,67 @@
package android.nfc.cardemulation {
- @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public final class AidGroup implements android.os.Parcelable {
- ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public AidGroup(@NonNull java.util.List<java.lang.String>, @Nullable String);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public static android.nfc.cardemulation.AidGroup createFromXml(@NonNull org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int describeContents();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dump(@NonNull android.util.proto.ProtoOutputStream);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getAids();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getCategory();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeAsXml(@NonNull org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeToParcel(@NonNull android.os.Parcel, int);
- field @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.AidGroup> CREATOR;
+ @FlaggedApi("android.nfc.enable_nfc_mainline") public final class AidGroup implements android.os.Parcelable {
+ ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public AidGroup(@NonNull java.util.List<java.lang.String>, @Nullable String);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public static android.nfc.cardemulation.AidGroup createFromXml(@NonNull org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.util.proto.ProtoOutputStream);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getAids();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getCategory();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeAsXml(@NonNull org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.AidGroup> CREATOR;
}
- @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public final class ApduServiceInfo implements android.os.Parcelable {
- ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int describeContents();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<android.nfc.cardemulation.AidGroup> getAidGroups();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getAids();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getCategoryForAid(@NonNull String);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.content.ComponentName getComponent();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getDescription();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public String getOffHostSecureElement();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getPrefixAids();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getSettingsActivityName();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getSubsetAids();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int getUid();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean hasCategory(@NonNull String);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean isOnHost();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public CharSequence loadAppLabel(@NonNull android.content.pm.PackageManager);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.graphics.drawable.Drawable loadBanner(@NonNull android.content.pm.PackageManager);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean requiresScreenOn();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean requiresUnlock();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void resetOffHostSecureElement();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setOffHostSecureElement(@NonNull String);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeToParcel(@NonNull android.os.Parcel, int);
- field @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
+ @FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
+ ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.AidGroup> getAidGroups();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getAids();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getCategoryForAid(@NonNull String);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.content.ComponentName getComponent();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public String getOffHostSecureElement();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean hasCategory(@NonNull String);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean isOnHost();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadAppLabel(@NonNull android.content.pm.PackageManager);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadBanner(@NonNull android.content.pm.PackageManager);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresScreenOn();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setOffHostSecureElement(@NonNull String);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
}
- @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public final class NfcFServiceInfo implements android.os.Parcelable {
- ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public NfcFServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int describeContents();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.content.ComponentName getComponent();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getDescription();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getNfcid2();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getSystemCode();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getT3tPmm();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int getUid();
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setDynamicNfcid2(@NonNull String);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setDynamicSystemCode(@NonNull String);
- method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeToParcel(@NonNull android.os.Parcel, int);
- field @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.NfcFServiceInfo> CREATOR;
+ @FlaggedApi("android.nfc.enable_nfc_mainline") public final class NfcFServiceInfo implements android.os.Parcelable {
+ ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public NfcFServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.content.ComponentName getComponent();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getNfcid2();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSystemCode();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getT3tPmm();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid();
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicNfcid2(@NonNull String);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicSystemCode(@NonNull String);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.NfcFServiceInfo> CREATOR;
}
}
@@ -10705,13 +10705,13 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetUnusedAppCount(@NonNull java.util.function.IntConsumer);
method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable);
method @Deprecated @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String);
- method @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
method @Deprecated @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable);
- method @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
@@ -16777,13 +16777,13 @@
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff
- field @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8
- field @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7
field public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3
field public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2
field public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0
field public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1
- field @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6
field public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4
field public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5
field public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 4692f92..ce883cd 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -44,6 +44,7 @@
private final int mId;
private final @Nullable String mPersistentId;
private final @Nullable String mName;
+ private final @Nullable CharSequence mDisplayName;
/**
* Creates a new instance of {@link VirtualDevice}.
@@ -53,6 +54,18 @@
*/
public VirtualDevice(@NonNull IVirtualDevice virtualDevice, int id,
@Nullable String persistentId, @Nullable String name) {
+ this(virtualDevice, id, persistentId, name, null);
+ }
+
+ /**
+ * Creates a new instance of {@link VirtualDevice}. Only to be used by the
+ * VirtualDeviceManagerService.
+ *
+ * @hide
+ */
+ public VirtualDevice(@NonNull IVirtualDevice virtualDevice, int id,
+ @Nullable String persistentId, @Nullable String name,
+ @Nullable CharSequence displayName) {
if (id <= Context.DEVICE_ID_DEFAULT) {
throw new IllegalArgumentException("VirtualDevice ID must be greater than "
+ Context.DEVICE_ID_DEFAULT);
@@ -61,6 +74,7 @@
mId = id;
mPersistentId = persistentId;
mName = name;
+ mDisplayName = displayName;
}
private VirtualDevice(@NonNull Parcel parcel) {
@@ -68,6 +82,7 @@
mId = parcel.readInt();
mPersistentId = parcel.readString8();
mName = parcel.readString8();
+ mDisplayName = parcel.readCharSequence();
}
/**
@@ -112,6 +127,15 @@
}
/**
+ * Returns the human-readable name of the virtual device, if defined, which is suitable to be
+ * shown in UI.
+ */
+ @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
+ public @Nullable CharSequence getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
* Returns the IDs of all virtual displays that belong to this device, if any.
*
* <p>The actual {@link android.view.Display} objects can be obtained by passing the returned
@@ -156,6 +180,7 @@
dest.writeInt(mId);
dest.writeString8(mPersistentId);
dest.writeString8(mName);
+ dest.writeCharSequence(mDisplayName);
}
@Override
@@ -165,6 +190,7 @@
+ " mId=" + mId
+ " mPersistentId=" + mPersistentId
+ " mName=" + mName
+ + " mDisplayName=" + mDisplayName
+ ")";
}
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 3e96c96..d0e13cd 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -34,3 +34,10 @@
description: "Enable Virtual Camera"
bug: "270352264"
}
+
+flag {
+ name: "stream_permissions"
+ namespace: "virtual_devices"
+ description: "Enable streaming permission dialogs to Virtual Devices"
+ bug: "291737919"
+}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 4700720..4791a83 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -698,4 +698,34 @@
return "AmbientLightSensorData(" + sensorName + ", " + sensorType + ")";
}
}
+
+ /**
+ * Associate a internal display to a {@link DisplayOffloader}.
+ *
+ * @param displayId the id of the internal display.
+ * @param displayOffloader the {@link DisplayOffloader} that controls offloading ops of internal
+ * display whose id is displayId.
+ * @return a {@link DisplayOffloadSession} associated with given displayId and displayOffloader.
+ */
+ public abstract DisplayOffloadSession registerDisplayOffloader(
+ int displayId, DisplayOffloader displayOffloader);
+
+ /** The callbacks that controls the entry & exit of display offloading. */
+ public interface DisplayOffloader {
+ boolean startOffload();
+
+ void stopOffload();
+ }
+
+ /** A session token that associates a internal display with a {@link DisplayOffloader}. */
+ public interface DisplayOffloadSession {
+ /** Provide the display state to use in place of state DOZE. */
+ void setDozeStateOverride(int displayState);
+ /** Returns the associated DisplayOffloader. */
+ DisplayOffloader getDisplayOffloader();
+ /** Returns whether displayoffload supports the given display state. */
+ static boolean isSupportedOffloadState(int displayState) {
+ return Display.isSuspendedState(displayState);
+ }
+ }
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 94d8516..6a82f6d 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -959,8 +959,8 @@
mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales,
AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER);
}
- notifyStateChangedLocked();
}
+ notifyStateChanged(availability);
}
/**
@@ -1370,8 +1370,8 @@
mAvailability = STATE_INVALID;
mIsAvailabilityOverriddenByTestApi = false;
- notifyStateChangedLocked();
}
+ notifyStateChanged(STATE_INVALID);
super.destroy();
}
@@ -1401,6 +1401,8 @@
*/
// TODO(b/281608561): remove the enrollment flow from AlwaysOnHotwordDetector
void onSoundModelsChanged() {
+ boolean notifyError = false;
+
synchronized (mLock) {
if (mAvailability == STATE_INVALID
|| mAvailability == STATE_HARDWARE_UNAVAILABLE
@@ -1441,6 +1443,9 @@
// calling stopRecognition where there is no started session.
Log.w(TAG, "Failed to stop recognition after enrollment update: code="
+ result);
+
+ // Execute a refresh availability task - which should then notify of a change.
+ new RefreshAvailabilityTask().execute();
} catch (Exception e) {
Slog.w(TAG, "Failed to stop recognition after enrollment update", e);
if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
@@ -1449,14 +1454,14 @@
+ Log.getStackTraceString(e),
FailureSuggestedAction.RECREATE_DETECTOR));
} else {
- updateAndNotifyStateChangedLocked(STATE_ERROR);
+ notifyError = true;
}
- return;
}
}
+ }
- // Execute a refresh availability task - which should then notify of a change.
- new RefreshAvailabilityTask().execute();
+ if (notifyError) {
+ updateAndNotifyStateChanged(STATE_ERROR);
}
}
@@ -1572,10 +1577,11 @@
}
}
- @GuardedBy("mLock")
- private void updateAndNotifyStateChangedLocked(int availability) {
- updateAvailabilityLocked(availability);
- notifyStateChangedLocked();
+ private void updateAndNotifyStateChanged(int availability) {
+ synchronized (mLock) {
+ updateAvailabilityLocked(availability);
+ }
+ notifyStateChanged(availability);
}
@GuardedBy("mLock")
@@ -1589,17 +1595,17 @@
}
}
- @GuardedBy("mLock")
- private void notifyStateChangedLocked() {
+ private void notifyStateChanged(int newAvailability) {
Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
- message.arg1 = mAvailability;
+ message.arg1 = newAvailability;
message.sendToTarget();
}
- @GuardedBy("mLock")
private void sendUnknownFailure(String failureMessage) {
- // update but do not call onAvailabilityChanged callback for STATE_ERROR
- updateAvailabilityLocked(STATE_ERROR);
+ synchronized (mLock) {
+ // update but do not call onAvailabilityChanged callback for STATE_ERROR
+ updateAvailabilityLocked(STATE_ERROR);
+ }
Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget();
}
@@ -1802,19 +1808,17 @@
availability = STATE_KEYPHRASE_UNENROLLED;
}
}
- updateAndNotifyStateChangedLocked(availability);
}
+ updateAndNotifyStateChanged(availability);
} catch (Exception e) {
// Any exception here not caught will crash the process because AsyncTask does not
// bubble up the exceptions to the client app, so we must propagate it to the app.
Slog.w(TAG, "Failed to refresh availability", e);
- synchronized (mLock) {
- if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
- sendUnknownFailure(
- "Failed to refresh availability: " + Log.getStackTraceString(e));
- } else {
- updateAndNotifyStateChangedLocked(STATE_ERROR);
- }
+ if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
+ sendUnknownFailure(
+ "Failed to refresh availability: " + Log.getStackTraceString(e));
+ } else {
+ updateAndNotifyStateChanged(STATE_ERROR);
}
}
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
new file mode 100644
index 0000000..46fa501
--- /dev/null
+++ b/core/java/android/text/ClientFlags.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import com.android.text.flags.Flags;
+
+/**
+ * An aconfig feature flags that can be accessible from application process without
+ * ContentProvider IPCs.
+ *
+ * When you add new flags, you have to add flag string to {@link TextFlags#TEXT_ACONFIGS_FLAGS}.
+ *
+ * @hide
+ */
+public class ClientFlags {
+
+ /**
+ * @see Flags#deprecateFontsXml()
+ */
+ public static boolean deprecateFontsXml() {
+ return TextFlags.isFeatureEnabled(Flags.FLAG_DEPRECATE_FONTS_XML);
+ }
+
+ /**
+ * @see Flags#noBreakNoHyphenationSpan()
+ */
+ public static boolean noBreakNoHyphenationSpan() {
+ return TextFlags.isFeatureEnabled(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN);
+ }
+
+ /**
+ * @see Flags#phraseStrictFallback()
+ */
+ public static boolean phraseStrictFallback() {
+ return TextFlags.isFeatureEnabled(Flags.FLAG_PHRASE_STRICT_FALLBACK);
+ }
+
+ /**
+ * @see Flags#useBoundsForWidth()
+ */
+ public static boolean useBoundsForWidth() {
+ return TextFlags.isFeatureEnabled(Flags.FLAG_USE_BOUNDS_FOR_WIDTH);
+ }
+}
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 4be6a8d..536e3cc 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -16,6 +16,11 @@
package android.text;
+import android.annotation.NonNull;
+import android.app.AppGlobals;
+
+import com.android.text.flags.Flags;
+
/**
* Flags in the "text" namespace.
*
@@ -46,4 +51,28 @@
*/
public static final boolean ENABLE_NEW_CONTEXT_MENU_DEFAULT = true;
+ /**
+ * List of text flags to be transferred to the application process.
+ */
+ public static final String[] TEXT_ACONFIGS_FLAGS = {
+ Flags.FLAG_DEPRECATE_FONTS_XML,
+ Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN,
+ Flags.FLAG_PHRASE_STRICT_FALLBACK,
+ Flags.FLAG_USE_BOUNDS_FOR_WIDTH,
+ };
+
+ /**
+ * Get a key for the feature flag.
+ */
+ public static String getKeyForFlag(@NonNull String flag) {
+ return "text__" + flag;
+ }
+
+ /**
+ * Return true if the feature flag is enabled.
+ */
+ public static boolean isFeatureEnabled(@NonNull String flag) {
+ return AppGlobals.getIntCoreSetting(
+ getKeyForFlag(flag), 0 /* aconfig is false by default */) != 0;
+ }
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index bdd0a9c..969b6a512 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -30,8 +31,10 @@
import android.graphics.TextureLayer;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.flags.Flags;
/**
* <p>A TextureView can be used to display a content stream, such as that
@@ -194,6 +197,9 @@
private Canvas mCanvas;
private int mSaveCount;
+ @FloatRange(from = 0.0) float mFrameRate;
+ @Surface.FrameRateCompatibility int mFrameRateCompatibility;
+
private final Object[] mNativeWindowLock = new Object[0];
// Set by native code, do not write!
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -465,6 +471,16 @@
mLayer.setSurfaceTexture(mSurface);
mSurface.setDefaultBufferSize(getWidth(), getHeight());
mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+ if (Flags.toolkitSetFrameRate()) {
+ mSurface.setOnSetFrameRateListener(
+ (surfaceTexture, frameRate, compatibility, strategy) -> {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(Trace.TRACE_TAG_VIEW, "setFrameRate: " + frameRate);
+ }
+ mFrameRate = frameRate;
+ mFrameRateCompatibility = compatibility;
+ }, mAttachInfo.mHandler);
+ }
if (mListener != null && createNewSurface) {
mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a0d0656..2c41330 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -103,6 +103,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.text.BoringLayout;
+import android.text.ClientFlags;
import android.text.DynamicLayout;
import android.text.Editable;
import android.text.GetChars;
@@ -1634,7 +1635,7 @@
}
if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
- mUseBoundsForWidth = false; // TODO: Connect to the flag.
+ mUseBoundsForWidth = ClientFlags.useBoundsForWidth();
} else {
mUseBoundsForWidth = false;
}
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index a88e394..451acbe 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -122,7 +122,7 @@
TYPE_PARAMETER_PROCESS_RUNNING,
TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT,
TYPE_PARAMETER_ACTIVITY_CREATED,
- TYPE_PARAMETER_ALLOW_ICON,
+ TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN,
TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN,
TYPE_PARAMETER_WINDOWLESS,
TYPE_PARAMETER_LEGACY_SPLASH_SCREEN
@@ -143,7 +143,7 @@
/** @hide */
public static final int TYPE_PARAMETER_ACTIVITY_CREATED = 0x00000010;
/** @hide */
- public static final int TYPE_PARAMETER_ALLOW_ICON = 0x00000020;
+ public static final int TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN = 0x00000020;
/**
* The parameter which indicates if the activity has finished drawing.
* @hide
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index d503904..7a87c3a 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -16,9 +16,11 @@
package com.android.internal.display;
+import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.net.Uri;
@@ -54,8 +56,7 @@
private static final int MSG_RUN_UPDATE = 1;
// The tolerance within which we consider brightness values approximately equal to eachother.
- // This value is approximately 1/3 of the smallest possible brightness value.
- public static final float EPSILON = 0.001f;
+ public static final float EPSILON = 0.0001f;
private static int sBrightnessUpdateCount = 1;
@@ -284,6 +285,74 @@
}
/**
+ * Converts between the int brightness setting and the float brightness system. The int
+ * brightness setting is between 0-255 and matches the brightness slider - e.g. 128 is 50% on
+ * the slider. Accounts for special values such as OFF and invalid values. Accounts for
+ * brightness limits; the maximum value here represents the max value allowed on the slider.
+ */
+ @VisibleForTesting
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public float brightnessIntSettingToFloat(int brightnessInt) {
+ if (brightnessInt == PowerManager.BRIGHTNESS_OFF) {
+ return PowerManager.BRIGHTNESS_OFF_FLOAT;
+ } else if (brightnessInt == PowerManager.BRIGHTNESS_INVALID) {
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ } else {
+ final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
+ final float maxInt = PowerManager.BRIGHTNESS_ON;
+
+ // Normalize to the range [0, 1]
+ float userPerceptionBrightness = MathUtils.norm(minInt, maxInt, brightnessInt);
+
+ // Convert from user-perception to linear scale
+ float linearBrightness = BrightnessUtils.convertGammaToLinear(userPerceptionBrightness);
+
+ // Interpolate to the range [0, currentlyAllowedMax]
+ final Display display = mContext.getDisplay();
+ if (display == null) {
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+ final BrightnessInfo info = display.getBrightnessInfo();
+ return MathUtils.lerp(info.brightnessMinimum, info.brightnessMaximum, linearBrightness);
+ }
+ }
+
+ /**
+ * Translates specified value from the float brightness system to the setting int brightness
+ * system. The value returned is between 0-255 and matches the brightness slider - e.g. 128 is
+ * 50% on the slider. Accounts for special values such as OFF and invalid values. Accounts for
+ * brightness limits; the maximum value here represents the max value currently allowed on
+ * the slider.
+ */
+ @VisibleForTesting
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public int brightnessFloatToIntSetting(float brightnessFloat) {
+ if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
+ return PowerManager.BRIGHTNESS_OFF;
+ } else if (Float.isNaN(brightnessFloat)) {
+ return PowerManager.BRIGHTNESS_INVALID;
+ } else {
+ // Normalize to the range [0, 1]
+ final Display display = mContext.getDisplay();
+ if (display == null) {
+ return PowerManager.BRIGHTNESS_INVALID;
+ }
+ final BrightnessInfo info = display.getBrightnessInfo();
+ float linearBrightness =
+ MathUtils.norm(info.brightnessMinimum, info.brightnessMaximum, brightnessFloat);
+
+ // Convert from linear to user-perception scale
+ float userPerceptionBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
+
+ // Interpolate to the range [0, 255]
+ final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
+ final float maxInt = PowerManager.BRIGHTNESS_ON;
+ float intBrightness = MathUtils.lerp(minInt, maxInt, userPerceptionBrightness);
+ return Math.round(intBrightness);
+ }
+ }
+
+ /**
* Encapsulates a brightness change event and contains logic for synchronizing the appropriate
* settings for the specified brightness change.
*/
@@ -421,14 +490,14 @@
if (mSourceType == TYPE_INT) {
return (int) mBrightness;
}
- return brightnessFloatToInt(mBrightness);
+ return brightnessFloatToIntSetting(mBrightness);
}
private float getBrightnessAsFloat() {
if (mSourceType == TYPE_FLOAT) {
return mBrightness;
}
- return brightnessIntToFloat((int) mBrightness);
+ return brightnessIntSettingToFloat((int) mBrightness);
}
private String toStringLabel(int type, float brightness) {
diff --git a/services/core/java/com/android/server/display/BrightnessUtils.java b/core/java/com/android/internal/display/BrightnessUtils.java
similarity index 96%
rename from services/core/java/com/android/server/display/BrightnessUtils.java
rename to core/java/com/android/internal/display/BrightnessUtils.java
index 84fa0cc..82b506b 100644
--- a/services/core/java/com/android/server/display/BrightnessUtils.java
+++ b/core/java/com/android/internal/display/BrightnessUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.display;
+package com.android.internal.display;
import android.util.MathUtils;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index c6f5086..7eeac29 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -154,6 +154,7 @@
void addQsTile(in ComponentName tile);
void remQsTile(in ComponentName tile);
+ void setQsTiles(in String[] tiles);
void clickQsTile(in ComponentName tile);
void handleSystemKey(in KeyEvent key);
diff --git a/core/java/com/android/internal/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java
index acb0e44..89f4659 100644
--- a/core/java/com/android/internal/widget/CallLayout.java
+++ b/core/java/com/android/internal/widget/CallLayout.java
@@ -49,6 +49,7 @@
private CachingIconView mIcon;
private CachingIconView mConversationIconBadgeBg;
private TextView mConversationText;
+ private boolean mSetDataAsyncEnabled = false;
public CallLayout(@NonNull Context context) {
super(context);
@@ -83,7 +84,8 @@
});
}
- private void updateCallLayout() {
+ @NonNull
+ private Icon getConversationIcon() {
CharSequence callerName = "";
String symbol = "";
Icon icon = null;
@@ -98,8 +100,7 @@
if (icon == null) {
icon = mPeopleHelper.createAvatarSymbol(callerName, symbol, mLayoutColor);
}
- // TODO(b/179178086): crop/clip the icon to a circle?
- mConversationIconView.setImageIcon(icon);
+ return icon;
}
@RemotableViewMethod
@@ -123,10 +124,38 @@
/**
* Set the notification extras so that this layout has access
*/
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
- setUser(extras.getParcelable(Notification.EXTRA_CALL_PERSON, android.app.Person.class));
- updateCallLayout();
+ final Person person = getPerson(extras);
+ setUser(person);
+
+ final Icon icon = getConversationIcon();
+ mConversationIconView.setImageIcon(icon);
+ }
+
+
+ public void setSetDataAsyncEnabled(boolean setDataAsyncEnabled) {
+ mSetDataAsyncEnabled = setDataAsyncEnabled;
+ }
+
+ /**
+ * Async implementation for setData
+ */
+ public Runnable setDataAsync(Bundle extras) {
+ if (!mSetDataAsyncEnabled) {
+ return () -> setData(extras);
+ }
+
+ final Person person = getPerson(extras);
+ setUser(person);
+
+ final Icon conversationIcon = getConversationIcon();
+ return mConversationIconView.setImageIconAsync(conversationIcon);
+ }
+
+ @Nullable
+ private Person getPerson(Bundle extras) {
+ return extras.getParcelable(Notification.EXTRA_CALL_PERSON, Person.class);
}
private void setUser(Person user) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 9ed4155..3795fc8 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -102,6 +102,7 @@
static_libs: [
"libnativehelper_lazy",
"libziparchive_for_incfs",
+ "libguiflags",
],
export_include_dirs: [
diff --git a/core/jni/android_graphics_SurfaceTexture.cpp b/core/jni/android_graphics_SurfaceTexture.cpp
index 21487ab..50832a5 100644
--- a/core/jni/android_graphics_SurfaceTexture.cpp
+++ b/core/jni/android_graphics_SurfaceTexture.cpp
@@ -17,27 +17,24 @@
#undef LOG_TAG
#define LOG_TAG "SurfaceTexture"
-#include <stdio.h>
-
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
-
+#include <com_android_graphics_libgui_flags.h>
+#include <cutils/atomic.h>
#include <gui/BufferQueue.h>
#include <gui/Surface.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <stdio.h>
#include <surfacetexture/SurfaceTexture.h>
#include <surfacetexture/surface_texture_platform.h>
-
-#include "core_jni_helpers.h"
-
-#include <cutils/atomic.h>
#include <utils/Log.h>
#include <utils/misc.h>
+#include "core_jni_helpers.h"
#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
// ----------------------------------------------------------------------------
@@ -55,6 +52,7 @@
jfieldID producer;
jfieldID frameAvailableListener;
jmethodID postEvent;
+ jmethodID postOnSetFrameRateEvent;
};
static fields_t fields;
@@ -139,61 +137,81 @@
// ----------------------------------------------------------------------------
-class JNISurfaceTextureContext : public SurfaceTexture::FrameAvailableListener
-{
+class JNISurfaceTextureContextCommon {
public:
- JNISurfaceTextureContext(JNIEnv* env, jobject weakThiz, jclass clazz);
- virtual ~JNISurfaceTextureContext();
- virtual void onFrameAvailable(const BufferItem& item);
+ JNISurfaceTextureContextCommon(JNIEnv* env, jobject weakThiz, jclass clazz)
+ : mWeakThiz(env->NewGlobalRef(weakThiz)), mClazz((jclass)env->NewGlobalRef(clazz)) {}
-private:
- static JNIEnv* getJNIEnv();
+ virtual ~JNISurfaceTextureContextCommon() {
+ JNIEnv* env = getJNIEnv();
+ if (env != NULL) {
+ env->DeleteGlobalRef(mWeakThiz);
+ env->DeleteGlobalRef(mClazz);
+ } else {
+ ALOGW("leaking JNI object references");
+ }
+ }
+
+ void onFrameAvailable(const BufferItem& item) {
+ JNIEnv* env = getJNIEnv();
+ if (env != NULL) {
+ env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
+ } else {
+ ALOGW("onFrameAvailable event will not posted");
+ }
+ }
+
+protected:
+ static JNIEnv* getJNIEnv() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ if (env == NULL) {
+ JavaVMAttachArgs args = {JNI_VERSION_1_4, "JNISurfaceTextureContext", NULL};
+ JavaVM* vm = AndroidRuntime::getJavaVM();
+ int result = vm->AttachCurrentThreadAsDaemon(&env, (void*)&args);
+ if (result != JNI_OK) {
+ ALOGE("thread attach failed: %#x", result);
+ return NULL;
+ }
+ }
+ return env;
+ }
jobject mWeakThiz;
jclass mClazz;
};
-JNISurfaceTextureContext::JNISurfaceTextureContext(JNIEnv* env,
- jobject weakThiz, jclass clazz) :
- mWeakThiz(env->NewGlobalRef(weakThiz)),
- mClazz((jclass)env->NewGlobalRef(clazz))
-{}
+class JNISurfaceTextureContextFrameAvailableListener
+ : public JNISurfaceTextureContextCommon,
+ public SurfaceTexture::FrameAvailableListener {
+public:
+ JNISurfaceTextureContextFrameAvailableListener(JNIEnv* env, jobject weakThiz, jclass clazz)
+ : JNISurfaceTextureContextCommon(env, weakThiz, clazz) {}
+ void onFrameAvailable(const BufferItem& item) override {
+ JNISurfaceTextureContextCommon::onFrameAvailable(item);
+ }
+};
-JNIEnv* JNISurfaceTextureContext::getJNIEnv() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- if (env == NULL) {
- JavaVMAttachArgs args = {
- JNI_VERSION_1_4, "JNISurfaceTextureContext", NULL };
- JavaVM* vm = AndroidRuntime::getJavaVM();
- int result = vm->AttachCurrentThreadAsDaemon(&env, (void*)&args);
- if (result != JNI_OK) {
- ALOGE("thread attach failed: %#x", result);
- return NULL;
+class JNISurfaceTextureContextListener : public JNISurfaceTextureContextCommon,
+ public SurfaceTexture::SurfaceTextureListener {
+public:
+ JNISurfaceTextureContextListener(JNIEnv* env, jobject weakThiz, jclass clazz)
+ : JNISurfaceTextureContextCommon(env, weakThiz, clazz) {}
+
+ void onFrameAvailable(const BufferItem& item) override {
+ JNISurfaceTextureContextCommon::onFrameAvailable(item);
+ }
+
+ void onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override {
+ JNIEnv* env = getJNIEnv();
+ if (env != NULL) {
+ env->CallStaticVoidMethod(mClazz, fields.postOnSetFrameRateEvent, mWeakThiz, frameRate,
+ compatibility, changeFrameRateStrategy);
+ } else {
+ ALOGW("onSetFrameRate event will not posted");
}
}
- return env;
-}
-
-JNISurfaceTextureContext::~JNISurfaceTextureContext()
-{
- JNIEnv* env = getJNIEnv();
- if (env != NULL) {
- env->DeleteGlobalRef(mWeakThiz);
- env->DeleteGlobalRef(mClazz);
- } else {
- ALOGW("leaking JNI object references");
- }
-}
-
-void JNISurfaceTextureContext::onFrameAvailable(const BufferItem& /* item */)
-{
- JNIEnv* env = getJNIEnv();
- if (env != NULL) {
- env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
- } else {
- ALOGW("onFrameAvailable event will not posted");
- }
-}
+};
// ----------------------------------------------------------------------------
@@ -229,6 +247,13 @@
if (fields.postEvent == NULL) {
ALOGE("can't find android/graphics/SurfaceTexture.postEventFromNative");
}
+
+ fields.postOnSetFrameRateEvent =
+ env->GetStaticMethodID(clazz, "postOnSetFrameRateEventFromNative",
+ "(Ljava/lang/ref/WeakReference;FII)V");
+ if (fields.postOnSetFrameRateEvent == NULL) {
+ ALOGE("can't find android/graphics/SurfaceTexture.postOnSetFrameRateEventFromNative");
+ }
}
static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
@@ -274,17 +299,27 @@
return;
}
- sp<JNISurfaceTextureContext> ctx(new JNISurfaceTextureContext(env, weakThiz,
- clazz));
- surfaceTexture->setFrameAvailableListener(ctx);
- SurfaceTexture_setFrameAvailableListener(env, thiz, ctx);
+ if (com::android::graphics::libgui::flags::bq_setframerate()) {
+ sp<JNISurfaceTextureContextListener> ctx(
+ new JNISurfaceTextureContextListener(env, weakThiz, clazz));
+ surfaceTexture->setSurfaceTextureListener(ctx);
+ } else {
+ sp<JNISurfaceTextureContextFrameAvailableListener> ctx(
+ new JNISurfaceTextureContextFrameAvailableListener(env, weakThiz, clazz));
+ surfaceTexture->setFrameAvailableListener(ctx);
+ SurfaceTexture_setFrameAvailableListener(env, thiz, ctx);
+ }
}
static void SurfaceTexture_finalize(JNIEnv* env, jobject thiz)
{
sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
- surfaceTexture->setFrameAvailableListener(0);
- SurfaceTexture_setFrameAvailableListener(env, thiz, 0);
+ if (com::android::graphics::libgui::flags::bq_setframerate()) {
+ surfaceTexture->setSurfaceTextureListener(0);
+ } else {
+ surfaceTexture->setFrameAvailableListener(0);
+ SurfaceTexture_setFrameAvailableListener(env, thiz, 0);
+ }
SurfaceTexture_setSurfaceTexture(env, thiz, 0);
SurfaceTexture_setProducer(env, thiz, 0);
}
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index dfe5012..dd82fed 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.FloatRange;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
@@ -24,8 +25,10 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Trace;
import android.view.Surface;
import android.view.TextureView;
+import android.view.flags.Flags;
import java.lang.ref.WeakReference;
@@ -79,6 +82,7 @@
private final Looper mCreatorLooper;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private Handler mOnFrameAvailableHandler;
+ private Handler mOnSetFrameRateHandler;
/**
* These fields are used by native code, do not access or modify.
@@ -100,6 +104,21 @@
}
/**
+ * Callback interface for being notified that a producer set a frame rate
+ * @hide
+ */
+ public interface OnSetFrameRateListener {
+ /**
+ * Called when the producer sets a frame rate
+ * @hide
+ */
+ void onSetFrameRate(SurfaceTexture surfaceTexture,
+ @FloatRange(from = 0.0) float frameRate,
+ @Surface.FrameRateCompatibility int compatibility,
+ @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy);
+ }
+
+ /**
* Exception thrown when a SurfaceTexture couldn't be created or resized.
*
* @deprecated No longer thrown. {@link android.view.Surface.OutOfResourcesException}
@@ -224,6 +243,48 @@
}
}
+ private static class SetFrameRateArgs {
+ SetFrameRateArgs(@FloatRange(from = 0.0) float frameRate,
+ @Surface.FrameRateCompatibility int compatibility,
+ @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) {
+ this.mFrameRate = frameRate;
+ this.mCompatibility = compatibility;
+ this.mChangeFrameRateStrategy = changeFrameRateStrategy;
+ }
+ final float mFrameRate;
+ final int mCompatibility;
+ final int mChangeFrameRateStrategy;
+ }
+
+ /**
+ * Register a callback to be invoked when the producer sets a frame rate using
+ * Surface.setFrameRate.
+ * @hide
+ */
+ public void setOnSetFrameRateListener(@Nullable final OnSetFrameRateListener listener,
+ @Nullable Handler handler) {
+ if (listener != null) {
+ Looper looper = handler != null ? handler.getLooper() :
+ mCreatorLooper != null ? mCreatorLooper : Looper.getMainLooper();
+ mOnSetFrameRateHandler = new Handler(looper, null, true /*async*/) {
+ @Override
+ public void handleMessage(Message msg) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSetFrameRateHandler");
+ try {
+ SetFrameRateArgs args = (SetFrameRateArgs) msg.obj;
+ listener.onSetFrameRate(SurfaceTexture.this,
+ args.mFrameRate, args.mCompatibility,
+ args.mChangeFrameRateStrategy);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+ };
+ } else {
+ mOnSetFrameRateHandler = null;
+ }
+ }
+
/**
* Set the default size of the image buffers. The image producer may override the buffer size,
* in which case the producer-set buffer size will be used, not the default size set by this
@@ -418,6 +479,35 @@
}
/**
+ * This method is invoked from native code only.
+ * @hide
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ private static void postOnSetFrameRateEventFromNative(WeakReference<SurfaceTexture> weakSelf,
+ @FloatRange(from = 0.0) float frameRate,
+ @Surface.FrameRateCompatibility int compatibility,
+ @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "postOnSetFrameRateEventFromNative");
+ try {
+ if (Flags.toolkitSetFrameRate()) {
+ SurfaceTexture st = weakSelf.get();
+ if (st != null) {
+ Handler handler = st.mOnSetFrameRateHandler;
+ if (handler != null) {
+ Message msg = new Message();
+ msg.obj = new SetFrameRateArgs(frameRate, compatibility,
+ changeFrameRateStrategy);
+ handler.sendMessage(msg);
+ }
+ }
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+
+ }
+
+ /**
* Returns {@code true} if the SurfaceTexture is single-buffered.
* @hide
*/
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 3956241..96c257b 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -16,6 +16,7 @@
package android.security.keystore;
+import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,7 +35,10 @@
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.spec.AlgorithmParameterSpec;
+import java.util.Collections;
import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
@@ -300,6 +304,7 @@
private final Date mKeyValidityForConsumptionEnd;
private final @KeyProperties.PurposeEnum int mPurposes;
private final @KeyProperties.DigestEnum String[] mDigests;
+ private final @NonNull @KeyProperties.DigestEnum Set<String> mMgf1Digests;
private final @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings;
private final @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings;
private final @KeyProperties.BlockModeEnum String[] mBlockModes;
@@ -345,6 +350,7 @@
Date keyValidityForConsumptionEnd,
@KeyProperties.PurposeEnum int purposes,
@KeyProperties.DigestEnum String[] digests,
+ @KeyProperties.DigestEnum Set<String> mgf1Digests,
@KeyProperties.EncryptionPaddingEnum String[] encryptionPaddings,
@KeyProperties.SignaturePaddingEnum String[] signaturePaddings,
@KeyProperties.BlockModeEnum String[] blockModes,
@@ -404,6 +410,9 @@
mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
mPurposes = purposes;
mDigests = ArrayUtils.cloneIfNotEmpty(digests);
+ // No need to copy the input parameter because the Builder class passes in an immutable
+ // collection.
+ mMgf1Digests = mgf1Digests != null ? mgf1Digests : Collections.emptySet();
mEncryptionPaddings =
ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings));
mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings));
@@ -566,7 +575,7 @@
/**
* Returns the set of digest algorithms (e.g., {@code SHA-256}, {@code SHA-384} with which the
- * key can be used or {@code null} if not specified.
+ * key can be used.
*
* <p>See {@link KeyProperties}.{@code DIGEST} constants.
*
@@ -594,6 +603,40 @@
}
/**
+ * Returns the set of digests that can be used by the MGF1 mask generation function
+ * (e.g., {@code SHA-256}, {@code SHA-384}) with the key. Useful with the {@code RSA-OAEP}
+ * scheme.
+ * If not explicitly specified during key generation, the default {@code SHA-1} digest is
+ * used and may be specified when using the key.
+ *
+ * <p>See {@link KeyProperties}.{@code DIGEST} constants.
+ *
+ * @throws IllegalStateException if this set has not been specified.
+ *
+ * @see #isMgf1DigestsSpecified()
+ */
+ @NonNull
+ @FlaggedApi("MGF1_DIGEST_SETTER")
+ public @KeyProperties.DigestEnum Set<String> getMgf1Digests() {
+ if (mMgf1Digests.isEmpty()) {
+ throw new IllegalStateException("Mask generation function (MGF) not specified");
+ }
+ return new HashSet(mMgf1Digests);
+ }
+
+ /**
+ * Returns {@code true} if the set of digests for the MGF1 mask generation function,
+ * with which the key can be used, has been specified. Useful with the {@code RSA-OAEP} scheme.
+ *
+ * @see #getMgf1Digests()
+ */
+ @NonNull
+ @FlaggedApi("MGF1_DIGEST_SETTER")
+ public boolean isMgf1DigestsSpecified() {
+ return !mMgf1Digests.isEmpty();
+ }
+
+ /**
* Returns the set of padding schemes (e.g., {@code PKCS7Padding}, {@code OEAPPadding},
* {@code PKCS1Padding}, {@code NoPadding}) with which the key can be used when
* encrypting/decrypting. Attempts to use the key with any other padding scheme will be
@@ -913,6 +956,8 @@
private Date mKeyValidityForOriginationEnd;
private Date mKeyValidityForConsumptionEnd;
private @KeyProperties.DigestEnum String[] mDigests;
+ private @NonNull @KeyProperties.DigestEnum Set<String> mMgf1Digests =
+ Collections.emptySet();
private @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings;
private @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings;
private @KeyProperties.BlockModeEnum String[] mBlockModes;
@@ -983,6 +1028,9 @@
if (sourceSpec.isDigestsSpecified()) {
mDigests = sourceSpec.getDigests();
}
+ if (sourceSpec.isMgf1DigestsSpecified()) {
+ mMgf1Digests = sourceSpec.getMgf1Digests();
+ }
mEncryptionPaddings = sourceSpec.getEncryptionPaddings();
mSignaturePaddings = sourceSpec.getSignaturePaddings();
mBlockModes = sourceSpec.getBlockModes();
@@ -1230,6 +1278,30 @@
}
/**
+ * Sets the set of hash functions (e.g., {@code SHA-256}, {@code SHA-384}) which could be
+ * used by the mask generation function MGF1 (which is used for certain operations with
+ * the key). Attempts to use the key with any other digest for the mask generation
+ * function will be rejected.
+ *
+ * <p>This can only be specified for signing/verification keys and RSA encryption/decryption
+ * keys used with RSA OAEP padding scheme because these operations involve a mask generation
+ * function (MGF1) with a digest.
+ * The default digest for MGF1 is {@code SHA-1}, which will be specified during key creation
+ * time if no digests have been explicitly provided.
+ * When using the key, the caller may not specify any digests that were not provided during
+ * key creation time. The caller may specify the default digest, {@code SHA-1}, if no
+ * digests were explicitly provided during key creation (but it is not necessary to do so).
+ *
+ * <p>See {@link KeyProperties}.{@code DIGEST} constants.
+ */
+ @NonNull
+ @FlaggedApi("MGF1_DIGEST_SETTER")
+ public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) {
+ mMgf1Digests = Set.of(mgf1Digests);
+ return this;
+ }
+
+ /**
* Sets the set of padding schemes (e.g., {@code PKCS7Padding}, {@code OAEPPadding},
* {@code PKCS1Padding}, {@code NoPadding}) with which the key can be used when
* encrypting/decrypting. Attempts to use the key with any other padding scheme will be
@@ -1782,6 +1854,7 @@
mKeyValidityForConsumptionEnd,
mPurposes,
mDigests,
+ mMgf1Digests,
mEncryptionPaddings,
mSignaturePaddings,
mBlockModes,
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 5ab21bc..c1e3bab 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -16,6 +16,7 @@
package android.security.keystore;
+import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,7 +31,10 @@
import java.security.KeyStore.ProtectionParameter;
import java.security.Signature;
import java.security.cert.Certificate;
+import java.util.Collections;
import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.Mac;
@@ -223,6 +227,7 @@
private final @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings;
private final @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings;
private final @KeyProperties.DigestEnum String[] mDigests;
+ private final @NonNull @KeyProperties.DigestEnum Set<String> mMgf1Digests;
private final @KeyProperties.BlockModeEnum String[] mBlockModes;
private final boolean mRandomizedEncryptionRequired;
private final boolean mUserAuthenticationRequired;
@@ -247,6 +252,7 @@
@KeyProperties.EncryptionPaddingEnum String[] encryptionPaddings,
@KeyProperties.SignaturePaddingEnum String[] signaturePaddings,
@KeyProperties.DigestEnum String[] digests,
+ @KeyProperties.DigestEnum Set<String> mgf1Digests,
@KeyProperties.BlockModeEnum String[] blockModes,
boolean randomizedEncryptionRequired,
boolean userAuthenticationRequired,
@@ -271,6 +277,7 @@
mSignaturePaddings =
ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings));
mDigests = ArrayUtils.cloneIfNotEmpty(digests);
+ mMgf1Digests = mgf1Digests;
mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticationRequired = userAuthenticationRequired;
@@ -381,6 +388,40 @@
}
/**
+ * Returns the set of digests that can be used by the MGF1 mask generation function
+ * (e.g., {@code SHA-256}, {@code SHA-384}) with the key. Useful with the {@code RSA-OAEP}
+ * scheme.
+ * If not explicitly specified during key generation, the default {@code SHA-1} digest is
+ * used and may be specified.
+ *
+ * <p>See {@link KeyProperties}.{@code DIGEST} constants.
+ *
+ * @throws IllegalStateException if this set has not been specified.
+ *
+ * @see #isMgf1DigestsSpecified()
+ */
+ @NonNull
+ @FlaggedApi("MGF1_DIGEST_SETTER")
+ public @KeyProperties.DigestEnum Set<String> getMgf1Digests() {
+ if (mMgf1Digests.isEmpty()) {
+ throw new IllegalStateException("Mask generation function (MGF) not specified");
+ }
+ return new HashSet(mMgf1Digests);
+ }
+
+ /**
+ * Returns {@code true} if the set of digests for the MGF1 mask generation function,
+ * with which the key can be used, has been specified. Useful with the {@code RSA-OAEP} scheme.
+ *
+ * @see #getMgf1Digests()
+ */
+ @NonNull
+ @FlaggedApi("MGF1_DIGEST_SETTER")
+ public boolean isMgf1DigestsSpecified() {
+ return !mMgf1Digests.isEmpty();
+ }
+
+ /**
* Gets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be used
* when encrypting/decrypting. Attempts to use the key with any other block modes will be
* rejected.
@@ -588,6 +629,8 @@
private @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings;
private @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings;
private @KeyProperties.DigestEnum String[] mDigests;
+ private @NonNull @KeyProperties.DigestEnum Set<String> mMgf1Digests =
+ Collections.emptySet();
private @KeyProperties.BlockModeEnum String[] mBlockModes;
private boolean mRandomizedEncryptionRequired = true;
private boolean mUserAuthenticationRequired;
@@ -739,6 +782,30 @@
}
/**
+ * Sets the set of hash functions (e.g., {@code SHA-256}, {@code SHA-384}) which could be
+ * used by the mask generation function MGF1 (which is used for certain operations with
+ * the key). Attempts to use the key with any other digest for the mask generation
+ * function will be rejected.
+ *
+ * <p>This can only be specified for signing/verification keys and RSA encryption/decryption
+ * keys used with RSA OAEP padding scheme because these operations involve a mask generation
+ * function (MGF1) with a digest.
+ * The default digest for MGF1 is {@code SHA-1}, which will be specified during key import
+ * time if no digests have been explicitly provided.
+ * When using the key, the caller may not specify any digests that were not provided during
+ * key import time. The caller may specify the default digest, {@code SHA-1}, if no
+ * digests were explicitly provided during key import (but it is not necessary to do so).
+ *
+ * <p>See {@link KeyProperties}.{@code DIGEST} constants.
+ */
+ @NonNull
+ @FlaggedApi("MGF1_DIGEST_SETTER")
+ public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) {
+ mMgf1Digests = Set.of(mgf1Digests);
+ return this;
+ }
+
+ /**
* Sets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be
* used when encrypting/decrypting. Attempts to use the key with any other block modes will
* be rejected.
@@ -1141,6 +1208,7 @@
mEncryptionPaddings,
mSignaturePaddings,
mDigests,
+ mMgf1Digests,
mBlockModes,
mRandomizedEncryptionRequired,
mUserAuthenticationRequired,
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 9356eb8..ceba04e 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -23,7 +23,11 @@
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
+import java.util.List;
+import java.util.Set;
import javax.security.auth.x500.X500Principal;
@@ -91,6 +95,11 @@
} else {
out.writeStringArray(null);
}
+ if (mSpec.isMgf1DigestsSpecified()) {
+ out.writeStringList(List.copyOf(mSpec.getMgf1Digests()));
+ } else {
+ out.writeStringList(null);
+ }
out.writeStringArray(mSpec.getEncryptionPaddings());
out.writeStringArray(mSpec.getSignaturePaddings());
out.writeStringArray(mSpec.getBlockModes());
@@ -153,6 +162,7 @@
final Date keyValidityForOriginationEnd = readDateOrNull(in);
final Date keyValidityForConsumptionEnd = readDateOrNull(in);
final String[] digests = in.createStringArray();
+ final ArrayList<String> mgf1Digests = in.createStringArrayList();
final String[] encryptionPaddings = in.createStringArray();
final String[] signaturePaddings = in.createStringArray();
final String[] blockModes = in.createStringArray();
@@ -191,6 +201,7 @@
keyValidityForConsumptionEnd,
purposes,
digests,
+ mgf1Digests != null ? Set.copyOf(mgf1Digests) : Collections.emptySet(),
encryptionPaddings,
signaturePaddings,
blockModes,
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
index 9ac0f6d..101a10e 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
@@ -24,6 +24,7 @@
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyProperties;
import android.security.keystore.KeyStoreCryptoOperation;
import android.system.keystore2.Authorization;
@@ -71,7 +72,7 @@
*/
abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation {
private static final String TAG = "AndroidKeyStoreCipherSpiBase";
- public static final String DEFAULT_MGF1_DIGEST = "SHA-1";
+ public static final String DEFAULT_MGF1_DIGEST = KeyProperties.DIGEST_SHA1;
// Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
// doFinal finishes.
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 1398da3..ed4b485 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -188,6 +188,7 @@
private int[] mKeymasterEncryptionPaddings;
private int[] mKeymasterSignaturePaddings;
private int[] mKeymasterDigests;
+ private int[] mKeymasterMgf1Digests;
private Long mRSAPublicExponent;
@@ -323,6 +324,21 @@
} else {
mKeymasterDigests = EmptyArray.INT;
}
+ if (spec.isMgf1DigestsSpecified()) {
+ // User-specified digests: Add all of them and do _not_ add the SHA-1
+ // digest by default (stick to what the user provided).
+ Set<String> mgfDigests = spec.getMgf1Digests();
+ mKeymasterMgf1Digests = new int[mgfDigests.size()];
+ int offset = 0;
+ for (String digest : mgfDigests) {
+ mKeymasterMgf1Digests[offset] = KeyProperties.Digest.toKeymaster(digest);
+ offset++;
+ }
+ } else {
+ // No user-specified digests: Add the SHA-1 default.
+ mKeymasterMgf1Digests = new int[]{
+ KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)};
+ }
// Check that user authentication related parameters are acceptable. This method
// will throw an IllegalStateException if there are issues (e.g., secure lock screen
@@ -544,6 +560,7 @@
mKeymasterEncryptionPaddings = null;
mKeymasterSignaturePaddings = null;
mKeymasterDigests = null;
+ mKeymasterMgf1Digests = null;
mKeySizeBits = 0;
mSpec = null;
mRSAPublicExponent = null;
@@ -831,24 +848,11 @@
KeymasterDefs.KM_TAG_PADDING, padding
));
if (padding == KeymasterDefs.KM_PAD_RSA_OAEP) {
- final boolean[] hasDefaultMgf1DigestBeenAdded = {false};
- ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
+ ArrayUtils.forEach(mKeymasterMgf1Digests, (mgf1Digest) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
- KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, digest
+ KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mgf1Digest
));
- hasDefaultMgf1DigestBeenAdded[0] |=
- digest.equals(KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST));
});
- /* Because of default MGF1 digest is SHA-1. It has to be added in Key
- * characteristics. Otherwise, crypto operations will fail with Incompatible
- * MGF1 digest.
- */
- if (!hasDefaultMgf1DigestBeenAdded[0]) {
- params.add(KeyStore2ParameterUtils.makeEnum(
- KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
- KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)
- ));
- }
}
});
ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 273dff1..ddbd93e 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -526,25 +526,22 @@
padding
));
if (padding == KeymasterDefs.KM_PAD_RSA_OAEP) {
- if (spec.isDigestsSpecified()) {
- boolean hasDefaultMgf1DigestBeenAdded = false;
- for (String digest : spec.getDigests()) {
+ if (spec.isMgf1DigestsSpecified()) {
+ for (String mgf1Digest : spec.getMgf1Digests()) {
importArgs.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
- KeyProperties.Digest.toKeymaster(digest)
+ KeyProperties.Digest.toKeymaster(mgf1Digest)
));
- hasDefaultMgf1DigestBeenAdded |= digest.equals(DEFAULT_MGF1_DIGEST);
}
+ } else {
/* Because of default MGF1 digest is SHA-1. It has to be added in Key
* characteristics. Otherwise, crypto operations will fail with Incompatible
* MGF1 digest.
*/
- if (!hasDefaultMgf1DigestBeenAdded) {
- importArgs.add(KeyStore2ParameterUtils.makeEnum(
- KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
- KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)
- ));
- }
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
+ KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)
+ ));
}
}
}
diff --git a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
index 2ae61ab..d4e2dbc 100644
--- a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
+++ b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
@@ -17,6 +17,7 @@
package android.security;
import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@@ -101,6 +102,7 @@
assertThat(spec.getKeyValidityForOriginationEnd(), is(KEY_VALIDITY_FOR_ORIG_END));
assertThat(spec.getKeyValidityForConsumptionEnd(), is(KEY_VALIDITY_FOR_CONSUMPTION_END));
assertThat(spec.getDigests(), is(new String[] {DIGEST}));
+ assertThat(spec.isMgf1DigestsSpecified(), is(false));
assertThat(spec.getEncryptionPaddings(), is(new String[] {ENCRYPTION_PADDING}));
assertThat(spec.getSignaturePaddings(), is(new String[] {SIGNATURE_PADDING}));
assertThat(spec.getBlockModes(), is(new String[] {BLOCK_MODE}));
@@ -189,4 +191,19 @@
ECGenParameterSpec parcelSpec = (ECGenParameterSpec) fromParcel.getAlgorithmParameterSpec();
assertEquals(parcelSpec.getName(), ecSpec.getName());
}
+
+ @Test
+ public void testParcelingMgf1Digests() {
+ String[] mgf1Digests =
+ new String[] {KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA256};
+
+ ParcelableKeyGenParameterSpec spec = new ParcelableKeyGenParameterSpec(
+ new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES)
+ .setMgf1Digests(mgf1Digests)
+ .build());
+ Parcel parcel = parcelForReading(spec);
+ KeyGenParameterSpec fromParcel =
+ ParcelableKeyGenParameterSpec.CREATOR.createFromParcel(parcel).getSpec();
+ assertArrayEquals(fromParcel.getMgf1Digests().toArray(), mgf1Digests);
+ }
}
diff --git a/keystore/tests/src/android/security/keystore/KeyGenParameterSpecTest.java b/keystore/tests/src/android/security/keystore/KeyGenParameterSpecTest.java
index ddbb1d8..da5e8bf 100644
--- a/keystore/tests/src/android/security/keystore/KeyGenParameterSpecTest.java
+++ b/keystore/tests/src/android/security/keystore/KeyGenParameterSpecTest.java
@@ -16,9 +16,12 @@
package android.security.keystore;
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertThrows;
import android.security.ParcelableKeyGenParameterSpecTest;
@@ -61,4 +64,54 @@
assertEquals(copiedSpec.getAttestationChallenge(), null);
}
+
+ @Test
+ public void testMgf1DigestsNotSpecifiedByDefault() {
+ KeyGenParameterSpec spec = ParcelableKeyGenParameterSpecTest.configureDefaultSpec();
+ assertThat(spec.isMgf1DigestsSpecified(), is(false));
+ assertThrows(IllegalStateException.class, () -> {
+ spec.getMgf1Digests();
+ });
+ }
+
+ @Test
+ public void testMgf1DigestsCanBeSpecified() {
+ String[] mgf1Digests =
+ new String[] {KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA256};
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES)
+ .setMgf1Digests(mgf1Digests)
+ .build();
+ assertThat(spec.isMgf1DigestsSpecified(), is(true));
+ assertThat(spec.getMgf1Digests(), containsInAnyOrder(mgf1Digests));
+
+ KeyGenParameterSpec copiedSpec = new KeyGenParameterSpec.Builder(spec).build();
+ assertThat(copiedSpec.isMgf1DigestsSpecified(), is(true));
+ assertThat(copiedSpec.getMgf1Digests(), containsInAnyOrder(mgf1Digests));
+ }
+
+ @Test
+ public void testMgf1DigestsAreNotModified() {
+ String[] mgf1Digests =
+ new String[] {KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA256};
+ KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES)
+ .setMgf1Digests(mgf1Digests);
+
+ KeyGenParameterSpec firstSpec = builder.build();
+ assertArrayEquals(mgf1Digests, firstSpec.getMgf1Digests().toArray());
+
+ String[] otherDigests = new String[] {KeyProperties.DIGEST_SHA224};
+ KeyGenParameterSpec secondSpec = builder.setMgf1Digests(otherDigests).build();
+ assertThat(secondSpec.getMgf1Digests(), containsInAnyOrder(otherDigests));
+
+ // Now check that the first spec created hasn't changed.
+ assertThat(firstSpec.getMgf1Digests(), containsInAnyOrder(mgf1Digests));
+ }
+
+ @Test
+ public void testEmptyMgf1DigestsCanBeSet() {
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES)
+ .setMgf1Digests(new String[] {}).build();
+
+ assertThat(spec.isMgf1DigestsSpecified(), is(false));
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
index 6ea6516..72fc8686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
@@ -30,7 +30,7 @@
import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
-import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_ICON;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS;
import android.window.StartingWindowInfo;
@@ -52,7 +52,8 @@
final boolean processRunning = (parameter & TYPE_PARAMETER_PROCESS_RUNNING) != 0;
final boolean allowTaskSnapshot = (parameter & TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT) != 0;
final boolean activityCreated = (parameter & TYPE_PARAMETER_ACTIVITY_CREATED) != 0;
- final boolean allowIcon = (parameter & TYPE_PARAMETER_ALLOW_ICON) != 0;
+ final boolean isSolidColorSplashScreen =
+ (parameter & TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN) != 0;
final boolean legacySplashScreen =
((parameter & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN) != 0);
final boolean activityDrawn = (parameter & TYPE_PARAMETER_ACTIVITY_DRAWN) != 0;
@@ -66,13 +67,13 @@
+ "processRunning=%b, "
+ "allowTaskSnapshot=%b, "
+ "activityCreated=%b, "
- + "allowIcon=%b, "
+ + "isSolidColorSplashScreen=%b, "
+ "legacySplashScreen=%b, "
+ "activityDrawn=%b, "
+ "windowless=%b, "
+ "topIsHome=%b",
newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated,
- allowIcon, legacySplashScreen, activityDrawn, windowlessSurface,
+ isSolidColorSplashScreen, legacySplashScreen, activityDrawn, windowlessSurface,
topIsHome);
if (windowlessSurface) {
@@ -80,7 +81,7 @@
}
if (!topIsHome) {
if (!processRunning || newTask || (taskSwitch && !activityCreated)) {
- return getSplashscreenType(allowIcon, legacySplashScreen);
+ return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen);
}
}
@@ -94,18 +95,18 @@
}
}
if (!activityDrawn && !topIsHome) {
- return getSplashscreenType(allowIcon, legacySplashScreen);
+ return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen);
}
}
return STARTING_WINDOW_TYPE_NONE;
}
- private static int getSplashscreenType(boolean allowIcon, boolean legacySplashScreen) {
- if (allowIcon) {
- return legacySplashScreen ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ private static int getSplashscreenType(boolean solidColorSplashScreen,
+ boolean legacySplashScreen) {
+ return solidColorSplashScreen
+ ? STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN
+ : legacySplashScreen
+ ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
: STARTING_WINDOW_TYPE_SPLASH_SCREEN;
- } else {
- return STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
- }
}
}
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index d1ebe6d..1c3399a 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -72,6 +72,7 @@
mSessionValid = true;
mHintSession = nullptr;
}
+ mResetsSinceLastReport = 0;
}
bool HintSessionWrapper::init() {
@@ -109,12 +110,13 @@
tids.push_back(mUiThreadId);
tids.push_back(mRenderThreadId);
- // Use a placeholder target value to initialize,
- // this will always be replaced elsewhere before it gets used
- int64_t defaultTargetDurationNanos = 16666667;
+ // Use the cached target value if there is one, otherwise use a default. This is to ensure
+ // the cached target and target in PowerHAL are consistent, and that it updates correctly
+ // whenever there is a change.
+ int64_t targetDurationNanos =
+ mLastTargetWorkDuration == 0 ? kDefaultTargetDuration : mLastTargetWorkDuration;
mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] {
- return mBinding->createSession(manager, tids.data(), tids.size(),
- defaultTargetDurationNanos);
+ return mBinding->createSession(manager, tids.data(), tids.size(), targetDurationNanos);
});
return false;
}
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index 36e91ea..41891cd 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -65,6 +65,7 @@
static constexpr nsecs_t kResetHintTimeout = 100_ms;
static constexpr int64_t kSanityCheckLowerBound = 100_us;
static constexpr int64_t kSanityCheckUpperBound = 10_s;
+ static constexpr int64_t kDefaultTargetDuration = 16666667;
// Allows easier stub when testing
class HintSessionBinding {
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 94ed06c..f76ea06 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -17,6 +17,7 @@
#include "RenderThread.h"
#include <GrContextOptions.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <android-base/properties.h>
#include <dlfcn.h>
#include <gl/GrGLInterface.h>
@@ -286,7 +287,7 @@
auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
auto size = glesVersion ? strlen(glesVersion) : -1;
cacheManager().configureContext(&options, glesVersion, size);
- sk_sp<GrDirectContext> grContext(GrDirectContext::MakeGL(std::move(glInterface), options));
+ sk_sp<GrDirectContext> grContext(GrDirectContexts::MakeGL(std::move(glInterface), options));
LOG_ALWAYS_FATAL_IF(!grContext.get());
setGrContext(grContext);
}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 4eabfb2..8445032 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -26,6 +26,21 @@
srcs: [
"PointerController_test.cpp",
],
+ sanitize: {
+ hwaddress: true,
+ undefined: true,
+ all_undefined: true,
+ diag: {
+ undefined: true,
+ },
+ },
+ target: {
+ host: {
+ sanitize: {
+ address: true,
+ },
+ },
+ },
shared_libs: [
"libandroid_runtime",
"libinputservice",
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 94faf4a..d9efd3c 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -148,6 +148,25 @@
latestPointerDisplayId = displayId;
}
+class TestPointerController : public PointerController {
+public:
+ TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener,
+ sp<PointerControllerPolicyInterface> policy, const sp<Looper>& looper,
+ SpriteController& spriteController)
+ : PointerController(
+ policy, looper, spriteController,
+ /*enabled=*/true,
+ [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
+ // Register listener
+ registeredListener = listener;
+ },
+ [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
+ // Unregister listener
+ if (registeredListener == listener) registeredListener = nullptr;
+ }) {}
+ ~TestPointerController() override {}
+};
+
class PointerControllerTest : public Test {
protected:
PointerControllerTest();
@@ -159,6 +178,7 @@
sp<MockPointerControllerPolicyInterface> mPolicy;
std::unique_ptr<MockSpriteController> mSpriteController;
std::shared_ptr<PointerController> mPointerController;
+ sp<android::gui::WindowInfosListener> mRegisteredListener;
private:
void loopThread();
@@ -181,11 +201,12 @@
EXPECT_CALL(*mSpriteController, createSprite())
.WillOnce(Return(mPointerSprite));
- mPointerController =
- PointerController::create(mPolicy, mLooper, *mSpriteController, /*enabled=*/true);
+ mPointerController = std::make_unique<TestPointerController>(mRegisteredListener, mPolicy,
+ mLooper, *mSpriteController);
}
PointerControllerTest::~PointerControllerTest() {
+ mPointerController.reset();
mRunning.store(false, std::memory_order_relaxed);
mThread.join();
}
@@ -316,31 +337,16 @@
class PointerControllerWindowInfoListenerTest : public Test {};
-class TestPointerController : public PointerController {
-public:
- TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener,
- const sp<Looper>& looper, SpriteController& spriteController)
- : PointerController(
- new MockPointerControllerPolicyInterface(), looper, spriteController,
- /*enabled=*/true,
- [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
- // Register listener
- registeredListener = listener;
- },
- [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
- // Unregister listener
- if (registeredListener == listener) registeredListener = nullptr;
- }) {}
-};
-
TEST_F(PointerControllerWindowInfoListenerTest,
doesNotCrashIfListenerCalledAfterPointerControllerDestroyed) {
sp<Looper> looper = new Looper(false);
auto spriteController = NiceMock<MockSpriteController>(looper);
sp<android::gui::WindowInfosListener> registeredListener;
sp<android::gui::WindowInfosListener> localListenerCopy;
+ sp<MockPointerControllerPolicyInterface> policy = new MockPointerControllerPolicyInterface();
{
- TestPointerController pointerController(registeredListener, looper, spriteController);
+ TestPointerController pointerController(registeredListener, policy, looper,
+ spriteController);
ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered";
localListenerCopy = registeredListener;
}
diff --git a/mime/Android.bp b/mime/Android.bp
index a3ea65c..757862b 100644
--- a/mime/Android.bp
+++ b/mime/Android.bp
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
@@ -125,6 +124,6 @@
srcs: [
"java-res/vendor.mime.types",
],
- // strip comments normalize whitepace drop empty lines prepend ? to fields that are missing it
- cmd: "awk '{gsub(/#.*$$/,\"\"); $$1=$$1; print;}' $(in) | grep ' ' | awk '{for(i=1;i<=NF;i++) { sub(/^\\??/, \"?\", $$i); }; print}' > $(out)",
+ // strip comments normalize whitepace drop empty lines prepend ? to fields that are missing it
+ cmd: "awk '{gsub(/#.*$$/,\"\"); $$1=$$1; print;}' $(in) | (grep ' ' || echo -n '') | awk '{for(i=1;i<=NF;i++) { sub(/^\\??/, \"?\", $$i); }; print}' > $(out)",
}
diff --git a/packages/CredentialManager/shared/Android.bp b/packages/CredentialManager/shared/Android.bp
index 38d98a9..0d4af2a 100644
--- a/packages/CredentialManager/shared/Android.bp
+++ b/packages/CredentialManager/shared/Android.bp
@@ -12,6 +12,7 @@
manifest: "AndroidManifest.xml",
srcs: ["src/**/*.kt"],
static_libs: [
+ "androidx.activity_activity-compose",
"androidx.core_core-ktx",
"androidx.credentials_credentials",
"guava",
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ApiConstants.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ApiConstants.kt
new file mode 100644
index 0000000..6498ff7
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ApiConstants.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager
+
+const val IS_AUTO_SELECTED_KEY = "IS_AUTO_SELECTED"
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
index defba8d..8986e52 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
@@ -17,14 +17,25 @@
package com.android.credentialmanager
import android.content.Intent
+import android.content.pm.PackageManager
import android.credentials.ui.RequestInfo
import com.android.credentialmanager.ktx.requestInfo
import com.android.credentialmanager.mapper.toGet
import com.android.credentialmanager.mapper.toRequestCancel
+import com.android.credentialmanager.mapper.toRequestClose
import com.android.credentialmanager.model.Request
-fun Intent.parse(): Request {
- this.toRequestCancel()?.let { return it }
+fun Intent.parse(
+ packageManager: PackageManager,
+ previousIntent: Intent? = null,
+): Request {
+ this.toRequestClose(packageManager, previousIntent)?.let { closeRequest ->
+ return closeRequest
+ }
+
+ this.toRequestCancel(packageManager)?.let { cancelRequest ->
+ return cancelRequest
+ }
return when (requestInfo?.type) {
RequestInfo.TYPE_CREATE -> {
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/activity/StartBalIntentSenderForResultContract.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/activity/StartBalIntentSenderForResultContract.kt
new file mode 100644
index 0000000..ef083fd
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/activity/StartBalIntentSenderForResultContract.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 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.credentialmanager.activity
+
+import android.app.ActivityOptions
+import android.content.Context
+import android.content.Intent
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.IntentSenderRequest
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.activity.result.contract.ActivityResultContracts
+
+/**
+ * A custom StartIntentSenderForResult contract implementation that attaches an [ActivityOptions]
+ * that opts in for background activity launch.
+ */
+class StartBalIntentSenderForResultContract :
+ ActivityResultContract<IntentSenderRequest, ActivityResult>() {
+ override fun createIntent(context: Context, input: IntentSenderRequest): Intent {
+ val activityOptionBundle =
+ ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ ).toBundle()
+ return Intent(
+ ActivityResultContracts.StartIntentSenderForResult.ACTION_INTENT_SENDER_REQUEST
+ ).putExtra(
+ ActivityResultContracts.StartActivityForResult.EXTRA_ACTIVITY_OPTIONS_BUNDLE,
+ activityOptionBundle
+ ).putExtra(
+ ActivityResultContracts.StartIntentSenderForResult.EXTRA_INTENT_SENDER_REQUEST,
+ input
+ )
+ }
+
+ override fun parseResult(
+ resultCode: Int,
+ intent: Intent?
+ ): ActivityResult = ActivityResult(resultCode, intent)
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index a4c20bf..4533db6 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -18,10 +18,12 @@
import android.content.Intent
import android.credentials.ui.CancelUiRequest
+import android.credentials.ui.Constants
import android.credentials.ui.CreateCredentialProviderData
import android.credentials.ui.GetCredentialProviderData
import android.credentials.ui.ProviderData
import android.credentials.ui.RequestInfo
+import android.os.ResultReceiver
val Intent.cancelUiRequest: CancelUiRequest?
get() = this.extras?.getParcelable(
@@ -46,3 +48,9 @@
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
CreateCredentialProviderData::class.java
) ?: emptyList()
+
+val Intent.resultReceiver: ResultReceiver?
+ get() = this.getParcelableExtra(
+ Constants.EXTRA_RESULT_RECEIVER,
+ ResultReceiver::class.java
+ )
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt
index 86a6d23..555a86f 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt
@@ -17,13 +17,23 @@
package com.android.credentialmanager.mapper
import android.content.Intent
+import android.content.pm.PackageManager
+import android.util.Log
+import com.android.credentialmanager.TAG
+import com.android.credentialmanager.ktx.appLabel
import com.android.credentialmanager.ktx.cancelUiRequest
import com.android.credentialmanager.model.Request
-fun Intent.toRequestCancel(): Request.Cancel? =
+fun Intent.toRequestCancel(packageManager: PackageManager): Request.Cancel? =
this.cancelUiRequest?.let { cancelUiRequest ->
- Request.Cancel(
- showCancellationUi = cancelUiRequest.shouldShowCancellationUi(),
- appPackageName = cancelUiRequest.appPackageName
- )
+ val appLabel = packageManager.appLabel(cancelUiRequest.appPackageName)
+ if (appLabel == null) {
+ Log.d(TAG, "Received UI cancel request with an invalid package name.")
+ null
+ } else {
+ Request.Cancel(
+ showCancellationUi = cancelUiRequest.shouldShowCancellationUi(),
+ appName = appLabel
+ )
+ }
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt
new file mode 100644
index 0000000..6de3e7d
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager.mapper
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import com.android.credentialmanager.ktx.requestInfo
+import com.android.credentialmanager.model.Request
+
+fun Intent.toRequestClose(
+ packageManager: PackageManager,
+ previousIntent: Intent? = null,
+): Request.Close? {
+ // Close request comes as "Cancel" request from Credential Manager API
+ val currentRequest = toRequestCancel(packageManager = packageManager) ?: return null
+
+ if (currentRequest.showCancellationUi) {
+ // Current request is to Cancel and not to Close
+ return null
+ }
+
+ previousIntent?.let {
+ val previousToken = previousIntent.requestInfo?.token
+ val currentToken = this.requestInfo?.token
+
+ if (previousToken != currentToken) {
+ // Current cancellation is for a different request, don't close the current flow.
+ return null
+ }
+ }
+
+ return Request.Close
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
index ed9d563..ee45fbb 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
@@ -1,22 +1,66 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager.mapper
import android.content.Intent
+import android.credentials.ui.Entry
import android.credentials.ui.GetCredentialProviderData
+import androidx.credentials.provider.PasswordCredentialEntry
+import com.android.credentialmanager.factory.fromSlice
import com.android.credentialmanager.ktx.getCredentialProviderDataList
+import com.android.credentialmanager.ktx.requestInfo
+import com.android.credentialmanager.ktx.resultReceiver
+import com.android.credentialmanager.model.Password
import com.android.credentialmanager.model.Request
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
-fun Intent.toGet() = Request.Get(
- providers = ImmutableMap.copyOf(
- getCredentialProviderDataList.associateBy { it.providerFlattenedComponentName }
- ),
- entries = ImmutableList.copyOf(
- getCredentialProviderDataList.map { providerData ->
- check(providerData is GetCredentialProviderData) {
- "Invalid provider data type for GetCredentialRequest"
+fun Intent.toGet(): Request.Get {
+ val credentialEntries = mutableListOf<Pair<String, Entry>>()
+ for (providerData in getCredentialProviderDataList) {
+ if (providerData is GetCredentialProviderData) {
+ for (credentialEntry in providerData.credentialEntries) {
+ credentialEntries.add(
+ Pair(providerData.providerFlattenedComponentName, credentialEntry)
+ )
}
- providerData
- }.flatMap { it.credentialEntries }
+ }
+ }
+
+ val passwordEntries = mutableListOf<Password>()
+ for ((providerId, entry) in credentialEntries) {
+ val slice = fromSlice(entry.slice)
+ if (slice is PasswordCredentialEntry) {
+ passwordEntries.add(
+ Password(
+ providerId = providerId,
+ entry = entry,
+ passwordCredentialEntry = slice
+ )
+ )
+ }
+ }
+
+ return Request.Get(
+ token = requestInfo?.token,
+ resultReceiver = this.resultReceiver,
+ providers = ImmutableMap.copyOf(
+ getCredentialProviderDataList.associateBy { it.providerFlattenedComponentName }
+ ),
+ passwordEntries = ImmutableList.copyOf(passwordEntries)
)
-)
\ No newline at end of file
+}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt
new file mode 100644
index 0000000..2fe4fd5
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager.model
+
+import android.credentials.ui.Entry
+import androidx.credentials.provider.PasswordCredentialEntry
+
+data class Password(
+ val providerId: String,
+ val entry: Entry,
+ val passwordCredentialEntry: PasswordCredentialEntry,
+)
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
index bc07310..6011a1c 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
@@ -16,8 +16,9 @@
package com.android.credentialmanager.model
-import android.credentials.ui.Entry
import android.credentials.ui.ProviderData
+import android.os.IBinder
+import android.os.ResultReceiver
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
@@ -25,15 +26,33 @@
* Represents the request made by the CredentialManager API.
*/
sealed class Request {
+
+ /**
+ * Request to close the app without displaying a message to the user and without reporting
+ * anything back to the Credential Manager service.
+ */
+ data object Close : Request()
+
+ /**
+ * Request to close the app, displaying a message to the user.
+ */
data class Cancel(
val showCancellationUi: Boolean,
- val appPackageName: String?
+ val appName: String
) : Request()
+ /**
+ * Request to start the get credentials flow.
+ */
data class Get(
+ val token: IBinder?,
+ val resultReceiver: ResultReceiver?,
val providers: ImmutableMap<String, ProviderData>,
- val entries: ImmutableList<Entry>,
+ val passwordEntries: ImmutableList<Password>,
) : Request()
+ /**
+ * Request to start the create credentials flow.
+ */
data object Create : Request()
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt
new file mode 100644
index 0000000..5ab5ab9
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager.repository
+
+import android.app.Application
+import android.content.Intent
+import android.util.Log
+import com.android.credentialmanager.TAG
+import com.android.credentialmanager.model.Request
+import com.android.credentialmanager.parse
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class RequestRepository(
+ private val application: Application,
+) {
+
+ private val _requests = MutableStateFlow<Request?>(null)
+ val requests: StateFlow<Request?> = _requests
+
+ suspend fun processRequest(intent: Intent, previousIntent: Intent? = null) {
+ val request = intent.parse(
+ packageManager = application.packageManager,
+ previousIntent = previousIntent
+ )
+
+ Log.d(TAG, "Request parsed: $request")
+
+ _requests.value = request
+ }
+}
diff --git a/packages/CredentialManager/wear/Android.bp b/packages/CredentialManager/wear/Android.bp
index c0dff16..e5f5cc2 100644
--- a/packages/CredentialManager/wear/Android.bp
+++ b/packages/CredentialManager/wear/Android.bp
@@ -37,6 +37,7 @@
"androidx.lifecycle_lifecycle-extensions",
"androidx.lifecycle_lifecycle-livedata",
"androidx.lifecycle_lifecycle-runtime-ktx",
+ "androidx.lifecycle_lifecycle-runtime-compose",
"androidx.lifecycle_lifecycle-viewmodel-compose",
"androidx.wear.compose_compose-foundation",
"androidx.wear.compose_compose-material",
diff --git a/packages/CredentialManager/wear/AndroidManifest.xml b/packages/CredentialManager/wear/AndroidManifest.xml
index 90248734..b480ac3 100644
--- a/packages/CredentialManager/wear/AndroidManifest.xml
+++ b/packages/CredentialManager/wear/AndroidManifest.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+
<!--
/*
* Copyright (c) 2023 Google Inc.
@@ -21,25 +22,27 @@
<uses-feature android:name="android.hardware.type.watch" />
- <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
- <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
+ <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS" />
<application
- android:allowBackup="true"
- android:dataExtractionRules="@xml/data_extraction_rules"
- android:fullBackupContent="@xml/backup_rules"
- android:label="@string/app_name"
- android:supportsRtl="true">
+ android:name=".CredentialSelectorApp"
+ android:allowBackup="true"
+ android:dataExtractionRules="@xml/data_extraction_rules"
+ android:fullBackupContent="@xml/backup_rules"
+ android:label="@string/app_name"
+ android:supportsRtl="true">
+ <!-- Activity called by GMS has to be exactly:
+ com.android.credentialmanager.CredentialSelectorActivity -->
<activity
- android:name=".ui.CredentialSelectorActivity"
+ android:name=".CredentialSelectorActivity"
+ android:excludeFromRecents="true"
android:exported="true"
- android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR"
- android:launchMode="singleTop"
android:label="@string/app_name"
- android:excludeFromRecents="true">
- </activity>
- </application>
+ android:launchMode="singleTop"
+ android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR" />
+ </application>
</manifest>
diff --git a/packages/CredentialManager/wear/res/values/themes.xml b/packages/CredentialManager/wear/res/values/themes.xml
deleted file mode 100644
index 22329e9f..0000000
--- a/packages/CredentialManager/wear/res/values/themes.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-<resources>
- <style name="Theme.CredentialSelector" parent="@*android:style/ThemeOverlay.DeviceDefault.Accent.DayNight">
- <item name="android:windowContentOverlay">@null</item>
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowIsTranslucent">true</item>
- </style>
-</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
new file mode 100644
index 0000000..273d0b1
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.wear.compose.material.MaterialTheme
+import com.android.credentialmanager.ui.WearApp
+import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen
+import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import com.google.android.horologist.compose.layout.belowTimeTextPreview
+import kotlinx.coroutines.launch
+
+class CredentialSelectorActivity : ComponentActivity() {
+
+ private val viewModel: CredentialSelectorViewModel by viewModels {
+ CredentialSelectorViewModel.Factory
+ }
+
+ @OptIn(ExperimentalHorologistApi::class)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setTheme(android.R.style.Theme_DeviceDefault)
+
+ // TODO: b/301027810 due to this issue with compose in Main platform, we are implementing a
+ // workaround. Once the issue is fixed, remove the "else" bracket and leave only the
+ // contents of the "if" bracket.
+ if (false) {
+ setContent {
+ MaterialTheme {
+ WearApp(
+ viewModel = viewModel,
+ onCloseApp = ::finish,
+ )
+ }
+ }
+ } else {
+ // TODO: b/301027810 Remove the content of this "else" bracket fully once issue is fixed
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.uiState.collect { uiState ->
+ when (uiState) {
+ CredentialSelectorUiState.Idle -> {
+ // Don't display anything, assuming that there should be minimal latency
+ // to parse the Credential Manager intent and define the state of the
+ // app. If latency is big, then a "loading" screen should be displayed
+ // to the user.
+ }
+
+ is CredentialSelectorUiState.Get -> {
+ setContent {
+ MaterialTheme {
+ SinglePasswordScreen(
+ columnState = belowTimeTextPreview(),
+ onCloseApp = ::finish,
+ )
+ }
+ }
+ }
+
+ else -> finish()
+ }
+ }
+ }
+ }
+ }
+
+ viewModel.onNewIntent(intent)
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+
+ val previousIntent = getIntent()
+ setIntent(intent)
+
+ viewModel.onNewIntent(intent, previousIntent)
+ }
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
new file mode 100644
index 0000000..7c81fd0
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager
+
+import android.app.Application
+import com.android.credentialmanager.di.inject
+import com.android.credentialmanager.repository.RequestRepository
+
+class CredentialSelectorApp : Application() {
+
+ lateinit var requestRepository: RequestRepository
+
+ override fun onCreate() {
+ super.onCreate()
+
+ inject()
+ }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
new file mode 100644
index 0000000..d557dc0
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager
+
+import android.content.Intent
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.CreationExtras
+import com.android.credentialmanager.model.Request
+import com.android.credentialmanager.repository.RequestRepository
+import com.android.credentialmanager.ui.mappers.toGet
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+class CredentialSelectorViewModel(
+ private val requestRepository: RequestRepository,
+) : ViewModel() {
+
+ val uiState: StateFlow<CredentialSelectorUiState> = requestRepository.requests
+ .map { request ->
+ when (request) {
+ null -> CredentialSelectorUiState.Idle
+ is Request.Cancel -> CredentialSelectorUiState.Cancel(request.appName)
+ Request.Close -> CredentialSelectorUiState.Close
+ Request.Create -> CredentialSelectorUiState.Create
+ is Request.Get -> request.toGet()
+ }
+ }
+ .stateIn(
+ viewModelScope,
+ started = SharingStarted.WhileSubscribed(5000),
+ initialValue = CredentialSelectorUiState.Idle,
+ )
+
+ fun onNewIntent(intent: Intent, previousIntent: Intent? = null) {
+ viewModelScope.launch {
+ requestRepository.processRequest(intent = intent, previousIntent = previousIntent)
+ }
+ }
+
+ companion object {
+ val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(
+ modelClass: Class<T>,
+ extras: CreationExtras
+ ): T {
+ val application = checkNotNull(extras[APPLICATION_KEY])
+
+ return CredentialSelectorViewModel(
+ requestRepository = (application as CredentialSelectorApp).requestRepository,
+ ) as T
+ }
+ }
+ }
+}
+
+sealed class CredentialSelectorUiState {
+ data object Idle : CredentialSelectorUiState()
+ sealed class Get : CredentialSelectorUiState() {
+ data object SingleProviderSinglePasskey : Get()
+ data object SingleProviderSinglePassword : Get()
+
+ // TODO: b/301206470 add the remaining states
+ }
+
+ data object Create : CredentialSelectorUiState()
+ data class Cancel(val appName: String) : CredentialSelectorUiState()
+ data object Close : CredentialSelectorUiState()
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt
new file mode 100644
index 0000000..a11017b
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt
@@ -0,0 +1,17 @@
+package com.android.credentialmanager.di
+
+import android.app.Application
+import com.android.credentialmanager.CredentialSelectorApp
+import com.android.credentialmanager.repository.RequestRepository
+
+// TODO b/301601582 add Hilt for dependency injection
+
+fun CredentialSelectorApp.inject() {
+ requestRepository = requestRepository(application = this)
+}
+
+private fun requestRepository(
+ application: Application,
+): RequestRepository = RequestRepository(
+ application = application,
+)
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt
deleted file mode 100644
index 53122ba..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2023 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.0N
- *
- * 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.credentialmanager.ui
-
-import android.content.Intent
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.viewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import androidx.wear.compose.material.MaterialTheme
-import kotlinx.coroutines.launch
-
-class CredentialSelectorActivity : ComponentActivity() {
-
- private val viewModel: CredentialSelectorViewModel by viewModels()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setTheme(android.R.style.Theme_DeviceDefault)
-
- lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.uiState.collect { uiState ->
- when (uiState) {
- CredentialSelectorUiState.Idle -> {
- // Don't display anything, assuming that there should be minimal latency
- // to parse the Credential Manager intent and define the state of the
- // app. If latency is big, then a "loading" screen should be displayed
- // to the user.
- }
-
- is CredentialSelectorUiState.Get -> {
- setContent {
- MaterialTheme {
- WearApp()
- }
- }
- }
-
- CredentialSelectorUiState.Create -> {
- // TODO: b/301206624 - Implement create flow
- finish()
- }
-
- is CredentialSelectorUiState.Cancel -> {
- // TODO: b/300422310 - Implement cancel with message flow
- finish()
- }
-
- CredentialSelectorUiState.Finish -> {
- finish()
- }
- }
- }
- }
- }
-
- viewModel.onNewIntent(intent)
- }
-
- override fun onNewIntent(intent: Intent) {
- super.onNewIntent(intent)
-
- val previousIntent = getIntent()
- setIntent(intent)
-
- viewModel.onNewIntent(intent, previousIntent)
- }
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt
deleted file mode 100644
index d22d5d1..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2023 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.0N
- *
- * 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.credentialmanager.ui
-
-import android.app.Application
-import android.content.Intent
-import android.util.Log
-import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.viewModelScope
-import com.android.credentialmanager.TAG
-import com.android.credentialmanager.parse
-import com.android.credentialmanager.ktx.appLabel
-import com.android.credentialmanager.ktx.requestInfo
-import com.android.credentialmanager.mapper.toGet
-import com.android.credentialmanager.ui.model.PasskeyUiModel
-import com.android.credentialmanager.ui.model.PasswordUiModel
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.ui.mapper.toGet
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.launch
-
-class CredentialSelectorViewModel(
- private val application: Application
-) : AndroidViewModel(application = application) {
-
- private val _uiState =
- MutableStateFlow<CredentialSelectorUiState>(CredentialSelectorUiState.Idle)
- val uiState: StateFlow<CredentialSelectorUiState> = _uiState
-
- fun onNewIntent(intent: Intent, previousIntent: Intent? = null) {
- viewModelScope.launch {
- val request = intent.parse()
- if (shouldFinishActivity(request = request, previousIntent = previousIntent)) {
- _uiState.value = CredentialSelectorUiState.Finish
- } else {
- when (request) {
- is Request.Cancel -> {
- request.appPackageName?.let { appPackageName ->
- application.packageManager.appLabel(appPackageName)?.let { appLabel ->
- _uiState.value = CredentialSelectorUiState.Cancel(appLabel)
- } ?: run {
- Log.d(TAG,
- "Received UI cancel request with an invalid package name.")
- _uiState.value = CredentialSelectorUiState.Finish
- }
- } ?: run {
- Log.d(TAG, "Received UI cancel request with an invalid package name.")
- _uiState.value = CredentialSelectorUiState.Finish
- }
- }
-
- Request.Create -> {
- _uiState.value = CredentialSelectorUiState.Create
- }
-
- is Request.Get -> {
- _uiState.value = request.toGet()
- }
- }
- }
- }
- }
-
- /**
- * Check if backend requested the UI activity to be cancelled. Different from the other
- * finishing flows, this one does not report anything back to the Credential Manager service
- * backend.
- */
- private fun shouldFinishActivity(request: Request, previousIntent: Intent? = null): Boolean {
- if (request !is Request.Cancel) {
- return false
- } else {
- Log.d(
- TAG, "Received UI cancellation intent. Should show cancellation" +
- " ui = ${request.showCancellationUi}")
-
- previousIntent?.let {
- val previousUiRequest = previousIntent.parse()
-
- if (previousUiRequest is Request.Cancel) {
- val previousToken = previousIntent.requestInfo?.token
- val currentToken = previousIntent.requestInfo?.token
-
- if (previousToken != currentToken) {
- // Cancellation was for a different request, don't cancel the current UI.
- return false
- }
- }
- }
-
- return !request.showCancellationUi
- }
- }
-}
-
-sealed class CredentialSelectorUiState {
- data object Idle : CredentialSelectorUiState()
- sealed class Get : CredentialSelectorUiState() {
- data class SingleProviderSinglePasskey(val passkeyUiModel: PasskeyUiModel) : Get()
- data class SingleProviderSinglePassword(val passwordUiModel: PasswordUiModel) : Get()
-
- // TODO: b/301206470 add the remaining states
- }
-
- data object Create : CredentialSelectorUiState()
- data class Cancel(val appName: String) : CredentialSelectorUiState()
- data object Finish : CredentialSelectorUiState()
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Navigation.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Navigation.kt
new file mode 100644
index 0000000..da5697d
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Navigation.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager.ui
+
+import androidx.navigation.NavController
+
+fun NavController.navigateToLoading() {
+ navigate(Screen.Loading.route)
+}
+
+fun NavController.navigateToSinglePasswordScreen() {
+ navigate(Screen.SinglePasswordScreen.route)
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt
index 7d1a49b..c3919a0 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt
@@ -19,5 +19,7 @@
sealed class Screen(
val route: String,
) {
- data object Main : Screen("main")
+ data object Loading : Screen("loading")
+
+ data object SinglePasswordScreen : Screen("singlePasswordScreen")
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index 19ea9ed..7e0ea30 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -19,28 +19,94 @@
package com.android.credentialmanager.ui
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.navigation.NavController
import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
-import com.android.credentialmanager.ui.screens.MainScreen
+import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.CredentialSelectorViewModel
+import com.android.credentialmanager.ui.screens.LoadingScreen
+import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.navscaffold.WearNavScaffold
import com.google.android.horologist.compose.navscaffold.composable
+import com.google.android.horologist.compose.navscaffold.scrollable
@Composable
-fun WearApp() {
+fun WearApp(
+ viewModel: CredentialSelectorViewModel,
+ onCloseApp: () -> Unit,
+) {
val navController = rememberSwipeDismissableNavController()
val swipeToDismissBoxState = rememberSwipeToDismissBoxState()
val navHostState =
rememberSwipeDismissableNavHostState(swipeToDismissBoxState = swipeToDismissBoxState)
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
WearNavScaffold(
- startDestination = Screen.Main.route,
+ startDestination = Screen.Loading.route,
navController = navController,
state = navHostState,
) {
- composable(Screen.Main.route) {
- MainScreen()
+ composable(Screen.Loading.route) {
+ LoadingScreen()
+ }
+
+ scrollable(Screen.SinglePasswordScreen.route) {
+ SinglePasswordScreen(
+ columnState = it.columnState,
+ onCloseApp = onCloseApp,
+ )
+ }
+ }
+
+ when (val state = uiState) {
+ CredentialSelectorUiState.Idle -> {
+ if (navController.currentDestination?.route != Screen.Loading.route) {
+ navController.navigateToLoading()
+ }
+ }
+
+ is CredentialSelectorUiState.Get -> {
+ handleGetNavigation(
+ navController = navController,
+ state = state,
+ onCloseApp = onCloseApp,
+ )
+ }
+
+ CredentialSelectorUiState.Create -> {
+ // TODO: b/301206624 - Implement create flow
+ onCloseApp()
+ }
+
+ is CredentialSelectorUiState.Cancel -> {
+ // TODO: b/300422310 - Implement cancel with message flow
+ onCloseApp()
+ }
+
+ CredentialSelectorUiState.Close -> {
+ onCloseApp()
+ }
+ }
+}
+
+private fun handleGetNavigation(
+ navController: NavController,
+ state: CredentialSelectorUiState.Get,
+ onCloseApp: () -> Unit,
+) {
+ when (state) {
+ is CredentialSelectorUiState.Get.SingleProviderSinglePassword -> {
+ navController.navigateToSinglePasswordScreen()
+ }
+
+ else -> {
+ // TODO: b/301206470 - Implement other get flows
+ onCloseApp()
}
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt
deleted file mode 100644
index 5ceec178..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.android.credentialmanager.ui.mapper
-
-import androidx.credentials.provider.CustomCredentialEntry
-import androidx.credentials.provider.PasswordCredentialEntry
-import androidx.credentials.provider.PublicKeyCredentialEntry
-import com.android.credentialmanager.ui.CredentialSelectorUiState
-import com.android.credentialmanager.factory.fromSlice
-import com.android.credentialmanager.ui.model.PasswordUiModel
-import com.android.credentialmanager.model.Request
-
-fun Request.Get.toGet(): CredentialSelectorUiState.Get {
- if (this.providers.isEmpty()) {
- throw IllegalStateException("Invalid GetCredential request with empty list of providers.")
- }
-
- if (this.entries.isEmpty()) {
- throw IllegalStateException("Invalid GetCredential request with empty list of entries.")
- }
-
- if (this.providers.size == 1) {
- if (this.entries.size == 1) {
- val slice = this.entries.first().slice
- when (val credentialEntry = fromSlice(slice)) {
- is PasswordCredentialEntry -> {
- return CredentialSelectorUiState.Get.SingleProviderSinglePassword(
- PasswordUiModel(credentialEntry.displayName.toString())
- )
- }
-
- is PublicKeyCredentialEntry -> {
- TODO("b/301206470 - to be implemented")
- }
-
- is CustomCredentialEntry -> {
- TODO("b/301206470 - to be implemented")
- }
-
- else -> {
- throw IllegalStateException(
- "Encountered unrecognized credential entry (${slice.spec?.type}) for " +
- "GetCredential request with single account"
- )
- }
- }
- } else {
- TODO("b/301206470 - to be implemented")
- }
- } else {
- TODO("b/301206470 - to be implemented")
- }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
new file mode 100644
index 0000000..f2f878e
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager.ui.mappers
+
+import com.android.credentialmanager.model.Request
+import com.android.credentialmanager.CredentialSelectorUiState
+
+fun Request.Get.toGet(): CredentialSelectorUiState.Get {
+ // TODO: b/301206470 returning a hard coded state for MVP
+ if (true) return CredentialSelectorUiState.Get.SingleProviderSinglePassword
+
+ return if (providers.size == 1) {
+ if (passwordEntries.size == 1) {
+ CredentialSelectorUiState.Get.SingleProviderSinglePassword
+ } else {
+ TODO() // b/301206470 - Implement other get flows
+ }
+ } else {
+ TODO() // b/301206470 - Implement other get flows
+ }
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/LoadingScreen.kt
similarity index 74%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt
rename to packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/LoadingScreen.kt
index 94a671e..b3ab0c4 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/LoadingScreen.kt
@@ -16,17 +16,15 @@
package com.android.credentialmanager.ui.screens
-import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.wear.compose.material.Text
@Composable
-fun MainScreen(
+fun LoadingScreen(
modifier: Modifier = Modifier
) {
- Box(modifier = modifier, contentAlignment = Alignment.Center) {
- Text("This is a placeholder for the main screen.")
- }
+ // Don't display anything, assuming that there should be minimal latency
+ // to parse the Credential Manager intent and define the state of the
+ // app. If latency is big, then a "loading" screen should be displayed
+ // to the user.
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt
deleted file mode 100644
index d863d3c..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2023 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
- *
- * https://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.
- */
-
-@file:OptIn(ExperimentalHorologistApi::class)
-
-package com.android.credentialmanager.ui.screens
-
-import androidx.compose.foundation.layout.padding
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import com.android.credentialmanager.R
-import com.android.credentialmanager.ui.components.DialogButtonsRow
-import com.android.credentialmanager.ui.components.PasswordRow
-import com.android.credentialmanager.ui.components.SignInHeader
-import com.google.android.horologist.annotations.ExperimentalHorologistApi
-import com.google.android.horologist.compose.layout.ScalingLazyColumnState
-import com.google.android.horologist.compose.layout.belowTimeTextPreview
-import com.google.android.horologist.compose.tools.WearPreview
-
-@Composable
-fun SinglePasswordScreen(
- email: String,
- onCancelClick: () -> Unit,
- onOKClick: () -> Unit,
- columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
-) {
- SingleAccountScreen(
- headerContent = {
- SignInHeader(
- icon = R.drawable.passkey_icon,
- title = stringResource(R.string.use_password_title),
- )
- },
- accountContent = {
- PasswordRow(
- email = email,
- modifier = Modifier.padding(top = 10.dp),
- )
- },
- columnState = columnState,
- modifier = modifier.padding(horizontal = 10.dp)
- ) {
- item {
- DialogButtonsRow(
- onCancelClick = onCancelClick,
- onOKClick = onOKClick,
- modifier = Modifier.padding(top = 10.dp)
- )
- }
- }
-}
-
-@WearPreview
-@Composable
-fun SinglePasswordScreenPreview() {
- SinglePasswordScreen(
- email = "beckett_bakery@gmail.com",
- onCancelClick = {},
- onOKClick = {},
- columnState = belowTimeTextPreview(),
- )
-}
-
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
similarity index 97%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt
rename to packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
index f344ad0..8532783 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
@@ -16,7 +16,7 @@
@file:OptIn(ExperimentalHorologistApi::class)
-package com.android.credentialmanager.ui.screens
+package com.android.credentialmanager.ui.screens.single
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
similarity index 94%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt
rename to packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index c8f871e..c9b0230 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -16,7 +16,7 @@
@file:OptIn(ExperimentalHorologistApi::class)
-package com.android.credentialmanager.ui.screens
+package com.android.credentialmanager.ui.screens.single.passkey
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
@@ -27,6 +27,7 @@
import com.android.credentialmanager.ui.components.AccountRow
import com.android.credentialmanager.ui.components.DialogButtonsRow
import com.android.credentialmanager.ui.components.SignInHeader
+import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
import com.google.android.horologist.compose.layout.belowTimeTextPreview
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
new file mode 100644
index 0000000..c885ec4
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2023 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
+ *
+ * https://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.
+ */
+
+@file:OptIn(ExperimentalHorologistApi::class)
+
+package com.android.credentialmanager.ui.screens.single.password
+
+import android.util.Log
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.credentialmanager.R
+import com.android.credentialmanager.TAG
+import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.ui.components.DialogButtonsRow
+import com.android.credentialmanager.ui.components.PasswordRow
+import com.android.credentialmanager.ui.components.SignInHeader
+import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
+import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import com.google.android.horologist.compose.layout.ScalingLazyColumnState
+import com.google.android.horologist.compose.layout.belowTimeTextPreview
+import com.google.android.horologist.compose.tools.WearPreview
+
+@Composable
+fun SinglePasswordScreen(
+ columnState: ScalingLazyColumnState,
+ onCloseApp: () -> Unit,
+ modifier: Modifier = Modifier,
+ viewModel: SinglePasswordScreenViewModel =
+ viewModel(factory = SinglePasswordScreenViewModel.Factory),
+) {
+ viewModel.initialize()
+
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ when (val state = uiState) {
+ SinglePasswordScreenUiState.Idle -> {
+ // TODO: b/301206470 implement latency version of the screen
+ }
+
+ is SinglePasswordScreenUiState.Loaded -> {
+ val model = state.passwordUiModel
+ SinglePasswordScreen(
+ email = model.email,
+ onCancelClick = viewModel::onCancelClick,
+ onOKClick = viewModel::onOKClick,
+ columnState = columnState,
+ modifier = modifier
+ )
+ }
+
+ is SinglePasswordScreenUiState.PasswordSelected -> {
+ val launcher = rememberLauncherForActivityResult(
+ StartBalIntentSenderForResultContract()
+ ) {
+ viewModel.onPasswordInfoRetrieved(it.resultCode, it.data)
+ }
+
+ SideEffect {
+ launcher.launch(state.intentSenderRequest)
+ }
+ }
+
+ SinglePasswordScreenUiState.Cancel -> {
+ // TODO: b/301206470 implement navigation for when user taps cancel
+ }
+
+ SinglePasswordScreenUiState.Error -> {
+ // TODO: b/301206470 implement navigation for when there is an error to load screen
+ }
+
+ SinglePasswordScreenUiState.Completed -> {
+ Log.d(TAG, "Received signal to finish the activity.")
+ onCloseApp()
+ }
+ }
+}
+
+@Composable
+fun SinglePasswordScreen(
+ email: String,
+ onCancelClick: () -> Unit,
+ onOKClick: () -> Unit,
+ columnState: ScalingLazyColumnState,
+ modifier: Modifier = Modifier,
+) {
+ SingleAccountScreen(
+ headerContent = {
+ SignInHeader(
+ icon = R.drawable.passkey_icon,
+ title = stringResource(R.string.use_password_title),
+ )
+ },
+ accountContent = {
+ PasswordRow(
+ email = email,
+ modifier = Modifier.padding(top = 10.dp),
+ )
+ },
+ columnState = columnState,
+ modifier = modifier.padding(horizontal = 10.dp)
+ ) {
+ item {
+ DialogButtonsRow(
+ onCancelClick = onCancelClick,
+ onOKClick = onOKClick,
+ modifier = Modifier.padding(top = 10.dp)
+ )
+ }
+ }
+}
+
+@WearPreview
+@Composable
+fun SinglePasswordScreenPreview() {
+ SinglePasswordScreen(
+ email = "beckett_bakery@gmail.com",
+ onCancelClick = {},
+ onOKClick = {},
+ columnState = belowTimeTextPreview(),
+ )
+}
+
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
new file mode 100644
index 0000000..9b06622
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager.ui.screens.single.password
+
+import android.content.Intent
+import android.credentials.ui.BaseDialogResult
+import android.credentials.ui.ProviderPendingIntentResponse
+import android.credentials.ui.UserSelectionDialogResult
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.result.IntentSenderRequest
+import androidx.annotation.MainThread
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.CreationExtras
+import com.android.credentialmanager.CredentialSelectorApp
+import com.android.credentialmanager.IS_AUTO_SELECTED_KEY
+import com.android.credentialmanager.TAG
+import com.android.credentialmanager.model.Password
+import com.android.credentialmanager.model.Request
+import com.android.credentialmanager.repository.RequestRepository
+import com.android.credentialmanager.ui.model.PasswordUiModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+class SinglePasswordScreenViewModel(
+ private val requestRepository: RequestRepository,
+) : ViewModel() {
+
+ private var initializeCalled = false
+
+ private lateinit var requestGet: Request.Get
+ private lateinit var password: Password
+
+ private val _uiState =
+ MutableStateFlow<SinglePasswordScreenUiState>(SinglePasswordScreenUiState.Idle)
+ val uiState: StateFlow<SinglePasswordScreenUiState> = _uiState
+
+ @MainThread
+ fun initialize() {
+ if (initializeCalled) return
+ initializeCalled = true
+
+ viewModelScope.launch {
+ val request = requestRepository.requests.first()
+ Log.d(TAG, "request: $request")
+
+ if (request !is Request.Get) {
+ _uiState.value = SinglePasswordScreenUiState.Error
+ } else {
+ requestGet = request
+ if (requestGet.passwordEntries.isEmpty()) {
+ Log.d(TAG, "Empty passwordEntries")
+ _uiState.value = SinglePasswordScreenUiState.Error
+ } else {
+ password = requestGet.passwordEntries.first()
+ _uiState.value = SinglePasswordScreenUiState.Loaded(
+ PasswordUiModel(
+ email = password.passwordCredentialEntry.username.toString(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ fun onCancelClick() {
+ _uiState.value = SinglePasswordScreenUiState.Cancel
+ }
+
+ fun onOKClick() {
+ // TODO: b/301206470 move this code to shared module
+ val entryIntent = password.entry.frameworkExtrasIntent
+ entryIntent?.putExtra(IS_AUTO_SELECTED_KEY, false)
+ val intentSenderRequest = IntentSenderRequest.Builder(
+ pendingIntent = password.passwordCredentialEntry.pendingIntent
+ ).setFillInIntent(entryIntent).build()
+
+ _uiState.value = SinglePasswordScreenUiState.PasswordSelected(
+ intentSenderRequest = intentSenderRequest
+ )
+ }
+
+ fun onPasswordInfoRetrieved(
+ resultCode: Int? = null,
+ resultData: Intent? = null,
+ ) {
+ // TODO: b/301206470 move this code to shared module
+ Log.d(TAG, "credential selected: {provider=${password.providerId}" +
+ ", key=${password.entry.key}, subkey=${password.entry.subkey}}")
+
+ val userSelectionDialogResult = UserSelectionDialogResult(
+ requestGet.token,
+ password.providerId,
+ password.entry.key,
+ password.entry.subkey,
+ if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
+ )
+ val resultDataBundle = Bundle()
+ UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle)
+ requestGet.resultReceiver?.send(
+ BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
+ resultDataBundle
+ )
+
+ _uiState.value = SinglePasswordScreenUiState.Completed
+ }
+
+ companion object {
+ val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(
+ modelClass: Class<T>,
+ extras: CreationExtras
+ ): T {
+ val application = checkNotNull(extras[APPLICATION_KEY])
+
+ return SinglePasswordScreenViewModel(
+ requestRepository = (application as CredentialSelectorApp).requestRepository,
+ ) as T
+ }
+ }
+ }
+}
+
+sealed class SinglePasswordScreenUiState {
+ data object Idle : SinglePasswordScreenUiState()
+ data class Loaded(val passwordUiModel: PasswordUiModel) : SinglePasswordScreenUiState()
+ data class PasswordSelected(
+ val intentSenderRequest: IntentSenderRequest
+ ) : SinglePasswordScreenUiState()
+
+ data object Cancel : SinglePasswordScreenUiState()
+ data object Error : SinglePasswordScreenUiState()
+ data object Completed : SinglePasswordScreenUiState()
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 5e7e044..104f3d2 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -235,6 +235,9 @@
srcs: [
/* Status bar fakes */
"tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt",
+ "tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt",
+ "tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt",
+ "tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt",
"tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt",
"tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
index 96a974d..7b2e1af 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
@@ -19,6 +19,12 @@
import android.os.Trace
import android.os.TraceNameSupplier
import java.util.concurrent.atomic.AtomicInteger
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.async
/**
* Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
@@ -85,5 +91,18 @@
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, cookie)
}
}
+
+ /**
+ * Convenience method to avoid one indentation level when we want to add a trace when
+ * launching a coroutine
+ */
+ fun <T> CoroutineScope.tracedAsync(
+ method: String,
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend () -> T
+ ): Deferred<T> {
+ return async(context, start) { traceAsync(method) { block() } }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 625c1de..b2287d87 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -163,7 +163,10 @@
}
mCurrentUser = KeyguardUpdateMonitor.getCurrentUser();
showPrimarySecurityScreen(false);
- reinflateViewFlipper((l) -> {});
+ if (mCurrentSecurityMode != SecurityMode.SimPin
+ && mCurrentSecurityMode != SecurityMode.SimPuk) {
+ reinflateViewFlipper((l) -> {});
+ }
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 954129e..22bd207 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -18,8 +18,8 @@
import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_X;
import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
-
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
+import static com.android.systemui.flags.Flags.SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX;
import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import android.animation.Animator;
@@ -482,7 +482,14 @@
boolean wasRemoved = false;
if (animView instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
- wasRemoved = row.isRemoved();
+ if (mFeatureFlags.isEnabled(SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX)) {
+ // If the view is already removed from its parent and added as Transient,
+ // we need to clean the transient view upon animation end
+ wasRemoved = row.getTransientContainer() != null
+ || row.getParent() == null || row.isRemoved();
+ } else {
+ wasRemoved = row.isRemoved();
+ }
}
if (!mCancelled || wasRemoved) {
mCallback.onChildDismissed(animView);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index 06cf723..e8740a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -26,7 +26,6 @@
import android.util.Log
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
@@ -40,6 +39,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.DevicePosture
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.user.data.repository.UserRepository
import java.io.PrintWriter
import javax.inject.Inject
@@ -133,6 +134,7 @@
devicePostureRepository: DevicePostureRepository,
facePropertyRepository: FacePropertyRepository,
fingerprintPropertyRepository: FingerprintPropertyRepository,
+ mobileConnectionsRepository: MobileConnectionsRepository,
dumpManager: DumpManager,
) : BiometricSettingsRepository, Dumpable {
@@ -346,14 +348,15 @@
.and(isFingerprintBiometricAllowed)
.stateIn(scope, SharingStarted.Eagerly, false)
- override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
- get() = isFaceAuthenticationEnabled.and(isFaceEnrolled)
+ override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> =
+ isFaceAuthenticationEnabled
+ .and(isFaceEnrolled)
+ .and(mobileConnectionsRepository.isAnySimSecure.isFalse())
- override val isFaceAuthCurrentlyAllowed: Flow<Boolean>
- get() =
- isFaceAuthEnrolledAndEnabled
- .and(isFaceBiometricsAllowed)
- .and(isFaceAuthSupportedInCurrentPosture)
+ override val isFaceAuthCurrentlyAllowed: Flow<Boolean> =
+ isFaceAuthEnrolledAndEnabled
+ .and(isFaceBiometricsAllowed)
+ .and(isFaceAuthSupportedInCurrentPosture)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -426,3 +429,5 @@
private fun Flow<Boolean>.and(anotherFlow: Flow<Boolean>): Flow<Boolean> =
this.combine(anotherFlow) { a, b -> a && b }
+
+private fun Flow<Boolean>.isFalse(): Flow<Boolean> = this.map { !it }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index dd7eee9..abc30ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -25,6 +25,8 @@
import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import android.widget.ImageView
+import androidx.core.animation.CycleInterpolator
+import androidx.core.animation.ObjectAnimator
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
@@ -383,6 +385,27 @@
falsingManager,
)
view.setOnTouchListener(onTouchListener)
+ view.setOnClickListener {
+ messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
+ val amplitude =
+ view.context.resources
+ .getDimensionPixelSize(R.dimen.keyguard_affordance_shake_amplitude)
+ .toFloat()
+ val shakeAnimator =
+ ObjectAnimator.ofFloat(
+ view,
+ "translationX",
+ -amplitude / 2,
+ amplitude / 2,
+ )
+ shakeAnimator.duration =
+ KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds
+ shakeAnimator.interpolator =
+ CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles)
+ shakeAnimator.start()
+
+ vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
+ }
view.onLongClickListener =
OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
index 125e2da..f2d39da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
@@ -99,41 +99,7 @@
// When not using a stylus, lifting the finger/pointer will actually cancel
// the long-press gesture. Calling cancel after the quick affordance was
// already long-press activated is a no-op, so it's safe to call from here.
- cancel(
- onAnimationEnd =
- if (event.eventTime - event.downTime < longPressDurationMs) {
- Runnable {
- messageDisplayer.invoke(
- R.string.keyguard_affordance_press_too_short
- )
- val amplitude =
- view.context.resources
- .getDimensionPixelSize(
- R.dimen.keyguard_affordance_shake_amplitude
- )
- .toFloat()
- val shakeAnimator =
- ObjectAnimator.ofFloat(
- view,
- "translationX",
- -amplitude / 2,
- amplitude / 2,
- )
- shakeAnimator.duration =
- KeyguardBottomAreaVibrations.ShakeAnimationDuration
- .inWholeMilliseconds
- shakeAnimator.interpolator =
- CycleInterpolator(
- KeyguardBottomAreaVibrations.ShakeAnimationCycles
- )
- shakeAnimator.start()
-
- vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
- }
- } else {
- null
- }
- )
+ cancel()
}
false
}
@@ -168,10 +134,10 @@
view.setOnClickListener(null)
}
- fun cancel(onAnimationEnd: Runnable? = null) {
+ fun cancel() {
longPressAnimator?.cancel()
longPressAnimator = null
- view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
+ view.animate().scaleX(1f).scaleY(1f)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index eeb4ac3..aa76702 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -23,6 +23,8 @@
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
+import androidx.core.animation.CycleInterpolator
+import androidx.core.animation.ObjectAnimator
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
@@ -216,6 +218,27 @@
falsingManager,
)
view.setOnTouchListener(onTouchListener)
+ view.setOnClickListener {
+ messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
+ val amplitude =
+ view.context.resources
+ .getDimensionPixelSize(R.dimen.keyguard_affordance_shake_amplitude)
+ .toFloat()
+ val shakeAnimator =
+ ObjectAnimator.ofFloat(
+ view,
+ "translationX",
+ -amplitude / 2,
+ amplitude / 2,
+ )
+ shakeAnimator.duration =
+ KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds
+ shakeAnimator.interpolator =
+ CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles)
+ shakeAnimator.start()
+
+ vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
+ }
view.onLongClickListener =
OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 6c2ce7f..1943b34 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -29,6 +29,7 @@
import com.android.systemui.log.LogcatEchoTrackerProd;
import com.android.systemui.log.table.TableLogBuffer;
import com.android.systemui.log.table.TableLogBufferFactory;
+import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.util.Compile;
import com.android.systemui.util.wakelock.WakeLockLog;
@@ -229,12 +230,12 @@
}
/**
- * Provides a logging buffer for logs related to {@link com.android.systemui.qs.QSFragment}'s
+ * Provides a logging buffer for logs related to {@link QSFragmentLegacy}'s
* disable flag adjustments.
*/
@Provides
@SysUISingleton
- @QSFragmentDisableLog
+ @QSDisableLog
public static LogBuffer provideQSFragmentDisableLogBuffer(LogBufferFactory factory) {
return factory.create("QSFragmentDisableFlagsLog", 10 /* maxSize */,
false /* systrace */);
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSDisableLog.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
rename to packages/SystemUI/src/com/android/systemui/log/dagger/QSDisableLog.java
index 557a254..b3bceca 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSDisableLog.java
@@ -19,6 +19,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.systemui.log.LogBuffer;
+import com.android.systemui.qs.QSFragmentLegacy;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -27,10 +28,10 @@
/**
* A {@link LogBuffer} for disable flag adjustments made in
- * {@link com.android.systemui.qs.QSFragment}.
+ * {@link QSFragmentLegacy}.
*/
@Qualifier
@Documented
@Retention(RUNTIME)
-public @interface QSFragmentDisableLog {
+public @interface QSDisableLog {
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 463c79c..eba1c25 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -28,7 +28,7 @@
import com.android.app.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.QSPanel.QSTileLayout;
@@ -86,8 +86,7 @@
private final QuickQSPanel mQuickQsPanel;
private final QSPanelController mQsPanelController;
private final QuickQSPanelController mQuickQSPanelController;
- private final QuickStatusBarHeader mQuickStatusBarHeader;
- private final QS mQs;
+ private final View mQsRootView;
@Nullable
private PagedTileLayout mPagedLayout;
@@ -115,8 +114,6 @@
// Brightness slider opacity driver. Uses linear interpolator.
@Nullable
private TouchAnimator mBrightnessOpacityAnimator;
- // Animator for Footer actions in QQS
- private TouchAnimator mQQSFooterActionsAnimator;
// Height animator for QQS tiles (height changing from QQS size to QS size)
@Nullable
private HeightExpansionAnimator mQQSTileHeightAnimator;
@@ -144,22 +141,21 @@
private int[] mTmpLoc2 = new int[2];
@Inject
- public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
+ public QSAnimator(@RootView View rootView, QuickQSPanel quickPanel,
QSPanelController qsPanelController,
QuickQSPanelController quickQSPanelController, QSHost qsTileHost,
@Main Executor executor, TunerService tunerService,
QSExpansionPathInterpolator qsExpansionPathInterpolator) {
- mQs = qs;
+ mQsRootView = rootView;
mQuickQsPanel = quickPanel;
mQsPanelController = qsPanelController;
mQuickQSPanelController = quickQSPanelController;
- mQuickStatusBarHeader = quickStatusBarHeader;
mHost = qsTileHost;
mExecutor = executor;
mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
mHost.addCallback(this);
mQsPanelController.addOnAttachStateChangeListener(this);
- qs.getView().addOnLayoutChangeListener(this);
+ mQsRootView.addOnLayoutChangeListener(this);
if (mQsPanelController.isAttachedToWindow()) {
onViewAttachedToWindow(null);
}
@@ -314,8 +310,7 @@
break;
}
- final View tileIcon = tileView.getIcon().getIconView();
- View view = mQs.getView();
+ View view = mQsRootView;
// This case: less tiles to animate in small displays.
if (count < mQuickQSPanelController.getTileLayout().getNumVisibleTiles()) {
@@ -480,7 +475,7 @@
.setStartDelay(QS_TILE_LABEL_FADE_OUT_START)
.setEndDelay(QS_TILE_LABEL_FADE_OUT_END);
SideLabelTileLayout qqsLayout = (SideLabelTileLayout) mQuickQsPanel.getTileLayout();
- View view = mQs.getView();
+ View view = mQsRootView;
List<String> specs = mPagedLayout.getSpecsForPage(page);
if (specs.isEmpty()) {
// specs should not be empty in a valid secondary page, as we scrolled to it.
@@ -577,7 +572,7 @@
// For (1), compute the distance via the vertical distance between QQS and QS tile
// layout top.
- View quickSettingsRootView = mQs.getView();
+ View quickSettingsRootView = mQsRootView;
View qsTileLayout = (View) mQsPanelController.getTileLayout();
View qqsTileLayout = (View) mQuickQSPanelController.getTileLayout();
getRelativePosition(mTmpLoc1, qsTileLayout, quickSettingsRootView);
@@ -607,7 +602,7 @@
private int getRelativeTranslationY(View view1, View view2) {
int[] qsPosition = new int[2];
int[] qqsPosition = new int[2];
- View commonView = mQs.getView();
+ View commonView = mQsRootView;
getRelativePositionInt(qsPosition, view1, commonView);
getRelativePositionInt(qqsPosition, view2, commonView);
return qsPosition[1] - qqsPosition[1];
@@ -690,9 +685,6 @@
if (mBrightnessTranslationAnimator != null) {
mBrightnessTranslationAnimator.setPosition(position);
}
- if (mQQSFooterActionsAnimator != null) {
- mQQSFooterActionsAnimator.setPosition(position);
- }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSDisableFlagsLogger.kt
similarity index 73%
rename from packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
rename to packages/SystemUI/src/com/android/systemui/qs/QSDisableFlagsLogger.kt
index 6563e42..6f6f467 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDisableFlagsLogger.kt
@@ -1,20 +1,22 @@
package com.android.systemui.qs
-import com.android.systemui.log.dagger.QSFragmentDisableLog
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.QSDisableLog
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
import javax.inject.Inject
-/** A helper class for logging disable flag changes made in [QSFragment]. */
-class QSFragmentDisableFlagsLogger @Inject constructor(
- @QSFragmentDisableLog private val buffer: LogBuffer,
+/** A helper class for logging disable flag changes made in [QSImpl]. */
+class QSDisableFlagsLogger
+@Inject
+constructor(
+ @QSDisableLog private val buffer: LogBuffer,
private val disableFlagsLogger: DisableFlagsLogger
) {
/**
- * Logs a string representing the new state received by [QSFragment] and any modifications that
- * were made to the flags locally.
+ * Logs a string representing the new state received by [QSImpl] and any modifications that were
+ * made to the flags locally.
*
* @param new see [DisableFlagsLogger.getDisableFlagsString]
* @param newAfterLocalModification see [DisableFlagsLogger.getDisableFlagsString]
@@ -43,4 +45,4 @@
}
}
-private const val TAG = "QSFragmentDisableFlagsLog"
+private const val TAG = "QSDisableFlagsLog"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
new file mode 100644
index 0000000..8589ae9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2023 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.qs;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Trace;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.FloatRange;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.qs.QSContainerController;
+import com.android.systemui.qs.dagger.QSFragmentComponent;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.util.LifecycleFragment;
+
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+public class QSFragmentLegacy extends LifecycleFragment implements QS {
+
+ private final Provider<QSImpl> mQsImplProvider;
+
+ private final QSFragmentComponent.Factory mQsComponentFactory;
+
+ @Nullable
+ private QSImpl mQsImpl;
+
+ @Inject
+ public QSFragmentLegacy(
+ Provider<QSImpl> qsImplProvider,
+ QSFragmentComponent.Factory qsComponentFactory
+ ) {
+ mQsComponentFactory = qsComponentFactory;
+ mQsImplProvider = qsImplProvider;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ Bundle savedInstanceState) {
+ try {
+ Trace.beginSection("QSFragment#onCreateView");
+ inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(),
+ R.style.Theme_SystemUI_QuickSettings));
+ return inflater.inflate(R.layout.qs_panel, container, false);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this);
+ mQsImpl = mQsImplProvider.get();
+ mQsImpl.onComponentCreated(qsFragmentComponent, savedInstanceState);
+ }
+
+ @Override
+ public void setScrollListener(ScrollListener listener) {
+ if (mQsImpl != null) {
+ mQsImpl.setScrollListener(listener);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (mQsImpl != null) {
+ mQsImpl.onCreate(savedInstanceState);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mQsImpl != null) {
+ mQsImpl.onDestroy();
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mQsImpl != null) {
+ mQsImpl.onSaveInstanceState(outState);
+ }
+ }
+
+ @Override
+ public View getHeader() {
+ if (mQsImpl != null) {
+ return mQsImpl.getHeader();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void setHasNotifications(boolean hasNotifications) {
+ if (mQsImpl != null) {
+ mQsImpl.setHasNotifications(hasNotifications);
+ }
+ }
+
+ @Override
+ public void setPanelView(HeightListener panelView) {
+ if (mQsImpl != null) {
+ mQsImpl.setPanelView(panelView);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (mQsImpl != null) {
+ mQsImpl.onConfigurationChanged(newConfig);
+ }
+ }
+
+ @Override
+ public void setFancyClipping(int leftInset, int top, int rightInset, int bottom,
+ int cornerRadius, boolean visible, boolean fullWidth) {
+ if (mQsImpl != null) {
+ mQsImpl.setFancyClipping(leftInset, top, rightInset, bottom, cornerRadius, visible,
+ fullWidth);
+ }
+ }
+
+ @Override
+ public boolean isFullyCollapsed() {
+ if (mQsImpl != null) {
+ return mQsImpl.isFullyCollapsed();
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public void setCollapsedMediaVisibilityChangedListener(Consumer<Boolean> listener) {
+ if (mQsImpl != null) {
+ mQsImpl.setCollapsedMediaVisibilityChangedListener(listener);
+ }
+ }
+
+ @Override
+ public void setContainerController(QSContainerController controller) {
+ if (mQsImpl != null) {
+ mQsImpl.setContainerController(controller);
+ }
+ }
+
+ @Override
+ public boolean isCustomizing() {
+ if (mQsImpl != null) {
+ return mQsImpl.isCustomizing();
+ } else {
+ return false;
+ }
+ }
+
+ public QSPanelController getQSPanelController() {
+ if (mQsImpl != null) {
+ return mQsImpl.getQSPanelController();
+ } else {
+ return null;
+ }
+ }
+
+ public void setBrightnessMirrorController(
+ BrightnessMirrorController brightnessMirrorController) {
+ if (mQsImpl != null) {
+ mQsImpl.setBrightnessMirrorController(brightnessMirrorController);
+ }
+ }
+
+ @Override
+ public boolean isShowingDetail() {
+ if (mQsImpl != null) {
+ return mQsImpl.isShowingDetail();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void setHeaderClickable(boolean clickable) {
+ if (mQsImpl != null) {
+ mQsImpl.setHeaderClickable(clickable);
+ }
+ }
+
+ @Override
+ public void setExpanded(boolean expanded) {
+ if (mQsImpl != null) {
+ mQsImpl.setExpanded(expanded);
+ }
+ }
+
+ @Override
+ public void setOverscrolling(boolean stackScrollerOverscrolling) {
+ if (mQsImpl != null) {
+ mQsImpl.setOverscrolling(stackScrollerOverscrolling);
+ }
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mQsImpl != null) {
+ mQsImpl.setListening(listening);
+ }
+ }
+
+ @Override
+ public void setQsVisible(boolean visible) {
+ if (mQsImpl != null) {
+ mQsImpl.setQsVisible(visible);
+ }
+ }
+
+ @Override
+ public void setHeaderListening(boolean listening) {
+ if (mQsImpl != null) {
+ mQsImpl.setHeaderListening(listening);
+ }
+ }
+
+ @Override
+ public void notifyCustomizeChanged() {
+ if (mQsImpl != null) {
+ mQsImpl.notifyCustomizeChanged();
+ }
+ }
+
+ @Override
+ public void setInSplitShade(boolean inSplitShade) {
+ if (mQsImpl != null) {
+ mQsImpl.setInSplitShade(inSplitShade);
+ }
+ }
+
+ @Override
+ public void setTransitionToFullShadeProgress(
+ boolean isTransitioningToFullShade,
+ @FloatRange(from = 0.0, to = 1.0) float qsTransitionFraction,
+ @FloatRange(from = 0.0, to = 1.0) float qsSquishinessFraction) {
+ if (mQsImpl != null) {
+ mQsImpl.setTransitionToFullShadeProgress(isTransitioningToFullShade,
+ qsTransitionFraction, qsSquishinessFraction);
+ }
+ }
+
+ @Override
+ public void setOverScrollAmount(int overScrollAmount) {
+ if (mQsImpl != null) {
+ mQsImpl.setOverScrollAmount(overScrollAmount);
+ }
+ }
+
+ @Override
+ public int getHeightDiff() {
+ if (mQsImpl != null) {
+ return mQsImpl.getHeightDiff();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public void setIsNotificationPanelFullWidth(boolean isFullWidth) {
+ if (mQsImpl != null) {
+ mQsImpl.setIsNotificationPanelFullWidth(isFullWidth);
+ }
+ }
+
+ @Override
+ public void setQsExpansion(float expansion, float panelExpansionFraction,
+ float proposedTranslation, float squishinessFraction) {
+ if (mQsImpl != null) {
+ mQsImpl.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+ squishinessFraction);
+ }
+ }
+
+ @Override
+ public void animateHeaderSlidingOut() {
+ if (mQsImpl != null) {
+ mQsImpl.animateHeaderSlidingOut();
+ }
+ }
+
+ @Override
+ public void setCollapseExpandAction(Runnable action) {
+ if (mQsImpl != null) {
+ mQsImpl.setCollapseExpandAction(action);
+ }
+ }
+
+ @Override
+ public void closeDetail() {
+ if (mQsImpl != null) {
+ mQsImpl.closeDetail();
+ }
+ }
+
+ @Override
+ public void closeCustomizer() {
+ if (mQsImpl != null) {
+ mQsImpl.closeDetail();
+ }
+ }
+
+ /**
+ * The height this view wants to be. This is different from {@link View#getMeasuredHeight} such
+ * that during closing the detail panel, this already returns the smaller height.
+ */
+ @Override
+ public int getDesiredHeight() {
+ if (mQsImpl != null) {
+ return mQsImpl.getDesiredHeight();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public void setHeightOverride(int desiredHeight) {
+ if (mQsImpl != null) {
+ mQsImpl.setHeightOverride(desiredHeight);
+ }
+ }
+
+ @Override
+ public int getQsMinExpansionHeight() {
+ if (mQsImpl != null) {
+ return mQsImpl.getQsMinExpansionHeight();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public void hideImmediately() {
+ if (mQsImpl != null) {
+ mQsImpl.hideImmediately();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
index 253560b..9fa6769 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
@@ -31,10 +31,13 @@
@Inject
constructor(
private val fragmentService: FragmentService,
- private val qsFragmentProvider: Provider<QSFragment>
+ private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>
) : CoreStartable {
override fun start() {
- fragmentService.addFragmentInstantiationProvider(QSFragment::class.java, qsFragmentProvider)
+ fragmentService.addFragmentInstantiationProvider(
+ QSFragmentLegacy::class.java,
+ qsFragmentLegacyProvider
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
rename to packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index fd81e9a..a32a024 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -1,15 +1,17 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2023 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
+ * 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.
+ * 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.qs;
@@ -20,21 +22,18 @@
import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
-import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.Trace;
import android.util.IndentingPrintWriter;
import android.util.Log;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import androidx.annotation.FloatRange;
@@ -47,7 +46,6 @@
import com.android.app.animation.Interpolators;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dump.DumpManager;
@@ -58,19 +56,20 @@
import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.customize.QSCustomizerController;
-import com.android.systemui.qs.dagger.QSFragmentComponent;
+import com.android.systemui.qs.dagger.QSComponent;
import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.res.R;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.util.LifecycleFragment;
import com.android.systemui.util.Utils;
import java.io.PrintWriter;
@@ -80,8 +79,8 @@
import javax.inject.Inject;
import javax.inject.Named;
-public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Callbacks,
- StatusBarStateController.StateListener, Dumpable {
+public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateController.StateListener,
+ Dumpable {
private static final String TAG = "QS";
private static final boolean DEBUG = false;
private static final String EXTRA_EXPANDED = "expanded";
@@ -113,8 +112,7 @@
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final MediaHost mQsMediaHost;
private final MediaHost mQqsMediaHost;
- private final QSFragmentComponent.Factory mQsComponentFactory;
- private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
+ private final QSDisableFlagsLogger mQsDisableFlagsLogger;
private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
private final FeatureFlags mFeatureFlags;
private final QSLogger mLogger;
@@ -167,14 +165,17 @@
private boolean mIsSmallScreen;
+ private CommandQueue mCommandQueue;
+
+ private View mRootView;
+
@Inject
- public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
+ public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
@Named(QS_PANEL) MediaHost qsMediaHost,
@Named(QUICK_QS_PANEL) MediaHost qqsMediaHost,
KeyguardBypassController keyguardBypassController,
- QSFragmentComponent.Factory qsComponentFactory,
- QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
+ QSDisableFlagsLogger qsDisableFlagsLogger,
DumpManager dumpManager, QSLogger qsLogger,
FooterActionsController footerActionsController,
FooterActionsViewModel.Factory footerActionsViewModelFactory,
@@ -184,12 +185,11 @@
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost;
mQqsMediaHost = qqsMediaHost;
- mQsComponentFactory = qsComponentFactory;
- mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger;
+ mQsDisableFlagsLogger = qsDisableFlagsLogger;
mLogger = qsLogger;
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
mFeatureFlags = featureFlags;
- commandQueue.observe(getLifecycle(), this);
+ mCommandQueue = commandQueue;
mBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
mDumpManager = dumpManager;
@@ -199,34 +199,23 @@
mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
}
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- Bundle savedInstanceState) {
- try {
- Trace.beginSection("QSFragment#onCreateView");
- inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(),
- R.style.Theme_SystemUI_QuickSettings));
- return inflater.inflate(R.layout.qs_panel, container, false);
- } finally {
- Trace.endSection();
- }
- }
+ public void onComponentCreated(QSComponent qsComponent, @Nullable Bundle savedInstanceState) {
+ mRootView = qsComponent.getRootView();
- @Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this);
- mQSPanelController = qsFragmentComponent.getQSPanelController();
- mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController();
+ mCommandQueue.addCallback(this);
+
+ mQSPanelController = qsComponent.getQSPanelController();
+ mQuickQSPanelController = qsComponent.getQuickQSPanelController();
mQSPanelController.init();
mQuickQSPanelController.init();
- mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
- this);
- bindFooterActionsView(view);
+ mQSFooterActionsViewModel = mFooterActionsViewModelFactory
+ .create(mListeningAndVisibilityLifecycleOwner);
+ bindFooterActionsView(mRootView);
mFooterActionsController.init();
- mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
+ mQSPanelScrollView = mRootView.findViewById(R.id.expanded_qs_scroll_view);
mQSPanelScrollView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
updateQsBounds();
@@ -238,26 +227,26 @@
if (mScrollListener != null) {
mScrollListener.onQsPanelScrollChanged(scrollY);
}
- });
- mHeader = view.findViewById(R.id.header);
- mFooter = qsFragmentComponent.getQSFooter();
+ });
+ mHeader = mRootView.findViewById(R.id.header);
+ mFooter = qsComponent.getQSFooter();
- mQSContainerImplController = qsFragmentComponent.getQSContainerImplController();
+ mQSContainerImplController = qsComponent.getQSContainerImplController();
mQSContainerImplController.init();
mContainer = mQSContainerImplController.getView();
mDumpManager.registerDumpable(mContainer.getClass().getSimpleName(), mContainer);
- mQSAnimator = qsFragmentComponent.getQSAnimator();
- mQSSquishinessController = qsFragmentComponent.getQSSquishinessController();
+ mQSAnimator = qsComponent.getQSAnimator();
+ mQSSquishinessController = qsComponent.getQSSquishinessController();
- mQSCustomizerController = qsFragmentComponent.getQSCustomizerController();
+ mQSCustomizerController = qsComponent.getQSCustomizerController();
mQSCustomizerController.init();
mQSCustomizerController.setQs(this);
if (savedInstanceState != null) {
setQsVisible(savedInstanceState.getBoolean(EXTRA_VISIBLE));
setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
- setEditLocation(view);
+ setEditLocation(mRootView);
mQSCustomizerController.restoreInstanceState(savedInstanceState);
if (mQsExpanded) {
mQSPanelController.getTileLayout().restoreInstanceState(savedInstanceState);
@@ -265,7 +254,7 @@
}
mStatusBarStateController.addCallback(this);
onStateChanged(mStatusBarStateController.getState());
- view.addOnLayoutChangeListener(
+ mRootView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
boolean sizeChanged = (oldTop - oldBottom) != (top - bottom);
if (sizeChanged) {
@@ -327,15 +316,12 @@
mScrollListener = listener;
}
- @Override
public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
mDumpManager.registerDumpable(getClass().getSimpleName(), this);
}
- @Override
public void onDestroy() {
- super.onDestroy();
+ mCommandQueue.removeCallback(this);
mStatusBarStateController.removeCallback(this);
if (mListening) {
setListening(false);
@@ -351,9 +337,7 @@
mListeningAndVisibilityLifecycleOwner.destroy();
}
- @Override
public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
outState.putBoolean(EXTRA_EXPANDED, mQsExpanded);
outState.putBoolean(EXTRA_LISTENING, mListening);
outState.putBoolean(EXTRA_VISIBLE, mQsVisible);
@@ -394,9 +378,7 @@
mPanelView = panelView;
}
- @Override
public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
setEditLocation(getView());
if (newConfig.getLayoutDirection() != mLayoutDirection) {
mLayoutDirection = newConfig.getLayoutDirection();
@@ -452,9 +434,9 @@
int state2BeforeAdjustment = state2;
state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
- mQsFragmentDisableFlagsLogger.logDisableFlagChange(
- /* new= */ new DisableState(state1, state2BeforeAdjustment),
- /* newAfterLocalModification= */ new DisableState(state1, state2)
+ mQsDisableFlagsLogger.logDisableFlagChange(
+ /* new= */ new DisableFlagsLogger.DisableState(state1, state2BeforeAdjustment),
+ /* newAfterLocalModification= */ new DisableFlagsLogger.DisableState(state1, state2)
);
final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
@@ -919,32 +901,6 @@
getView().setY(-getQsMinExpansionHeight());
}
- private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
- = new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getView().getViewTreeObserver().removeOnPreDrawListener(this);
- getView().animate()
- .translationY(0f)
- .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setListener(mAnimateHeaderSlidingInListener)
- .start();
- return true;
- }
- };
-
- private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
- = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mHeaderAnimating = false;
- updateQsState();
- // Unset the listener, otherwise this may persist for another view property animation
- getView().animate().setListener(null);
- }
- };
-
@Override
public void onUpcomingStateChanged(int upcomingState) {
if (upcomingState == KEYGUARD) {
@@ -1030,6 +986,20 @@
return "GONE";
}
+ @Override
+ public View getView() {
+ return mRootView;
+ }
+
+ @Override
+ public Context getContext() {
+ return mRootView.getContext();
+ }
+
+ private Resources getResources() {
+ return getContext().getResources();
+ }
+
/**
* A {@link LifecycleOwner} whose state is driven by the current state of this fragment:
*
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 9359958..6bbdc54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -19,7 +19,7 @@
import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
+import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_MEDIA_PLAYER;
import android.view.MotionEvent;
import android.view.View;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index ef81674..60c92c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,9 +43,6 @@
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
-import kotlin.Unit;
-import kotlin.jvm.functions.Function1;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
@@ -54,7 +50,8 @@
import java.util.function.Consumer;
import java.util.stream.Collectors;
-import javax.inject.Named;
+import kotlin.Unit;
+import kotlin.jvm.functions.Function1;
/**
* Controller for QSPanel views.
@@ -135,7 +132,7 @@
T view,
QSHost host,
QSCustomizerController qsCustomizerController,
- @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
+ boolean usingMediaPlayer,
MediaHost mediaHost,
MetricsLogger metricsLogger,
UiEventLogger uiEventLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 099d19d8..f278dce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -17,14 +17,13 @@
package com.android.systemui.qs;
import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_COLLAPSED_LANDSCAPE_MEDIA;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
+import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_COLLAPSED_LANDSCAPE_MEDIA;
+import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_MEDIA_PLAYER;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.media.controls.ui.MediaHost;
@@ -32,6 +31,7 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.leak.RotationUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 7888f4c..a103566 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -33,11 +33,11 @@
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.qs.QSDetailClipper;
import com.android.systemui.qs.QSUtils;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.LightBarController;
/**
@@ -135,8 +135,10 @@
setVisibility(View.VISIBLE);
long duration = mClipper.animateCircularClip(
mX, mY, true, new ExpandAnimatorListener(tileAdapter));
- mQsContainerController.setCustomizerAnimating(true);
- mQsContainerController.setCustomizerShowing(true, duration);
+ if (mQsContainerController != null) {
+ mQsContainerController.setCustomizerAnimating(true);
+ mQsContainerController.setCustomizerShowing(true, duration);
+ }
}
}
@@ -150,8 +152,10 @@
mClipper.showBackground();
isShown = true;
setCustomizing(true);
- mQsContainerController.setCustomizerAnimating(false);
- mQsContainerController.setCustomizerShowing(true);
+ if (mQsContainerController != null) {
+ mQsContainerController.setCustomizerAnimating(false);
+ mQsContainerController.setCustomizerShowing(true);
+ }
}
}
@@ -169,8 +173,10 @@
} else {
setVisibility(View.GONE);
}
- mQsContainerController.setCustomizerAnimating(animate);
- mQsContainerController.setCustomizerShowing(false, duration);
+ if (mQsContainerController != null) {
+ mQsContainerController.setCustomizerAnimating(animate);
+ mQsContainerController.setCustomizerShowing(false, duration);
+ }
}
}
@@ -180,7 +186,9 @@
void setCustomizing(boolean customizing) {
mCustomizing = customizing;
- mQs.notifyCustomizeChanged();
+ if (mQs != null) {
+ mQs.notifyCustomizeChanged();
+ }
}
public boolean isCustomizing() {
@@ -208,15 +216,21 @@
setCustomizing(true);
}
mOpening = false;
- mQsContainerController.setCustomizerAnimating(false);
+ if (mQsContainerController != null) {
+ mQsContainerController.setCustomizerAnimating(false);
+ }
mRecyclerView.setAdapter(mTileAdapter);
}
@Override
public void onAnimationCancel(Animator animation) {
mOpening = false;
- mQs.notifyCustomizeChanged();
- mQsContainerController.setCustomizerAnimating(false);
+ if (mQs != null) {
+ mQs.notifyCustomizeChanged();
+ }
+ if (mQsContainerController != null) {
+ mQsContainerController.setCustomizerAnimating(false);
+ }
}
}
@@ -226,7 +240,9 @@
if (!isShown) {
setVisibility(View.GONE);
}
- mQsContainerController.setCustomizerAnimating(false);
+ if (mQsContainerController != null) {
+ mQsContainerController.setCustomizerAnimating(false);
+ }
}
@Override
@@ -234,7 +250,9 @@
if (!isShown) {
setVisibility(View.GONE);
}
- mQsContainerController.setCustomizerAnimating(false);
+ if (mQsContainerController != null) {
+ mQsContainerController.setCustomizerAnimating(false);
+ }
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index ce504b2..024e760 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -34,14 +34,14 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSEditEvent;
-import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -199,7 +199,7 @@
}
/** */
- public void setQs(@Nullable QSFragment qsFragment) {
+ public void setQs(@Nullable QS qsFragment) {
mView.setQs(qsFragment);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSComponent.kt
new file mode 100644
index 0000000..f3413b80
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSComponent.kt
@@ -0,0 +1,40 @@
+package com.android.systemui.qs.dagger
+
+import android.view.View
+import com.android.systemui.dagger.qualifiers.RootView
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.QSAnimator
+import com.android.systemui.qs.QSContainerImplController
+import com.android.systemui.qs.QSFooter
+import com.android.systemui.qs.QSPanelController
+import com.android.systemui.qs.QSSquishinessController
+import com.android.systemui.qs.QuickQSPanelController
+import com.android.systemui.qs.customize.QSCustomizerController
+
+interface QSComponent {
+ /** Construct a [QSPanelController]. */
+ fun getQSPanelController(): QSPanelController
+
+ /** Construct a [QuickQSPanelController]. */
+ fun getQuickQSPanelController(): QuickQSPanelController
+
+ /** Construct a [QSAnimator]. */
+ fun getQSAnimator(): QSAnimator
+
+ /** Construct a [QSContainerImplController]. */
+ fun getQSContainerImplController(): QSContainerImplController
+
+ /** Construct a [QSFooter] */
+ fun getQSFooter(): QSFooter
+
+ /** Construct a [QSCustomizerController]. */
+ fun getQSCustomizerController(): QSCustomizerController
+
+ /** Construct a [QSSquishinessController]. */
+ fun getQSSquishinessController(): QSSquishinessController
+
+ /** Construct a [FooterActionsController]. */
+ fun getQSFooterActionController(): FooterActionsController
+
+ @RootView fun getRootView(): View
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt
new file mode 100644
index 0000000..ba1aa62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.qs.dagger
+
+import android.view.View
+import com.android.systemui.dagger.qualifiers.RootView
+import dagger.BindsInstance
+import dagger.Subcomponent
+
+@Subcomponent(modules = [QSFlexiglassModule::class])
+@QSScope
+interface QSFlexiglassComponent : QSComponent {
+
+ @Subcomponent.Factory
+ interface Factory {
+ fun create(@RootView @BindsInstance rootView: View): QSFlexiglassComponent
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt
new file mode 100644
index 0000000..36fac44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.qs.dagger
+
+import android.content.Context
+import com.android.systemui.qs.dagger.QSScopeModule.Companion.QS_USING_COLLAPSED_LANDSCAPE_MEDIA
+import com.android.systemui.qs.dagger.QSScopeModule.Companion.QS_USING_MEDIA_PLAYER
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module(includes = [QSScopeModule::class])
+interface QSFlexiglassModule {
+
+ @Module
+ companion object {
+
+ /** */
+ @Provides
+ @Named(QS_USING_MEDIA_PLAYER)
+ @JvmStatic
+ fun providesQSUsingMediaPlayer(context: Context?): Boolean {
+ return false
+ }
+
+ /** */
+ @Provides
+ @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
+ @JvmStatic
+ fun providesQSUsingCollapsedLandscapeMedia(context: Context): Boolean {
+ return false
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
index 594f4f8..327e858 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
@@ -16,53 +16,21 @@
package com.android.systemui.qs.dagger;
-import com.android.systemui.qs.FooterActionsController;
-import com.android.systemui.qs.QSAnimator;
-import com.android.systemui.qs.QSContainerImplController;
-import com.android.systemui.qs.QSFooter;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.qs.QSPanelController;
-import com.android.systemui.qs.QSSquishinessController;
-import com.android.systemui.qs.QuickQSPanelController;
-import com.android.systemui.qs.customize.QSCustomizerController;
+import com.android.systemui.qs.QSFragmentLegacy;
import dagger.BindsInstance;
import dagger.Subcomponent;
/**
- * Dagger Subcomponent for {@link QSFragment}.
+ * Dagger Subcomponent for {@link QSFragmentLegacy}.
*/
@Subcomponent(modules = {QSFragmentModule.class})
@QSScope
-public interface QSFragmentComponent {
+public interface QSFragmentComponent extends QSComponent {
/** Factory for building a {@link QSFragmentComponent}. */
@Subcomponent.Factory
interface Factory {
- QSFragmentComponent create(@BindsInstance QSFragment qsFragment);
+ QSFragmentComponent create(@BindsInstance QSFragmentLegacy qsFragment);
}
-
- /** Construct a {@link QSPanelController}. */
- QSPanelController getQSPanelController();
-
- /** Construct a {@link QuickQSPanelController}. */
- QuickQSPanelController getQuickQSPanelController();
-
- /** Construct a {@link QSAnimator}. */
- QSAnimator getQSAnimator();
-
- /** Construct a {@link QSContainerImplController}. */
- QSContainerImplController getQSContainerImplController();
-
- /** Construct a {@link QSFooter} */
- QSFooter getQSFooter();
-
- /** Construct a {@link QSCustomizerController}. */
- QSCustomizerController getQSCustomizerController();
-
- /** Construct a {@link QSSquishinessController}. */
- QSSquishinessController getQSSquishinessController();
-
- /** Construct a {@link FooterActionsController}. */
- FooterActionsController getQSFooterActionController();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index bcd9803..0c9c24d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -20,21 +20,11 @@
import static com.android.systemui.util.Utils.useQsMediaPlayer;
import android.content.Context;
-import android.view.LayoutInflater;
import android.view.View;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qs.QSContainerImpl;
-import com.android.systemui.qs.QSFooter;
-import com.android.systemui.qs.QSFooterView;
-import com.android.systemui.qs.QSFooterViewController;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QuickQSPanel;
-import com.android.systemui.qs.QuickStatusBarHeader;
-import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.qs.QSFragmentLegacy;
import javax.inject.Named;
@@ -45,93 +35,31 @@
/**
* Dagger Module for {@link QSFragmentComponent}.
*/
-@Module
-public interface QSFragmentModule {
- String QS_USING_MEDIA_PLAYER = "qs_using_media_player";
- String QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media";
+@Module(includes = {QSScopeModule.class})
+public interface QSFragmentModule {
- /**
- * Provide a context themed using the QS theme
- */
- @Provides
- @QSThemedContext
- static Context provideThemedContext(@RootView View view) {
- return view.getContext();
- }
-
- /** */
- @Provides
- @QSThemedContext
- static LayoutInflater provideThemedLayoutInflater(@QSThemedContext Context context) {
- return LayoutInflater.from(context);
- }
-
- /** */
@Provides
@RootView
- static View provideRootView(QSFragment qsFragment) {
+ static View provideRootView(QSFragmentLegacy qsFragment) {
return qsFragment.getView();
}
/** */
- @Provides
- static QSPanel provideQSPanel(@RootView View view) {
- return view.findViewById(R.id.quick_settings_panel);
- }
-
- /** */
- @Provides
- static QSContainerImpl providesQSContainerImpl(@RootView View view) {
- return view.findViewById(R.id.quick_settings_container);
- }
-
- /** */
@Binds
- QS bindQS(QSFragment qsFragment);
+ QS bindQS(QSFragmentLegacy qsFragment);
/** */
@Provides
- static QuickStatusBarHeader providesQuickStatusBarHeader(@RootView View view) {
- return view.findViewById(R.id.header);
- }
-
- /** */
- @Provides
- static QuickQSPanel providesQuickQSPanel(QuickStatusBarHeader quickStatusBarHeader) {
- return quickStatusBarHeader.findViewById(R.id.quick_qs_panel);
- }
-
- /** */
- @Provides
- static QSFooterView providesQSFooterView(@RootView View view) {
- return view.findViewById(R.id.qs_footer);
- }
-
- /** */
- @Provides
- @QSScope
- static QSFooter providesQSFooter(QSFooterViewController qsFooterViewController) {
- qsFooterViewController.init();
- return qsFooterViewController;
- }
-
- /** */
- @Provides
- @QSScope
- static QSCustomizer providesQSCutomizer(@RootView View view) {
- return view.findViewById(R.id.qs_customize);
- }
-
- /** */
- @Provides
- @Named(QS_USING_MEDIA_PLAYER)
+ @Named(QSScopeModule.QS_USING_MEDIA_PLAYER)
static boolean providesQSUsingMediaPlayer(Context context) {
return useQsMediaPlayer(context);
}
+
+
/** */
@Provides
- @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
+ @Named(QSScopeModule.QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
static boolean providesQSUsingCollapsedLandscapeMedia(Context context) {
return useCollapsedMediaInLandscape(context.getResources());
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 03de3a0..92490e8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -52,7 +52,7 @@
/**
* Module for QS dependencies
*/
-@Module(subcomponents = {QSFragmentComponent.class},
+@Module(subcomponents = {QSFragmentComponent.class, QSFlexiglassComponent.class},
includes = {
MediaModule.class,
QSExternalModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSScopeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSScopeModule.kt
new file mode 100644
index 0000000..e68ec4c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSScopeModule.kt
@@ -0,0 +1,92 @@
+package com.android.systemui.qs.dagger
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import com.android.systemui.dagger.qualifiers.RootView
+import com.android.systemui.qs.QSContainerImpl
+import com.android.systemui.qs.QSFooter
+import com.android.systemui.qs.QSFooterView
+import com.android.systemui.qs.QSFooterViewController
+import com.android.systemui.qs.QSPanel
+import com.android.systemui.qs.QuickQSPanel
+import com.android.systemui.qs.QuickStatusBarHeader
+import com.android.systemui.qs.customize.QSCustomizer
+import com.android.systemui.res.R
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface QSScopeModule {
+ companion object {
+ const val QS_USING_MEDIA_PLAYER = "qs_using_media_player"
+ const val QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media"
+
+ @Provides
+ @QSThemedContext
+ @JvmStatic
+ fun provideThemedContext(@RootView view: View): Context {
+ return view.context
+ }
+
+ /** */
+ @Provides
+ @QSThemedContext
+ @JvmStatic
+ fun provideThemedLayoutInflater(@QSThemedContext context: Context): LayoutInflater {
+ return LayoutInflater.from(context)
+ }
+
+ /** */
+ @Provides
+ @JvmStatic
+ fun provideQSPanel(@RootView view: View): QSPanel {
+ return view.requireViewById<QSPanel>(R.id.quick_settings_panel)
+ }
+
+ /** */
+ @Provides
+ @JvmStatic
+ fun providesQSContainerImpl(@RootView view: View): QSContainerImpl {
+ return view.requireViewById<QSContainerImpl>(R.id.quick_settings_container)
+ }
+
+ /** */
+ @Provides
+ @JvmStatic
+ fun providesQuickStatusBarHeader(@RootView view: View): QuickStatusBarHeader {
+ return view.requireViewById<QuickStatusBarHeader>(R.id.header)
+ }
+
+ /** */
+ @Provides
+ @JvmStatic
+ fun providesQuickQSPanel(quickStatusBarHeader: QuickStatusBarHeader): QuickQSPanel {
+ return quickStatusBarHeader.requireViewById<QuickQSPanel>(R.id.quick_qs_panel)
+ }
+
+ /** */
+ @Provides
+ @JvmStatic
+ fun providesQSFooterView(@RootView view: View): QSFooterView {
+ return view.requireViewById<QSFooterView>(R.id.qs_footer)
+ }
+
+ /** */
+ @Provides
+ @QSScope
+ @JvmStatic
+ fun providesQSFooter(qsFooterViewController: QSFooterViewController): QSFooter {
+ qsFooterViewController.init()
+ return qsFooterViewController
+ }
+
+ /** */
+ @Provides
+ @QSScope
+ @JvmStatic
+ fun providesQSCutomizer(@RootView view: View): QSCustomizer {
+ return view.requireViewById<QSCustomizer>(R.id.qs_customize)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index b394a07..8b2c3de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -72,7 +72,7 @@
* Show the device monitoring dialog, expanded from [expandable] if it's not null.
*
* Important: [quickSettingsContext] *must* be the [Context] associated to the
- * [Quick Settings fragment][com.android.systemui.qs.QSFragment].
+ * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy].
*/
fun showDeviceMonitoringDialog(quickSettingsContext: Context, expandable: Expandable?)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 769cb1f..64fa33c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -23,7 +23,6 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.android.settingslib.Utils
-import com.android.systemui.res.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -34,6 +33,7 @@
import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
+import com.android.systemui.res.R
import com.android.systemui.util.icuMessageFormat
import javax.inject.Inject
import javax.inject.Named
@@ -43,7 +43,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -201,8 +200,8 @@
* will suspend indefinitely and will need to be cancelled to stop observing.
*
* Important: [quickSettingsContext] must be the [Context] associated to the
- * [Quick Settings fragment][com.android.systemui.qs.QSFragment], and the call to this function
- * must be cancelled when that fragment is destroyed.
+ * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this
+ * function must be cancelled when that fragment is destroyed.
*/
suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
footerActionsInteractor.deviceMonitoringDialogRequests.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index a4600fb..21aaa94 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -20,8 +20,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostRepository
+import com.android.systemui.qs.pipeline.data.repository.DefaultTilesRepository
import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepositoryImpl
+import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredBroadcastRepository
+import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -42,6 +46,11 @@
abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository
@Binds
+ abstract fun provideDefaultTilesRepository(
+ impl: DefaultTilesQSHostRepository
+ ): DefaultTilesRepository
+
+ @Binds
abstract fun bindCurrentTilesInteractor(
impl: CurrentTilesInteractorImpl
): CurrentTilesInteractor
@@ -56,6 +65,11 @@
@ClassKey(QSPipelineCoreStartable::class)
abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable
+ @Binds
+ abstract fun provideQSSettingsRestoredRepository(
+ impl: QSSettingsRestoredBroadcastRepository
+ ): QSSettingsRestoredRepository
+
companion object {
/**
* Provides a logging buffer for all logs related to the new Quick Settings pipeline to log
@@ -67,5 +81,12 @@
fun provideQSTileListLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create(QSPipelineLogger.TILE_LIST_TAG, maxSize = 700, systrace = false)
}
+
+ @Provides
+ @SysUISingleton
+ @QSRestoreLog
+ fun providesQSRestoreLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create(QSPipelineLogger.RESTORE_TAG, maxSize = 50, systrace = false)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSRestoreLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSRestoreLog.kt
new file mode 100644
index 0000000..c964929
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSRestoreLog.kt
@@ -0,0 +1,6 @@
+package com.android.systemui.qs.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for the QS pipeline to track restore of associated settings. */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class QSRestoreLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreData.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreData.kt
new file mode 100644
index 0000000..d962632
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreData.kt
@@ -0,0 +1,10 @@
+package com.android.systemui.qs.pipeline.data.model
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/** Data restored from Quick Settings as part of Backup & Restore. */
+data class RestoreData(
+ val restoredTiles: List<TileSpec>,
+ val restoredAutoAddedTiles: Set<TileSpec>,
+ val userId: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
index 43a16b6..7998dfb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
@@ -16,28 +16,19 @@
package com.android.systemui.qs.pipeline.data.repository
-import android.database.ContentObserver
-import android.provider.Settings
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import android.util.SparseArray
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.util.settings.SecureSettings
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.withContext
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
/** Repository to track what QS tiles have been auto-added */
interface AutoAddRepository {
/** Flow of tiles that have been auto-added */
- fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>>
+ suspend fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>>
/** Mark a tile as having been auto-added */
suspend fun markTileAdded(userId: Int, spec: TileSpec)
@@ -47,89 +38,39 @@
* multiple times.
*/
suspend fun unmarkTileAdded(userId: Int, spec: TileSpec)
+
+ suspend fun reconcileRestore(restoreData: RestoreData)
}
/**
- * Implementation that tracks the auto-added tiles stored in [Settings.Secure.QS_AUTO_ADDED_TILES].
+ * Implementation of [AutoAddRepository] that delegates to an instance of [UserAutoAddRepository]
+ * for each user.
*/
@SysUISingleton
class AutoAddSettingRepository
@Inject
-constructor(
- private val secureSettings: SecureSettings,
- @Background private val bgDispatcher: CoroutineDispatcher,
-) : AutoAddRepository {
- override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
- return conflatedCallbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
+constructor(private val userAutoAddRepositoryFactory: UserAutoAddRepository.Factory) :
+ AutoAddRepository {
- secureSettings.registerContentObserverForUser(SETTING, observer, userId)
+ private val userAutoAddRepositories = SparseArray<UserAutoAddRepository>()
- awaitClose { secureSettings.unregisterContentObserver(observer) }
- }
- .onStart { emit(Unit) }
- .map { secureSettings.getStringForUser(SETTING, userId) ?: "" }
- .distinctUntilChanged()
- .map {
- it.split(DELIMITER).map(TileSpec::create).filter { it !is TileSpec.Invalid }.toSet()
- }
- .flowOn(bgDispatcher)
+ override suspend fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
+ if (userId !in userAutoAddRepositories) {
+ val repository = userAutoAddRepositoryFactory.create(userId)
+ userAutoAddRepositories.put(userId, repository)
+ }
+ return userAutoAddRepositories.get(userId).autoAdded()
}
override suspend fun markTileAdded(userId: Int, spec: TileSpec) {
- if (spec is TileSpec.Invalid) {
- return
- }
- val added = load(userId).toMutableSet()
- if (added.add(spec)) {
- store(userId, added)
- }
+ userAutoAddRepositories.get(userId)?.markTileAdded(spec)
}
override suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) {
- if (spec is TileSpec.Invalid) {
- return
- }
- val added = load(userId).toMutableSet()
- if (added.remove(spec)) {
- store(userId, added)
- }
+ userAutoAddRepositories.get(userId)?.unmarkTileAdded(spec)
}
- private suspend fun store(userId: Int, tiles: Set<TileSpec>) {
- val toStore =
- tiles
- .filter { it !is TileSpec.Invalid }
- .joinToString(DELIMITER, transform = TileSpec::spec)
- withContext(bgDispatcher) {
- secureSettings.putStringForUser(
- SETTING,
- toStore,
- null,
- false,
- userId,
- true,
- )
- }
- }
-
- private suspend fun load(userId: Int): Set<TileSpec> {
- return withContext(bgDispatcher) {
- (secureSettings.getStringForUser(SETTING, userId) ?: "")
- .split(",")
- .map(TileSpec::create)
- .filter { it !is TileSpec.Invalid }
- .toSet()
- }
- }
-
- companion object {
- private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
- private const val DELIMITER = ","
+ override suspend fun reconcileRestore(restoreData: RestoreData) {
+ userAutoAddRepositories.get(restoreData.userId)?.reconcileRestore(restoreData)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt
new file mode 100644
index 0000000..fe0a69b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt
@@ -0,0 +1,25 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.res.Resources
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+interface DefaultTilesRepository {
+ val defaultTiles: List<TileSpec>
+}
+
+@SysUISingleton
+class DefaultTilesQSHostRepository
+@Inject
+constructor(
+ @Main private val resources: Resources,
+) : DefaultTilesRepository {
+ override val defaultTiles: List<TileSpec>
+ get() =
+ QSHost.getDefaultSpecs(resources).map(TileSpec::create).filter {
+ it != TileSpec.Invalid
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
new file mode 100644
index 0000000..6cee116
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
@@ -0,0 +1,122 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter.toTilesList
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
+
+/** Provides restored data (from Backup and Restore) for Quick Settings pipeline */
+interface QSSettingsRestoredRepository {
+ val restoreData: Flow<RestoreData>
+}
+
+@SysUISingleton
+class QSSettingsRestoredBroadcastRepository
+@Inject
+constructor(
+ broadcastDispatcher: BroadcastDispatcher,
+ logger: QSPipelineLogger,
+ @Application private val scope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : QSSettingsRestoredRepository {
+
+ override val restoreData =
+ flow {
+ val firstIntent = mutableMapOf<Int, Intent>()
+ broadcastDispatcher
+ .broadcastFlow(INTENT_FILTER, UserHandle.ALL) { intent, receiver ->
+ intent to receiver.sendingUserId
+ }
+ .filter { it.first.isCorrectSetting() }
+ .collect { (intent, user) ->
+ if (user !in firstIntent) {
+ firstIntent[user] = intent
+ } else {
+ val firstRestored = firstIntent.remove(user)!!
+ emit(processIntents(user, firstRestored, intent))
+ }
+ }
+ }
+ .catch { Log.e(TAG, "Error parsing broadcast", it) }
+ .flowOn(backgroundDispatcher)
+ .buffer(BUFFER_CAPACITY)
+ .shareIn(scope, SharingStarted.Eagerly)
+ .onEach(logger::logSettingsRestored)
+
+ private fun processIntents(user: Int, intent1: Intent, intent2: Intent): RestoreData {
+ intent1.validateIntent()
+ intent2.validateIntent()
+ val setting1 = intent1.getStringExtra(Intent.EXTRA_SETTING_NAME)
+ val setting2 = intent2.getStringExtra(Intent.EXTRA_SETTING_NAME)
+ val (tiles, autoAdd) =
+ if (setting1 == TILES_SETTING && setting2 == AUTO_ADD_SETTING) {
+ intent1 to intent2
+ } else if (setting1 == AUTO_ADD_SETTING && setting2 == TILES_SETTING) {
+ intent2 to intent1
+ } else {
+ throw IllegalStateException("Wrong intents ($intent1, $intent2)")
+ }
+
+ return RestoreData(
+ (tiles.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?: "").toTilesList(),
+ (autoAdd.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?: "").toTilesSet(),
+ user,
+ )
+ }
+
+ private companion object {
+ private const val TAG = "QSSettingsRestoredBroadcastRepository"
+ // This capacity is the number of restore data that we will keep buffered in the shared
+ // flow. It is unlikely that at any given time there would be this many restores being
+ // processed by consumers, but just in case that a couple of users are restored at the
+ // same time and they need to be replayed for the consumers of the flow.
+ private const val BUFFER_CAPACITY = 10
+
+ private val INTENT_FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
+ private const val TILES_SETTING = Settings.Secure.QS_TILES
+ private const val AUTO_ADD_SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
+ private val requiredExtras =
+ listOf(
+ Intent.EXTRA_SETTING_NAME,
+ Intent.EXTRA_SETTING_PREVIOUS_VALUE,
+ Intent.EXTRA_SETTING_NEW_VALUE,
+ )
+
+ private fun Intent.isCorrectSetting(): Boolean {
+ val setting = getStringExtra(Intent.EXTRA_SETTING_NAME)
+ return setting == TILES_SETTING || setting == AUTO_ADD_SETTING
+ }
+
+ private fun Intent.validateIntent() {
+ requiredExtras.forEach { extra ->
+ if (!hasExtra(extra)) {
+ throw IllegalStateException("$this doesn't have $extra")
+ }
+ }
+ }
+
+ private fun String.toTilesList() = toTilesList(this)
+
+ private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 47c99f2..00ea0b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -18,34 +18,19 @@
import android.annotation.UserIdInt
import android.content.res.Resources
-import android.database.ContentObserver
-import android.provider.Settings
import android.util.SparseArray
import com.android.systemui.res.R
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.retail.data.repository.RetailModeRepository
-import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.withContext
/** Repository that tracks the current tiles. */
interface TileSpecRepository {
@@ -55,7 +40,7 @@
*
* Tiles will never be [TileSpec.Invalid] in the list and it will never be empty.
*/
- fun tilesSpecs(@UserIdInt userId: Int): Flow<List<TileSpec>>
+ suspend fun tilesSpecs(@UserIdInt userId: Int): Flow<List<TileSpec>>
/**
* Adds a [tile] for a given [userId] at [position]. Using [POSITION_AT_END] will add the tile
@@ -81,6 +66,8 @@
*/
suspend fun setTiles(@UserIdInt userId: Int, tiles: List<TileSpec>)
+ suspend fun reconcileRestore(restoreData: RestoreData, currentAutoAdded: Set<TileSpec>)
+
companion object {
/** Position to indicate the end of the list */
const val POSITION_AT_END = -1
@@ -88,28 +75,23 @@
}
/**
- * Implementation of [TileSpecRepository] that persist the values of tiles in
- * [Settings.Secure.QS_TILES].
- *
- * All operations against [Settings] will be performed in a background thread.
+ * Implementation of [TileSpecRepository] that delegates to an instance of [UserTileSpecRepository]
+ * for each user.
*
* If the device is in retail mode, the tiles are fixed to the value of
* [R.string.quick_settings_tiles_retail_mode].
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class TileSpecSettingsRepository
@Inject
constructor(
- private val secureSettings: SecureSettings,
@Main private val resources: Resources,
private val logger: QSPipelineLogger,
private val retailModeRepository: RetailModeRepository,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val userTileSpecRepositoryFactory: UserTileSpecRepository.Factory,
) : TileSpecRepository {
- private val mutex = Mutex()
- private val tileSpecsPerUser = SparseArray<List<TileSpec>>()
-
private val retailModeTiles by lazy {
resources
.getString(R.string.quick_settings_tiles_retail_mode)
@@ -118,123 +100,59 @@
.filter { it !is TileSpec.Invalid }
}
- @OptIn(ExperimentalCoroutinesApi::class)
- override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+ private val userTileRepositories = SparseArray<UserTileSpecRepository>()
+
+ override suspend fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+ if (userId !in userTileRepositories) {
+ val userTileRepository = userTileSpecRepositoryFactory.create(userId)
+ userTileRepositories.put(userId, userTileRepository)
+ }
+ val realTiles = userTileRepositories.get(userId).tiles()
+
return retailModeRepository.retailMode.flatMapLatest { inRetailMode ->
if (inRetailMode) {
logger.logUsingRetailTiles()
flowOf(retailModeTiles)
} else {
- settingsTiles(userId)
+ realTiles
}
}
}
- private fun settingsTiles(userId: Int): Flow<List<TileSpec>> {
- return conflatedCallbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
-
- secureSettings.registerContentObserverForUser(SETTING, observer, userId)
-
- awaitClose { secureSettings.unregisterContentObserver(observer) }
- }
- .onStart { emit(Unit) }
- .map { loadTiles(userId) }
- .onEach { logger.logTilesChangedInSettings(it, userId) }
- .distinctUntilChanged()
- .map { parseTileSpecs(it, userId).also { storeTiles(userId, it) } }
- .distinctUntilChanged()
- .onEach { mutex.withLock { tileSpecsPerUser.put(userId, it) } }
- .flowOn(backgroundDispatcher)
- }
-
- override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) =
- mutex.withLock {
- if (tile == TileSpec.Invalid) {
- return
- }
- val tilesList = tileSpecsPerUser.get(userId, emptyList()).toMutableList()
- if (tile !in tilesList) {
- if (position < 0 || position >= tilesList.size) {
- tilesList.add(tile)
- } else {
- tilesList.add(position, tile)
- }
- storeTiles(userId, tilesList)
- tileSpecsPerUser.put(userId, tilesList)
- }
- }
-
- override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) =
- mutex.withLock {
- if (tiles.all { it == TileSpec.Invalid }) {
- return
- }
- val tilesList = tileSpecsPerUser.get(userId, emptyList()).toMutableList()
- if (tilesList.removeAll(tiles)) {
- storeTiles(userId, tilesList.toList())
- tileSpecsPerUser.put(userId, tilesList)
- }
- }
-
- override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) =
- mutex.withLock {
- val filtered = tiles.filter { it != TileSpec.Invalid }
- if (filtered.isNotEmpty()) {
- storeTiles(userId, filtered)
- tileSpecsPerUser.put(userId, tiles)
- }
- }
-
- private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) {
+ override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
if (retailModeRepository.inRetailMode) {
- // No storing tiles when in retail mode
return
}
- val toStore =
- tiles
- .filter { it !is TileSpec.Invalid }
- .joinToString(DELIMITER, transform = TileSpec::spec)
- withContext(backgroundDispatcher) {
- secureSettings.putStringForUser(
- SETTING,
- toStore,
- null,
- false,
- forUser,
- true,
- )
+ if (tile is TileSpec.Invalid) {
+ return
}
+ userTileRepositories.get(userId)?.addTile(tile, position)
}
- private suspend fun loadTiles(userId: Int): String {
- return withContext(backgroundDispatcher) {
- secureSettings.getStringForUser(SETTING, userId) ?: ""
+ override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
+ if (retailModeRepository.inRetailMode) {
+ return
}
+ userTileRepositories.get(userId)?.removeTiles(tiles)
}
- private fun parseTileSpecs(tilesFromSettings: String, user: Int): List<TileSpec> {
- val fromSettings =
- tilesFromSettings.split(DELIMITER).map(TileSpec::create).filter {
- it != TileSpec.Invalid
- }
- return if (fromSettings.isNotEmpty()) {
- fromSettings.also { logger.logParsedTiles(it, false, user) }
- } else {
- QSHost.getDefaultSpecs(resources)
- .map(TileSpec::create)
- .filter { it != TileSpec.Invalid }
- .also { logger.logParsedTiles(it, true, user) }
+ override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) {
+ if (retailModeRepository.inRetailMode) {
+ return
}
+ userTileRepositories.get(userId)?.setTiles(tiles)
+ }
+
+ override suspend fun reconcileRestore(
+ restoreData: RestoreData,
+ currentAutoAdded: Set<TileSpec>
+ ) {
+ userTileRepositories
+ .get(restoreData.userId)
+ ?.reconcileRestore(restoreData, currentAutoAdded)
}
companion object {
- private const val SETTING = Settings.Secure.QS_TILES
- private const val DELIMITER = ","
+ private const val DELIMITER = TilesSettingConverter.DELIMITER
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverter.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverter.kt
new file mode 100644
index 0000000..bb55fcd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverter.kt
@@ -0,0 +1,18 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+object TilesSettingConverter {
+
+ const val DELIMITER = ","
+
+ fun toTilesList(commaSeparatedTiles: String) =
+ commaSeparatedTiles.split(DELIMITER).map(TileSpec::create).filter { it != TileSpec.Invalid }
+
+ fun toTilesSet(commaSeparatedTiles: String) =
+ commaSeparatedTiles
+ .split(DELIMITER)
+ .map(TileSpec::create)
+ .filter { it != TileSpec.Invalid }
+ .toSet()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt
new file mode 100644
index 0000000..d452241
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt
@@ -0,0 +1,186 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.database.ContentObserver
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.settings.SecureSettings
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Single user version of [AutoAddRepository]. It provides a similar interface as
+ * [AutoAddRepository], but focusing solely on the user it was created for.
+ *
+ * This is the source of truth for that user's tiles, after the user has been started. Persisting
+ * all the changes to [Settings]. Changes in [Settings] that disagree with this repository will be
+ * reverted
+ *
+ * All operations against [Settings] will be performed in a background thread.
+ */
+class UserAutoAddRepository
+@AssistedInject
+constructor(
+ @Assisted private val userId: Int,
+ private val secureSettings: SecureSettings,
+ private val logger: QSPipelineLogger,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+) {
+
+ private val changeEvents = MutableSharedFlow<ChangeAction>(
+ extraBufferCapacity = CHANGES_BUFFER_SIZE
+ )
+
+ private lateinit var _autoAdded: StateFlow<Set<TileSpec>>
+
+ suspend fun autoAdded(): StateFlow<Set<TileSpec>> {
+ if (!::_autoAdded.isInitialized) {
+ _autoAdded =
+ changeEvents
+ .scan(load().also { logger.logAutoAddTilesParsed(userId, it) }) {
+ current,
+ change ->
+ change.apply(current).also {
+ if (change is RestoreTiles) {
+ logger.logAutoAddTilesRestoredReconciled(userId, it)
+ }
+ }
+ }
+ .flowOn(bgDispatcher)
+ .stateIn(applicationScope)
+ .also { startFlowCollections(it) }
+ }
+ return _autoAdded
+ }
+
+ private fun startFlowCollections(autoAdded: StateFlow<Set<TileSpec>>) {
+ applicationScope.launch(bgDispatcher) {
+ launch { autoAdded.collect { store(it) } }
+ launch {
+ // As Settings is not the source of truth, once we started tracking tiles for a
+ // user, we don't want anyone to change the underlying setting. Therefore, if there
+ // are any changes that don't match with the source of truth (this class), we
+ // overwrite them with the current value.
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+ secureSettings.registerContentObserverForUser(SETTING, observer, userId)
+ awaitClose { secureSettings.unregisterContentObserver(observer) }
+ }
+ .map { load() }
+ .flowOn(bgDispatcher)
+ .collect { setting ->
+ val current = autoAdded.value
+ if (setting != current) {
+ store(current)
+ }
+ }
+ }
+ }
+ }
+
+ suspend fun markTileAdded(spec: TileSpec) {
+ if (spec is TileSpec.Invalid) {
+ return
+ }
+ changeEvents.emit(MarkTile(spec))
+ }
+
+ suspend fun unmarkTileAdded(spec: TileSpec) {
+ if (spec is TileSpec.Invalid) {
+ return
+ }
+ changeEvents.emit(UnmarkTile(spec))
+ }
+
+ private suspend fun store(tiles: Set<TileSpec>) {
+ val toStore =
+ tiles
+ .filter { it !is TileSpec.Invalid }
+ .joinToString(DELIMITER, transform = TileSpec::spec)
+ withContext(bgDispatcher) {
+ secureSettings.putStringForUser(
+ SETTING,
+ toStore,
+ null,
+ false,
+ userId,
+ true,
+ )
+ }
+ }
+
+ private suspend fun load(): Set<TileSpec> {
+ return withContext(bgDispatcher) {
+ (secureSettings.getStringForUser(SETTING, userId) ?: "").toTilesSet()
+ }
+ }
+
+ suspend fun reconcileRestore(restoreData: RestoreData) {
+ changeEvents.emit(RestoreTiles(restoreData))
+ }
+
+ private sealed interface ChangeAction {
+ fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec>
+ }
+
+ private data class MarkTile(
+ val tileSpec: TileSpec,
+ ) : ChangeAction {
+ override fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec> {
+ return currentAutoAdded.toMutableSet().apply { add(tileSpec) }
+ }
+ }
+
+ private data class UnmarkTile(
+ val tileSpec: TileSpec,
+ ) : ChangeAction {
+ override fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec> {
+ return currentAutoAdded.toMutableSet().apply { remove(tileSpec) }
+ }
+ }
+
+ private data class RestoreTiles(
+ val restoredData: RestoreData,
+ ) : ChangeAction {
+ override fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec> {
+ return currentAutoAdded + restoredData.restoredAutoAddedTiles
+ }
+ }
+
+ companion object {
+ private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
+ private const val DELIMITER = ","
+ // We want a small buffer in case multiple changes come in at the same time (sometimes
+ // happens in first start. This should be enough to not lose changes.
+ private const val CHANGES_BUFFER_SIZE = 10
+
+ private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(userId: Int): UserAutoAddRepository
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
new file mode 100644
index 0000000..152fd0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -0,0 +1,252 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.annotation.UserIdInt
+import android.database.ContentObserver
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.settings.SecureSettings
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Single user version of [TileSpecRepository]. It provides a similar interface as
+ * [TileSpecRepository], but focusing solely on the user it was created for.
+ *
+ * This is the source of truth for that user's tiles, after the user has been started. Persisting
+ * all the changes to [Settings]. Changes in [Settings] that disagree with this repository will be
+ * reverted
+ *
+ * All operations against [Settings] will be performed in a background thread.
+ */
+class UserTileSpecRepository
+@AssistedInject
+constructor(
+ @Assisted private val userId: Int,
+ private val defaultTilesRepository: DefaultTilesRepository,
+ private val secureSettings: SecureSettings,
+ private val logger: QSPipelineLogger,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+
+ private val defaultTiles: List<TileSpec>
+ get() = defaultTilesRepository.defaultTiles
+
+ private val changeEvents = MutableSharedFlow<ChangeAction>(
+ extraBufferCapacity = CHANGES_BUFFER_SIZE
+ )
+
+ private lateinit var _tiles: StateFlow<List<TileSpec>>
+
+ suspend fun tiles(): Flow<List<TileSpec>> {
+ if (!::_tiles.isInitialized) {
+ _tiles =
+ changeEvents
+ .scan(loadTilesFromSettingsAndParse(userId)) { current, change ->
+ change.apply(current).also {
+ if (current != it) {
+ if (change is RestoreTiles) {
+ logger.logTilesRestoredAndReconciled(current, it, userId)
+ } else {
+ logger.logProcessTileChange(change, it, userId)
+ }
+ }
+ }
+ }
+ .flowOn(backgroundDispatcher)
+ .stateIn(applicationScope)
+ .also { startFlowCollections(it) }
+ }
+ return _tiles
+ }
+
+ private fun startFlowCollections(tiles: StateFlow<List<TileSpec>>) {
+ applicationScope.launch(backgroundDispatcher) {
+ launch { tiles.collect { storeTiles(userId, it) } }
+ launch {
+ // As Settings is not the source of truth, once we started tracking tiles for a
+ // user, we don't want anyone to change the underlying setting. Therefore, if there
+ // are any changes that don't match with the source of truth (this class), we
+ // overwrite them with the current value.
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+ secureSettings.registerContentObserverForUser(SETTING, observer, userId)
+ awaitClose { secureSettings.unregisterContentObserver(observer) }
+ }
+ .map { loadTilesFromSettings(userId) }
+ .flowOn(backgroundDispatcher)
+ .collect { setting ->
+ val current = tiles.value
+ if (setting != current) {
+ storeTiles(userId, current)
+ }
+ }
+ }
+ }
+ }
+
+ private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) {
+ val toStore =
+ tiles
+ .filter { it !is TileSpec.Invalid }
+ .joinToString(DELIMITER, transform = TileSpec::spec)
+ withContext(backgroundDispatcher) {
+ secureSettings.putStringForUser(
+ SETTING,
+ toStore,
+ null,
+ false,
+ forUser,
+ true,
+ )
+ }
+ }
+
+ suspend fun addTile(tile: TileSpec, position: Int = TileSpecRepository.POSITION_AT_END) {
+ if (tile is TileSpec.Invalid) {
+ return
+ }
+ changeEvents.emit(AddTile(tile, position))
+ }
+
+ suspend fun removeTiles(tiles: Collection<TileSpec>) {
+ changeEvents.emit(RemoveTiles(tiles))
+ }
+
+ suspend fun setTiles(tiles: List<TileSpec>) {
+ changeEvents.emit(ChangeTiles(tiles))
+ }
+
+ private fun parseTileSpecs(fromSettings: List<TileSpec>, user: Int): List<TileSpec> {
+ return if (fromSettings.isNotEmpty()) {
+ fromSettings.also { logger.logParsedTiles(it, false, user) }
+ } else {
+ defaultTiles.also { logger.logParsedTiles(it, true, user) }
+ }
+ }
+
+ private suspend fun loadTilesFromSettingsAndParse(userId: Int): List<TileSpec> {
+ return parseTileSpecs(loadTilesFromSettings(userId), userId)
+ }
+
+ private suspend fun loadTilesFromSettings(userId: Int): List<TileSpec> {
+ return withContext(backgroundDispatcher) {
+ secureSettings.getStringForUser(SETTING, userId) ?: ""
+ }
+ .toTilesList()
+ }
+
+ suspend fun reconcileRestore(restoreData: RestoreData, currentAutoAdded: Set<TileSpec>) {
+ changeEvents.emit(RestoreTiles(restoreData, currentAutoAdded))
+ }
+
+ sealed interface ChangeAction {
+ fun apply(currentTiles: List<TileSpec>): List<TileSpec>
+ }
+
+ private data class AddTile(
+ val tileSpec: TileSpec,
+ val position: Int = TileSpecRepository.POSITION_AT_END
+ ) : ChangeAction {
+ override fun apply(currentTiles: List<TileSpec>): List<TileSpec> {
+ val tilesList = currentTiles.toMutableList()
+ if (tileSpec !in tilesList) {
+ if (position < 0 || position >= tilesList.size) {
+ tilesList.add(tileSpec)
+ } else {
+ tilesList.add(position, tileSpec)
+ }
+ }
+ return tilesList
+ }
+ }
+
+ private data class RemoveTiles(val tileSpecs: Collection<TileSpec>) : ChangeAction {
+ override fun apply(currentTiles: List<TileSpec>): List<TileSpec> {
+ return currentTiles.toMutableList().apply { removeAll(tileSpecs) }
+ }
+ }
+
+ private data class ChangeTiles(
+ val newTiles: List<TileSpec>,
+ ) : ChangeAction {
+ override fun apply(currentTiles: List<TileSpec>): List<TileSpec> {
+ val new = newTiles.filter { it !is TileSpec.Invalid }
+ return if (new.isNotEmpty()) new else currentTiles
+ }
+ }
+
+ private data class RestoreTiles(
+ val restoreData: RestoreData,
+ val currentAutoAdded: Set<TileSpec>,
+ ) : ChangeAction {
+
+ override fun apply(currentTiles: List<TileSpec>): List<TileSpec> {
+ return reconcileTiles(currentTiles, currentAutoAdded, restoreData)
+ }
+ }
+
+ companion object {
+ private const val SETTING = Settings.Secure.QS_TILES
+ private const val DELIMITER = TilesSettingConverter.DELIMITER
+ // We want a small buffer in case multiple changes come in at the same time (sometimes
+ // happens in first start. This should be enough to not lose changes.
+ private const val CHANGES_BUFFER_SIZE = 10
+
+ private fun String.toTilesList() = TilesSettingConverter.toTilesList(this)
+
+ fun reconcileTiles(
+ currentTiles: List<TileSpec>,
+ currentAutoAdded: Set<TileSpec>,
+ restoreData: RestoreData
+ ): List<TileSpec> {
+ val toRestore = restoreData.restoredTiles.toMutableList()
+ val freshlyAutoAdded =
+ currentAutoAdded.filterNot { it in restoreData.restoredAutoAddedTiles }
+ freshlyAutoAdded
+ .filter { it in currentTiles && it !in restoreData.restoredTiles }
+ .map { it to currentTiles.indexOf(it) }
+ .sortedBy { it.second }
+ .forEachIndexed { iteration, (tile, position) ->
+ val insertAt = position + iteration
+ if (insertAt > toRestore.size) {
+ toRestore.add(tile)
+ } else {
+ toRestore.add(insertAt, tile)
+ }
+ }
+
+ return toRestore
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ userId: Int,
+ ): UserTileSpecRepository
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 5a5e47a..00c2358 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.content.Intent
import android.os.UserHandle
+import android.util.Log
import com.android.systemui.Dumpable
import com.android.systemui.ProtoDumpable
import com.android.systemui.dagger.SysUISingleton
@@ -268,6 +269,7 @@
// repository
launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
}
+ Log.d("Fabian", "Finished resolving tiles")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt
new file mode 100644
index 0000000..9844903
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt
@@ -0,0 +1,53 @@
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
+import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flatMapConcat
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+
+/**
+ * Interactor in charge of triggering reconciliation after QS Secure Settings are restored. For a
+ * given user, it will trigger the reconciliations in the correct order to prevent race conditions.
+ *
+ * Currently, the order is:
+ * 1. TileSpecRepository, with the restored data and the current (before restore) auto add tiles
+ * 2. AutoAddRepository
+ *
+ * [start] needs to be called to trigger the collection of [QSSettingsRestoredRepository].
+ */
+@SysUISingleton
+class RestoreReconciliationInteractor
+@Inject
+constructor(
+ private val tileSpecRepository: TileSpecRepository,
+ private val autoAddRepository: AutoAddRepository,
+ private val qsSettingsRestoredRepository: QSSettingsRestoredRepository,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ fun start() {
+ applicationScope.launch(backgroundDispatcher) {
+ qsSettingsRestoredRepository.restoreData.flatMapConcat { data ->
+ autoAddRepository.autoAddedTiles(data.userId)
+ .take(1)
+ .map { tiles -> data to tiles }
+ }.collect { (restoreData, autoAdded) ->
+ tileSpecRepository.reconcileRestore(restoreData, autoAdded)
+ autoAddRepository.reconcileRestore(restoreData)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index 0743ba0..1539f05 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -20,6 +20,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.RestoreReconciliationInteractor
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import javax.inject.Inject
@@ -30,11 +31,13 @@
private val currentTilesInteractor: CurrentTilesInteractor,
private val autoAddInteractor: AutoAddInteractor,
private val featureFlags: QSPipelineFlagsRepository,
+ private val restoreReconciliationInteractor: RestoreReconciliationInteractor,
) : CoreStartable {
override fun start() {
if (featureFlags.pipelineAutoAddEnabled) {
autoAddInteractor.init(currentTilesInteractor)
+ restoreReconciliationInteractor.start()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index 573cb715..bca86c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -16,11 +16,13 @@
package com.android.systemui.qs.pipeline.shared.logging
-import android.annotation.UserIdInt
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.qs.pipeline.dagger.QSAutoAddLog
+import com.android.systemui.qs.pipeline.dagger.QSRestoreLog
import com.android.systemui.qs.pipeline.dagger.QSTileListLog
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.UserTileSpecRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
@@ -34,11 +36,13 @@
constructor(
@QSTileListLog private val tileListLogBuffer: LogBuffer,
@QSAutoAddLog private val tileAutoAddLogBuffer: LogBuffer,
+ @QSRestoreLog private val restoreLogBuffer: LogBuffer,
) {
companion object {
const val TILE_LIST_TAG = "QSTileListLog"
const val AUTO_ADD_TAG = "QSAutoAddableLog"
+ const val RESTORE_TAG = "QSRestoreLog"
}
/**
@@ -60,20 +64,37 @@
)
}
- /**
- * Logs when the tiles change in Settings.
- *
- * This could be caused by SystemUI, or restore.
- */
- fun logTilesChangedInSettings(newTiles: String, @UserIdInt user: Int) {
+ fun logTilesRestoredAndReconciled(
+ currentTiles: List<TileSpec>,
+ reconciledTiles: List<TileSpec>,
+ user: Int,
+ ) {
tileListLogBuffer.log(
TILE_LIST_TAG,
- LogLevel.VERBOSE,
+ LogLevel.DEBUG,
{
- str1 = newTiles
+ str1 = currentTiles.toString()
+ str2 = reconciledTiles.toString()
int1 = user
},
- { "Tiles changed in settings for user $int1: $str1" }
+ { "Tiles restored and reconciled for user: $int1\nWas: $str1\nSet to: $str2" }
+ )
+ }
+
+ fun logProcessTileChange(
+ action: UserTileSpecRepository.ChangeAction,
+ newList: List<TileSpec>,
+ userId: Int,
+ ) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = action.toString()
+ str2 = newList.toString()
+ int1 = userId
+ },
+ { "Processing $str1 for user $int1\nNew list: $str2" }
)
}
@@ -139,6 +160,30 @@
)
}
+ fun logAutoAddTilesParsed(userId: Int, tiles: Set<TileSpec>) {
+ tileAutoAddLogBuffer.log(
+ AUTO_ADD_TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = tiles.toString()
+ int1 = userId
+ },
+ { "Auto add tiles parsed for user $int1: $str1" }
+ )
+ }
+
+ fun logAutoAddTilesRestoredReconciled(userId: Int, tiles: Set<TileSpec>) {
+ tileAutoAddLogBuffer.log(
+ AUTO_ADD_TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = tiles.toString()
+ int1 = userId
+ },
+ { "Auto-add tiles reconciled for user $int1: $str1" }
+ )
+ }
+
fun logTileAutoAdded(userId: Int, spec: TileSpec, position: Int) {
tileAutoAddLogBuffer.log(
AUTO_ADD_TAG,
@@ -164,6 +209,23 @@
)
}
+ fun logSettingsRestored(restoreData: RestoreData) {
+ restoreLogBuffer.log(
+ RESTORE_TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = restoreData.userId
+ str1 = restoreData.restoredTiles.toString()
+ str2 = restoreData.restoredAutoAddedTiles.toString()
+ },
+ {
+ "Restored settings data for user $int1\n" +
+ "\tRestored tiles: $str1\n" +
+ "\tRestored auto added tiles: $str2"
+ }
+ )
+ }
+
/** Reasons for destroying an existing tile. */
enum class TileDestroyedReason(val readable: String) {
TILE_REMOVED("Tile removed from current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index f08eb14..4b3bd0b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -53,9 +53,6 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
-import android.media.AudioAttributes;
-import android.media.AudioSystem;
-import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Process;
@@ -86,8 +83,6 @@
import android.window.OnBackInvokedDispatcher;
import android.window.WindowContext;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-
import com.android.internal.app.ChooserActivity;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
@@ -108,7 +103,6 @@
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
-import java.io.File;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
@@ -116,11 +110,11 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Supplier;
+import javax.inject.Provider;
+
/**
* Controls the state and flow for screenshots.
@@ -274,7 +268,8 @@
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mWindowLayoutParams;
private final AccessibilityManager mAccessibilityManager;
- private final ListenableFuture<MediaPlayer> mCameraSound;
+ @Nullable
+ private final ScreenshotSoundController mScreenshotSoundController;
private final ScrollCaptureClient mScrollCaptureClient;
private final PhoneWindow mWindow;
private final DisplayManager mDisplayManager;
@@ -339,6 +334,7 @@
UserManager userManager,
AssistContentRequester assistContentRequester,
MessageContainerController messageContainerController,
+ Provider<ScreenshotSoundController> screenshotSoundController,
@Assisted int displayId
) {
mScreenshotSmartActions = screenshotSmartActions;
@@ -387,8 +383,12 @@
mConfigChanges.applyNewConfig(context.getResources());
reloadAssets();
- // Setup the Camera shutter sound
- mCameraSound = loadCameraSound();
+ // Sound is only reproduced from the controller of the default display.
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ mScreenshotSoundController = screenshotSoundController.get();
+ } else {
+ mScreenshotSoundController = null;
+ }
mCopyBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -573,17 +573,8 @@
}
private void releaseMediaPlayer() {
- // Note that this may block if the sound is still being loaded (very unlikely) but we can't
- // reliably release in the background because the service is being destroyed.
- try {
- MediaPlayer player = mCameraSound.get(1, TimeUnit.SECONDS);
- if (player != null) {
- player.release();
- }
- } catch (InterruptedException | ExecutionException | TimeoutException e) {
- mCameraSound.cancel(true);
- Log.w(TAG, "Error releasing shutter sound", e);
- }
+ if (mScreenshotSoundController == null) return;
+ mScreenshotSoundController.releaseScreenshotSound();
}
private void respondToKeyDismissal() {
@@ -889,39 +880,10 @@
}
}
- private ListenableFuture<MediaPlayer> loadCameraSound() {
- // The media player creation is slow and needs on the background thread.
- return CallbackToFutureAdapter.getFuture((completer) -> {
- mBgExecutor.execute(() -> {
- try {
- MediaPlayer player = MediaPlayer.create(mContext,
- Uri.fromFile(new File(mContext.getResources().getString(
- com.android.internal.R.string.config_cameraShutterSound))),
- null,
- new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .build(), AudioSystem.newAudioSessionId());
- completer.set(player);
- } catch (IllegalStateException e) {
- Log.w(TAG, "Screenshot sound initialization failed", e);
- completer.set(null);
- }
- });
- return "ScreenshotController#loadCameraSound";
- });
- }
-
- private void playCameraSound() {
- mCameraSound.addListener(() -> {
- try {
- MediaPlayer player = mCameraSound.get();
- if (player != null) {
- player.start();
- }
- } catch (InterruptedException | ExecutionException e) {
- }
- }, mBgExecutor);
+ private void playCameraSoundIfNeeded() {
+ if (mScreenshotSoundController == null) return;
+ // the controller is not-null only on the default display controller
+ mScreenshotSoundController.playCameraSound();
}
/**
@@ -930,7 +892,7 @@
*/
private void saveScreenshotAndToast(UserHandle owner, Consumer<Uri> finisher) {
// Play the shutter sound to notify that we've taken a screenshot
- playCameraSound();
+ playCameraSoundIfNeeded();
saveScreenshotInWorkerThread(
owner,
@@ -974,7 +936,7 @@
}
// Play the shutter sound to notify that we've taken a screenshot
- playCameraSound();
+ playCameraSoundIfNeeded();
if (DEBUG_ANIM) {
Log.d(TAG, "starting post-screenshot animation");
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
new file mode 100644
index 0000000..cd0cab5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 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.screenshot
+
+import android.media.MediaPlayer
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.TraceUtils.Companion.tracedAsync
+import com.google.errorprone.annotations.CanIgnoreReturnValue
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.withTimeout
+
+/** Controls sound reproduction after a screenshot is taken. */
+interface ScreenshotSoundController {
+ /** Reproduces the camera sound. */
+ @CanIgnoreReturnValue fun playCameraSound(): Deferred<Unit>
+
+ /** Releases the sound. [playCameraSound] behaviour is undefined after this has been called. */
+ @CanIgnoreReturnValue fun releaseScreenshotSound(): Deferred<Unit>
+}
+
+class ScreenshotSoundControllerImpl
+@Inject
+constructor(
+ private val soundProvider: ScreenshotSoundProvider,
+ @Application private val coroutineScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher
+) : ScreenshotSoundController {
+
+ val player: Deferred<MediaPlayer?> =
+ coroutineScope.tracedAsync("loadCameraSound", bgDispatcher) {
+ try {
+ soundProvider.getScreenshotSound()
+ } catch (e: IllegalStateException) {
+ Log.w(TAG, "Screenshot sound initialization failed", e)
+ null
+ }
+ }
+
+ override fun playCameraSound(): Deferred<Unit> {
+ return coroutineScope.tracedAsync("playCameraSound", bgDispatcher) {
+ player.await()?.start()
+ }
+ }
+ override fun releaseScreenshotSound(): Deferred<Unit> {
+ return coroutineScope.tracedAsync("releaseScreenshotSound", bgDispatcher) {
+ try {
+ withTimeout(1.seconds) { player.await()?.release() }
+ } catch (e: TimeoutCancellationException) {
+ player.cancel()
+ Log.w(TAG, "Error releasing shutter sound", e)
+ }
+ }
+ }
+
+ private companion object {
+ const val TAG = "ScreenshotSoundControllerImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundProvider.kt
new file mode 100644
index 0000000..73151d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundProvider.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 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.screenshot
+
+import android.content.Context
+import android.media.AudioAttributes
+import android.media.AudioSystem
+import android.media.MediaPlayer
+import android.net.Uri
+import com.android.internal.R
+import com.android.systemui.dagger.SysUISingleton
+import java.io.File
+import javax.inject.Inject
+
+/** Provides a [MediaPlayer] that reproduces the screenshot sound. */
+interface ScreenshotSoundProvider {
+
+ /**
+ * Creates a new [MediaPlayer] that reproduces the screenshot sound. This should be called from
+ * a background thread, as it might take time.
+ */
+ fun getScreenshotSound(): MediaPlayer
+}
+
+@SysUISingleton
+class ScreenshotSoundProviderImpl
+@Inject
+constructor(
+ private val context: Context,
+) : ScreenshotSoundProvider {
+ override fun getScreenshotSound(): MediaPlayer {
+ return MediaPlayer.create(
+ context,
+ Uri.fromFile(File(context.resources.getString(R.string.config_cameraShutterSound))),
+ /* holder = */ null,
+ AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build(),
+ AudioSystem.newAudioSessionId()
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 7d17d4c..3797b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -25,6 +25,10 @@
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
import com.android.systemui.screenshot.ScreenshotProxyService;
import com.android.systemui.screenshot.ScreenshotRequestProcessor;
+import com.android.systemui.screenshot.ScreenshotSoundController;
+import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
+import com.android.systemui.screenshot.ScreenshotSoundProvider;
+import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
import com.android.systemui.screenshot.TakeScreenshotService;
import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
import com.android.systemui.screenshot.appclips.AppClipsService;
@@ -69,4 +73,12 @@
@Binds
abstract ScreenshotRequestProcessor bindScreenshotRequestProcessor(
RequestProcessor requestProcessor);
+
+ @Binds
+ abstract ScreenshotSoundProvider bindScreenshotSoundProvider(
+ ScreenshotSoundProviderImpl screenshotSoundProviderImpl);
+
+ @Binds
+ abstract ScreenshotSoundController bindScreenshotSoundController(
+ ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index e8be40e..9b74ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -791,7 +791,8 @@
/** update Qs height state */
public void setExpansionHeight(float height) {
// TODO(b/277909752): remove below log when bug is fixed
- if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0) {
+ if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0
+ && mBarState == SHADE) {
Log.wtf(TAG,
"setting QS height to 0 in split shade while shade is open(ing). "
+ "Value of mExpandImmediate = " + mExpandImmediate);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 670fb12..93bb435 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -172,6 +172,7 @@
private static final int MSG_LOCK_TASK_MODE_CHANGED = 75 << MSG_SHIFT;
private static final int MSG_CONFIRM_IMMERSIVE_PROMPT = 77 << MSG_SHIFT;
private static final int MSG_IMMERSIVE_CHANGED = 78 << MSG_SHIFT;
+ private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1;
@@ -301,6 +302,8 @@
default void addQsTile(ComponentName tile) { }
default void remQsTile(ComponentName tile) { }
+
+ default void setQsTiles(String[] tiles) {}
default void clickTile(ComponentName tile) { }
default void handleSystemKey(KeyEvent arg1) { }
@@ -903,6 +906,13 @@
}
@Override
+ public void setQsTiles(String[] tiles) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_SET_QS_TILES, tiles).sendToTarget();
+ }
+ }
+
+ @Override
public void clickQsTile(ComponentName tile) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_CLICK_QS_TILE, tile).sendToTarget();
@@ -1533,6 +1543,11 @@
mCallbacks.get(i).remQsTile((ComponentName) msg.obj);
}
break;
+ case MSG_SET_QS_TILES:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).setQsTiles((String[]) msg.obj);
+ }
+ break;
case MSG_CLICK_QS_TILE:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).clickTile((ComponentName) msg.obj);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 485ab32..f7ff39c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -75,6 +75,7 @@
import dagger.Lazy;
+import java.util.Arrays;
import java.util.Optional;
import javax.inject.Inject;
@@ -201,6 +202,11 @@
}
@Override
+ public void setQsTiles(String[] tiles) {
+ mQSHost.changeTilesByUser(mQSHost.getSpecs(), Arrays.stream(tiles).toList());
+ }
+
+ @Override
public void clickTile(ComponentName tile) {
// Can't inject this because it changes with the QS fragment
QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 6f69ea81..05beded 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -24,9 +24,11 @@
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
+
import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
import static androidx.lifecycle.Lifecycle.State.RESUMED;
+
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -121,7 +123,6 @@
import com.android.systemui.EventLogTags;
import com.android.systemui.InitController;
import com.android.systemui.Prefs;
-import com.android.systemui.res.R;
import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
@@ -165,8 +166,9 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
-import com.android.systemui.qs.QSFragment;
+import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.UserTracker;
@@ -244,8 +246,6 @@
import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
import com.android.wm.shell.startingsurface.StartingSurface;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
@@ -257,6 +257,8 @@
import javax.inject.Named;
import javax.inject.Provider;
+import dagger.Lazy;
+
/**
* A class handling initialization and coordination between some of the key central surfaces in
* System UI: The notification shade, the keyguard (lockscreen), and the status bar.
@@ -1346,9 +1348,10 @@
});
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
- if (qs instanceof QSFragment) {
- mQSPanelController = ((QSFragment) qs).getQSPanelController();
- ((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController);
+ if (qs instanceof QSFragmentLegacy) {
+ QSFragmentLegacy qsFragment = (QSFragmentLegacy) qs;
+ mQSPanelController = qsFragment.getQSPanelController();
+ qsFragment.setBrightnessMirrorController(mBrightnessMirrorController);
}
});
}
@@ -1502,7 +1505,7 @@
protected QS createDefaultQSFragment() {
return mFragmentService
.getFragmentHostManager(getNotificationShadeWindowView())
- .create(QSFragment.class);
+ .create(QSFragmentLegacy.class);
}
private void setUpPresenter() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index b53939e..a27e67b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -112,6 +112,9 @@
mDisplayCutout = null;
}
+ // Per b/300629388, we let the PhoneStatusBarView detect onConfigurationChanged to
+ // updateResources, instead of letting the PhoneStatusBarViewController detect onConfigChanged
+ // then notify PhoneStatusBarView.
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index e1096e2..f9702dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone
import android.app.StatusBarManager.WINDOW_STATUS_BAR
-import android.content.res.Configuration
import android.graphics.Point
import android.util.Log
import android.view.MotionEvent
@@ -72,10 +71,6 @@
private val configurationListener =
object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- mView.updateResources()
- }
-
override fun onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 02473f2..aacdc63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -186,6 +186,10 @@
}
)
}
+
+ fun logOnSimStateChanged() {
+ buffer.log(TAG, LogLevel.INFO, "onSimStateChanged")
+ }
}
private const val TAG = "MobileInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index ea77163..cf1c97c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -90,4 +90,12 @@
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
val defaultMobileIconGroup: Flow<MobileIconGroup>
+
+ /**
+ * If any active SIM on the device is in
+ * [android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED] or
+ * [android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED] or
+ * [android.telephony.TelephonyManager.SIM_STATE_PERM_DISABLED]
+ */
+ val isAnySimSecure: Flow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 991ff56..2291631 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -151,6 +151,8 @@
override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
activeRepo.flatMapLatest { it.defaultMobileIconGroup }
+ override val isAnySimSecure: Flow<Boolean> = activeRepo.flatMapLatest { it.isAnySimSecure }
+
override val defaultDataSubId: StateFlow<Int> =
activeRepo
.flatMapLatest { it.defaultDataSubId }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index ee13d93..c7987e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -134,6 +134,8 @@
override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
+ override val isAnySimSecure: Flow<Boolean> = flowOf(false)
+
override val defaultMobileIconMapping = MutableStateFlow(TelephonyIcons.ICON_NAME_TO_ICON)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index ec54f08..ecb80f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -28,9 +28,10 @@
import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
import com.android.internal.telephony.PhoneConstants
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings.Config
-import com.android.systemui.res.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -38,6 +39,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
@@ -94,6 +96,7 @@
// See [CarrierMergedConnectionRepository] for details.
wifiRepository: WifiRepository,
private val fullMobileRepoFactory: FullMobileConnectionRepository.Factory,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : MobileConnectionsRepository {
private var subIdRepositoryCache: MutableMap<Int, FullMobileConnectionRepository> =
mutableMapOf()
@@ -253,6 +256,27 @@
.distinctUntilChanged()
.onEach { logger.logDefaultMobileIconGroup(it) }
+ override val isAnySimSecure: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onSimStateChanged(subId: Int, slotId: Int, simState: Int) {
+ logger.logOnSimStateChanged()
+ trySend(keyguardUpdateMonitor.isSimPinSecure)
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ trySend(false)
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "isAnySimSecure",
+ initialValue = false,
+ )
+ .distinctUntilChanged()
+
override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository =
getOrCreateRepoForSubId(subId)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 360fa56..944b059 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -32,7 +32,6 @@
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
-import com.android.systemui.res.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
@@ -48,6 +47,10 @@
import com.android.systemui.keyguard.data.repository.BiometricType.SIDE_FINGERPRINT
import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT
import com.android.systemui.keyguard.shared.model.DevicePosture
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.eq
@@ -87,6 +90,7 @@
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var biometricManager: BiometricManager
+ @Mock private lateinit var tableLogger: TableLogBuffer
@Captor
private lateinit var strongAuthTracker: ArgumentCaptor<LockPatternUtils.StrongAuthTracker>
@Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback>
@@ -97,6 +101,7 @@
private lateinit var devicePostureRepository: FakeDevicePostureRepository
private lateinit var facePropertyRepository: FakeFacePropertyRepository
private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
@@ -112,6 +117,8 @@
devicePostureRepository = FakeDevicePostureRepository()
facePropertyRepository = FakeFacePropertyRepository()
fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ mobileConnectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
}
private suspend fun createBiometricSettingsRepository() {
@@ -132,6 +139,7 @@
dumpManager = dumpManager,
facePropertyRepository = facePropertyRepository,
fingerprintPropertyRepository = fingerprintPropertyRepository,
+ mobileConnectionsRepository = mobileConnectionsRepository,
)
testScope.runCurrent()
fingerprintPropertyRepository.setProperties(
@@ -421,6 +429,50 @@
}
@Test
+ fun anySimSecure_disablesFaceAuth() =
+ testScope.runTest {
+ faceAuthIsEnrolled()
+ createBiometricSettingsRepository()
+
+ faceAuthIsEnabledByBiometricManager()
+ doNotDisableKeyguardAuthFeatures()
+ mobileConnectionsRepository.isAnySimSecure.value = false
+ runCurrent()
+
+ val isFaceAuthEnabledAndEnrolled by
+ collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+
+ assertThat(isFaceAuthEnabledAndEnrolled).isTrue()
+
+ mobileConnectionsRepository.isAnySimSecure.value = true
+ runCurrent()
+
+ assertThat(isFaceAuthEnabledAndEnrolled).isFalse()
+ }
+
+ @Test
+ fun anySimSecure_disablesFaceAuthToNotCurrentlyRun() =
+ testScope.runTest {
+ faceAuthIsEnrolled()
+
+ createBiometricSettingsRepository()
+ val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
+
+ deviceIsInPostureThatSupportsFaceAuth()
+ doNotDisableKeyguardAuthFeatures()
+ faceAuthIsStrongBiometric()
+ faceAuthIsEnabledByBiometricManager()
+ mobileConnectionsRepository.isAnySimSecure.value = false
+
+ onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+ onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+ assertThat(isFaceAuthCurrentlyAllowed).isTrue()
+
+ mobileConnectionsRepository.isAnySimSecure.value = true
+ assertThat(isFaceAuthCurrentlyAllowed).isFalse()
+ }
+
+ @Test
fun biometricManagerControlsFaceAuthenticationEnabledStatus() =
testScope.runTest {
faceAuthIsEnrolled()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
index 93f316e..9e5d3bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
@@ -29,15 +29,16 @@
import org.mockito.Mockito.mock
@SmallTest
-class QSFragmentDisableFlagsLoggerTest : SysuiTestCase() {
+class QSDisableFlagsLoggerTest : SysuiTestCase() {
- private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
- .create("buffer", 10)
- private val disableFlagsLogger = DisableFlagsLogger(
- listOf(DisableFlagsLogger.DisableFlag(0b001, 'A', 'a')),
- listOf(DisableFlagsLogger.DisableFlag(0b001, 'B', 'b'))
- )
- private val logger = QSFragmentDisableFlagsLogger(buffer, disableFlagsLogger)
+ private val buffer =
+ LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)).create("buffer", 10)
+ private val disableFlagsLogger =
+ DisableFlagsLogger(
+ listOf(DisableFlagsLogger.DisableFlag(0b001, 'A', 'a')),
+ listOf(DisableFlagsLogger.DisableFlag(0b001, 'B', 'b'))
+ )
+ private val logger = QSDisableFlagsLogger(buffer, disableFlagsLogger)
@Test
fun logDisableFlagChange_bufferHasStates() {
@@ -48,9 +49,8 @@
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
- val expectedLogString = disableFlagsLogger.getDisableFlagsString(
- new = state, newAfterLocalModification = state
- )
+ val expectedLogString =
+ disableFlagsLogger.getDisableFlagsString(new = state, newAfterLocalModification = state)
assertThat(actualString).contains(expectedLogString)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
similarity index 67%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index c4c233c..d57765c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -1,15 +1,17 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2023 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
+ * 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.
+ * 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.qs;
@@ -34,7 +36,6 @@
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
-import android.app.Fragment;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
@@ -49,12 +50,12 @@
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.res.R;
-import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.qs.customize.QSCustomizerController;
-import com.android.systemui.qs.dagger.QSFragmentComponent;
+import com.android.systemui.qs.dagger.QSComponent;
import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
@@ -66,8 +67,8 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.util.animation.UniqueObjectHostView;
import org.junit.Before;
@@ -79,10 +80,9 @@
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
-public class QSFragmentTest extends SysuiBaseFragmentTest {
+public class QSImplTest extends SysuiTestCase {
- @Mock private QSFragmentComponent.Factory mQsComponentFactory;
- @Mock private QSFragmentComponent mQsFragmentComponent;
+ @Mock private QSComponent mQsComponent;
@Mock private QSPanelController mQSPanelController;
@Mock private MediaHost mQSMediaHost;
@Mock private MediaHost mQQSMediaHost;
@@ -107,69 +107,54 @@
@Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
@Mock private FooterActionsViewBinder mFooterActionsViewBinder;
@Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
- @Mock private FeatureFlags mFeatureFlags;
- private View mQsFragmentView;
+ @Mock private FeatureFlagsClassic mFeatureFlags;
+ private View mQsView;
- public QSFragmentTest() {
- super(QSFragment.class);
- }
+ private QSImpl mUnderTest;
+
@Before
public void setup() {
- injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
+ mUnderTest = instantiate();
+
+ mUnderTest.onComponentCreated(mQsComponent, null);
}
- @Test
- public void testListening() {
- QSFragment qs = (QSFragment) mFragment;
- mFragments.dispatchResume();
- processAllMessages();
-
- qs.setListening(true);
- processAllMessages();
-
- qs.setListening(false);
- processAllMessages();
- }
@Test
public void testSaveState() {
- mFragments.dispatchResume();
- processAllMessages();
+ mUnderTest.setListening(true);
+ mUnderTest.setExpanded(true);
+ mUnderTest.setQsVisible(true);
- QSFragment qs = (QSFragment) mFragment;
- qs.setListening(true);
- qs.setExpanded(true);
- qs.setQsVisible(true);
- processAllMessages();
- recreateFragment();
- processAllMessages();
+ Bundle bundle = new Bundle();
+ mUnderTest.onSaveInstanceState(bundle);
- // Get the reference to the new fragment.
- qs = (QSFragment) mFragment;
- assertTrue(qs.isListening());
- assertTrue(qs.isExpanded());
- assertTrue(qs.isQsVisible());
+ // Get a new instance
+ QSImpl other = instantiate();
+ other.onComponentCreated(mQsComponent, bundle);
+
+ assertTrue(other.isListening());
+ assertTrue(other.isExpanded());
+ assertTrue(other.isQsVisible());
}
@Test
public void transitionToFullShade_smallScreen_alphaAlways1() {
- QSFragment fragment = resumeAndGetFragment();
setIsSmallScreen();
setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.5f;
float squishinessFraction = 0.5f;
- fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
- assertThat(mQsFragmentView.getAlpha()).isEqualTo(1f);
+ assertThat(mQsView.getAlpha()).isEqualTo(1f);
}
@Test
public void transitionToFullShade_largeScreen_alphaLargeScreenShadeInterpolator() {
- QSFragment fragment = resumeAndGetFragment();
setIsLargeScreen();
setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
boolean isTransitioningToFullShade = true;
@@ -177,43 +162,40 @@
float squishinessFraction = 0.5f;
when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
- fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
- assertThat(mQsFragmentView.getAlpha())
- .isEqualTo(123f);
+ assertThat(mQsView.getAlpha()).isEqualTo(123f);
}
@Test
public void
transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() {
- QSFragment fragment = resumeAndGetFragment();
setStatusBarCurrentAndUpcomingState(KEYGUARD);
when(mQSPanelController.isBouncerInTransit()).thenReturn(false);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.5f;
float squishinessFraction = 0.5f;
- fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
- assertThat(mQsFragmentView.getAlpha()).isEqualTo(transitionProgress);
+ assertThat(mQsView.getAlpha()).isEqualTo(transitionProgress);
}
@Test
public void
transitionToFullShade_onKeyguard_bouncerActive_setsAlphaUsingBouncerInterpolator() {
- QSFragment fragment = resumeAndGetFragment();
setStatusBarCurrentAndUpcomingState(KEYGUARD);
when(mQSPanelController.isBouncerInTransit()).thenReturn(true);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.5f;
float squishinessFraction = 0.5f;
- fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
- assertThat(mQsFragmentView.getAlpha())
+ assertThat(mQsView.getAlpha())
.isEqualTo(
BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(
transitionProgress));
@@ -221,40 +203,37 @@
@Test
public void transitionToFullShade_inFullWidth_alwaysSetsAlphaTo1() {
- QSFragment fragment = resumeAndGetFragment();
- fragment.setIsNotificationPanelFullWidth(true);
+ mUnderTest.setIsNotificationPanelFullWidth(true);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.1f;
float squishinessFraction = 0.5f;
- fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
- assertThat(mQsFragmentView.getAlpha()).isEqualTo(1);
+ assertThat(mQsView.getAlpha()).isEqualTo(1);
transitionProgress = 0.5f;
- fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
- assertThat(mQsFragmentView.getAlpha()).isEqualTo(1);
- assertThat(mQsFragmentView.getAlpha()).isEqualTo(1);
+ assertThat(mQsView.getAlpha()).isEqualTo(1);
+ assertThat(mQsView.getAlpha()).isEqualTo(1);
transitionProgress = 0.7f;
- fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
- assertThat(mQsFragmentView.getAlpha()).isEqualTo(1);
+ assertThat(mQsView.getAlpha()).isEqualTo(1);
}
@Test
public void transitionToFullShade_setsSquishinessOnController() {
- QSFragment fragment = resumeAndGetFragment();
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.123f;
float squishinessFraction = 0.456f;
- fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
- verify(mQsFragmentComponent.getQSSquishinessController())
- .setSquishiness(squishinessFraction);
+ verify(mQsComponent.getQSSquishinessController()).setSquishiness(squishinessFraction);
}
@Test
@@ -265,10 +244,9 @@
float proposedTranslation = 456f;
float squishinessFraction = 0.987f;
- QSFragment fragment = resumeAndGetFragment();
enableSplitShade();
- fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+ mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
squishinessFraction);
verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
@@ -283,10 +261,9 @@
float proposedTranslation = 456f;
float squishinessFraction = 0.987f;
- QSFragment fragment = resumeAndGetFragment();
disableSplitShade();
- fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+ mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
squishinessFraction);
verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
@@ -295,7 +272,6 @@
@Test
public void setQsExpansion_inSplitShade_whenTransitioningToKeyguard_setsAlphaBasedOnShadeTransitionProgress() {
- QSFragment fragment = resumeAndGetFragment();
enableSplitShade();
when(mStatusBarStateController.getState()).thenReturn(SHADE);
when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
@@ -303,24 +279,23 @@
float transitionProgress = 0;
float squishinessFraction = 0f;
- fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
// trigger alpha refresh with non-zero expansion and fraction values
- fragment.setQsExpansion(/* expansion= */ 1, /* panelExpansionFraction= */1,
+ mUnderTest.setQsExpansion(/* expansion= */ 1, /* panelExpansionFraction= */1,
/* proposedTranslation= */ 0, /* squishinessFraction= */ 1);
// alpha should follow lockscreen to shade progress, not panel expansion fraction
- assertThat(mQsFragmentView.getAlpha()).isEqualTo(transitionProgress);
+ assertThat(mQsView.getAlpha()).isEqualTo(transitionProgress);
}
@Test
public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() {
- QSFragment fragment = resumeAndGetFragment();
disableSplitShade();
when(mHeader.getHeight()).thenReturn(1234);
- int height = fragment.getQsMinExpansionHeight();
+ int height = mUnderTest.getQsMinExpansionHeight();
assertThat(height).isEqualTo(mHeader.getHeight());
}
@@ -329,13 +304,12 @@
public void getQsMinExpansionHeight_inSplitShade_returnsAbsoluteBottomOfQSContainer() {
int top = 1234;
int height = 9876;
- QSFragment fragment = resumeAndGetFragment();
enableSplitShade();
- setLocationOnScreen(mQsFragmentView, top);
- when(mQsFragmentView.getHeight()).thenReturn(height);
+ setLocationOnScreen(mQsView, top);
+ when(mQsView.getHeight()).thenReturn(height);
int expectedHeight = top + height;
- assertThat(fragment.getQsMinExpansionHeight()).isEqualTo(expectedHeight);
+ assertThat(mUnderTest.getQsMinExpansionHeight()).isEqualTo(expectedHeight);
}
@Test
@@ -343,47 +317,43 @@
int top = 1234;
int height = 9876;
float translationY = -600f;
- QSFragment fragment = resumeAndGetFragment();
enableSplitShade();
- setLocationOnScreen(mQsFragmentView, (int) (top + translationY));
- when(mQsFragmentView.getHeight()).thenReturn(height);
- when(mQsFragmentView.getTranslationY()).thenReturn(translationY);
+ setLocationOnScreen(mQsView, (int) (top + translationY));
+ when(mQsView.getHeight()).thenReturn(height);
+ when(mQsView.getTranslationY()).thenReturn(translationY);
int expectedHeight = top + height;
- assertThat(fragment.getQsMinExpansionHeight()).isEqualTo(expectedHeight);
+ assertThat(mUnderTest.getQsMinExpansionHeight()).isEqualTo(expectedHeight);
}
@Test
public void hideImmediately_notInSplitShade_movesViewUpByHeaderHeight() {
- QSFragment fragment = resumeAndGetFragment();
disableSplitShade();
when(mHeader.getHeight()).thenReturn(555);
- fragment.hideImmediately();
+ mUnderTest.hideImmediately();
- assertThat(mQsFragmentView.getY()).isEqualTo(-mHeader.getHeight());
+ assertThat(mQsView.getY()).isEqualTo(-mHeader.getHeight());
}
@Test
public void hideImmediately_inSplitShade_movesViewUpByQSAbsoluteBottom() {
- QSFragment fragment = resumeAndGetFragment();
enableSplitShade();
int top = 1234;
int height = 9876;
- setLocationOnScreen(mQsFragmentView, top);
- when(mQsFragmentView.getHeight()).thenReturn(height);
+ setLocationOnScreen(mQsView, top);
+ when(mQsView.getHeight()).thenReturn(height);
- fragment.hideImmediately();
+ mUnderTest.hideImmediately();
int qsAbsoluteBottom = top + height;
- assertThat(mQsFragmentView.getY()).isEqualTo(-qsAbsoluteBottom);
+ assertThat(mQsView.getY()).isEqualTo(-qsAbsoluteBottom);
}
@Test
public void setCollapseExpandAction_passedToControllers() {
Runnable action = () -> {};
- QSFragment fragment = resumeAndGetFragment();
- fragment.setCollapseExpandAction(action);
+ mUnderTest.setCollapseExpandAction(action);
verify(mQSPanelController).setCollapseExpandAction(action);
verify(mQuickQSPanelController).setCollapseExpandAction(action);
@@ -391,24 +361,21 @@
@Test
public void setOverScrollAmount_setsTranslationOnView() {
- QSFragment fragment = resumeAndGetFragment();
+ mUnderTest.setOverScrollAmount(123);
- fragment.setOverScrollAmount(123);
-
- assertThat(mQsFragmentView.getTranslationY()).isEqualTo(123);
+ assertThat(mQsView.getTranslationY()).isEqualTo(123);
}
@Test
public void setOverScrollAmount_beforeViewCreated_translationIsNotSet() {
- QSFragment fragment = getFragment();
+ QSImpl other = instantiate();
+ other.setOverScrollAmount(123);
- fragment.setOverScrollAmount(123);
-
- assertThat(mQsFragmentView.getTranslationY()).isEqualTo(0);
+ assertThat(mQsView.getTranslationY()).isEqualTo(0);
}
private Lifecycle.State getListeningAndVisibilityLifecycleState() {
- return getFragment()
+ return mUnderTest
.getListeningAndVisibilityLifecycleOwner()
.getLifecycle()
.getCurrentState();
@@ -416,11 +383,10 @@
@Test
public void setListeningFalse_notVisible() {
- QSFragment fragment = resumeAndGetFragment();
- fragment.setQsVisible(false);
+ mUnderTest.setQsVisible(false);
clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
- fragment.setListening(false);
+ mUnderTest.setListening(false);
verify(mQSContainerImplController).setListening(false);
assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
verify(mQSPanelController).setListening(eq(false), anyBoolean());
@@ -428,11 +394,10 @@
@Test
public void setListeningTrue_notVisible() {
- QSFragment fragment = resumeAndGetFragment();
- fragment.setQsVisible(false);
+ mUnderTest.setQsVisible(false);
clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
- fragment.setListening(true);
+ mUnderTest.setListening(true);
verify(mQSContainerImplController).setListening(false);
assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.STARTED);
verify(mQSPanelController).setListening(eq(false), anyBoolean());
@@ -440,11 +405,10 @@
@Test
public void setListeningFalse_visible() {
- QSFragment fragment = resumeAndGetFragment();
- fragment.setQsVisible(true);
+ mUnderTest.setQsVisible(true);
clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
- fragment.setListening(false);
+ mUnderTest.setListening(false);
verify(mQSContainerImplController).setListening(false);
assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
verify(mQSPanelController).setListening(eq(false), anyBoolean());
@@ -452,11 +416,10 @@
@Test
public void setListeningTrue_visible() {
- QSFragment fragment = resumeAndGetFragment();
- fragment.setQsVisible(true);
+ mUnderTest.setQsVisible(true);
clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
- fragment.setListening(true);
+ mUnderTest.setListening(true);
verify(mQSContainerImplController).setListening(true);
assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.RESUMED);
verify(mQSPanelController).setListening(eq(true), anyBoolean());
@@ -464,31 +427,28 @@
@Test
public void passCorrectExpansionState_inSplitShade() {
- QSFragment fragment = resumeAndGetFragment();
enableSplitShade();
clearInvocations(mQSPanelController);
- fragment.setExpanded(true);
+ mUnderTest.setExpanded(true);
verify(mQSPanelController).setExpanded(true);
- fragment.setExpanded(false);
+ mUnderTest.setExpanded(false);
verify(mQSPanelController).setExpanded(false);
}
@Test
public void startsListeningAfterStateChangeToExpanded_inSplitShade() {
- QSFragment fragment = resumeAndGetFragment();
enableSplitShade();
- fragment.setQsVisible(true);
+ mUnderTest.setQsVisible(true);
clearInvocations(mQSPanelController);
- fragment.setExpanded(true);
+ mUnderTest.setExpanded(true);
verify(mQSPanelController).setListening(true, true);
}
@Test
public void testUpdateQSBounds_setMediaClipCorrectly() {
- QSFragment fragment = resumeAndGetFragment();
disableSplitShade();
Rect mediaHostClip = new Rect();
@@ -497,7 +457,7 @@
when(mQSPanelScrollView.getMeasuredHeight()).thenReturn(200);
when(mQSMediaHost.getCurrentClipping()).thenReturn(mediaHostClip);
- fragment.updateQsBounds();
+ mUnderTest.updateQsBounds();
assertEquals(25, mediaHostClip.top);
assertEquals(175, mediaHostClip.bottom);
@@ -505,17 +465,15 @@
@Test
public void testQsUpdatesQsAnimatorWithUpcomingState() {
- QSFragment fragment = resumeAndGetFragment();
setStatusBarCurrentAndUpcomingState(SHADE);
- fragment.onUpcomingStateChanged(KEYGUARD);
+ mUnderTest.onUpcomingStateChanged(KEYGUARD);
verify(mQSAnimator).setOnKeyguard(true);
}
- @Override
- protected Fragment instantiate(Context context, String className, Bundle arguments) {
+ private QSImpl instantiate() {
MockitoAnnotations.initMocks(this);
- CommandQueue commandQueue = new CommandQueue(context, new FakeDisplayTracker(context));
+ CommandQueue commandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext));
setupQsComponent();
setUpViews();
@@ -523,9 +481,9 @@
setUpMedia();
setUpOther();
- return new QSFragment(
+ return new QSImpl(
new RemoteInputQuickSettingsDisabler(
- context,
+ mContext,
commandQueue,
new ResourcesSplitShadeStateController(),
mock(ConfigurationController.class)),
@@ -534,8 +492,7 @@
mQSMediaHost,
mQQSMediaHost,
mBypassController,
- mQsComponentFactory,
- mock(QSFragmentDisableFlagsLogger.class),
+ mock(QSDisableFlagsLogger.class),
mock(DumpManager.class),
mock(QSLogger.class),
mock(FooterActionsController.class),
@@ -561,12 +518,13 @@
}
private void setUpViews() {
- mQsFragmentView = spy(new View(mContext));
- when(mQsFragmentView.findViewById(R.id.expanded_qs_scroll_view))
+ mQsView = spy(new View(mContext));
+ when(mQsComponent.getRootView()).thenReturn(mQsView);
+ when(mQsView.findViewById(R.id.expanded_qs_scroll_view))
.thenReturn(mQSPanelScrollView);
- when(mQsFragmentView.findViewById(R.id.header)).thenReturn(mHeader);
- when(mQsFragmentView.findViewById(android.R.id.edit)).thenReturn(new View(mContext));
- when(mQsFragmentView.findViewById(R.id.qs_footer_actions)).thenAnswer(
+ when(mQsView.findViewById(R.id.header)).thenReturn(mHeader);
+ when(mQsView.findViewById(android.R.id.edit)).thenReturn(new View(mContext));
+ when(mQsView.findViewById(R.id.qs_footer_actions)).thenAnswer(
invocation -> new FooterActionsViewBinder().create(mContext));
}
@@ -597,37 +555,26 @@
return realInflater.inflate(layoutRes, root, attachToRoot);
}
- return mQsFragmentView;
+ return mQsView;
}
private void setupQsComponent() {
- when(mQsComponentFactory.create(any(QSFragment.class))).thenReturn(mQsFragmentComponent);
- when(mQsFragmentComponent.getQSPanelController()).thenReturn(mQSPanelController);
- when(mQsFragmentComponent.getQuickQSPanelController()).thenReturn(mQuickQSPanelController);
- when(mQsFragmentComponent.getQSCustomizerController()).thenReturn(mQsCustomizerController);
- when(mQsFragmentComponent.getQSContainerImplController())
+ when(mQsComponent.getQSPanelController()).thenReturn(mQSPanelController);
+ when(mQsComponent.getQuickQSPanelController()).thenReturn(mQuickQSPanelController);
+ when(mQsComponent.getQSCustomizerController()).thenReturn(mQsCustomizerController);
+ when(mQsComponent.getQSContainerImplController())
.thenReturn(mQSContainerImplController);
- when(mQsFragmentComponent.getQSFooter()).thenReturn(mFooter);
- when(mQsFragmentComponent.getQSFooterActionController())
+ when(mQsComponent.getQSFooter()).thenReturn(mFooter);
+ when(mQsComponent.getQSFooterActionController())
.thenReturn(mQSFooterActionController);
- when(mQsFragmentComponent.getQSAnimator()).thenReturn(mQSAnimator);
- when(mQsFragmentComponent.getQSSquishinessController()).thenReturn(mSquishinessController);
- }
-
- private QSFragment getFragment() {
- return ((QSFragment) mFragment);
- }
-
- private QSFragment resumeAndGetFragment() {
- mFragments.dispatchResume();
- processAllMessages();
- return getFragment();
+ when(mQsComponent.getQSAnimator()).thenReturn(mQSAnimator);
+ when(mQsComponent.getQSSquishinessController()).thenReturn(mSquishinessController);
}
private void setStatusBarCurrentAndUpcomingState(int statusBarState) {
when(mStatusBarStateController.getState()).thenReturn(statusBarState);
when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(statusBarState);
- getFragment().onStateChanged(statusBarState);
+ mUnderTest.onStateChanged(statusBarState);
}
private void enableSplitShade() {
@@ -639,7 +586,7 @@
}
private void setSplitShadeEnabled(boolean enabled) {
- getFragment().setInSplitShade(enabled);
+ mUnderTest.setInSplitShade(enabled);
}
private void setLocationOnScreen(View view, int top) {
@@ -652,10 +599,10 @@
}
private void setIsLargeScreen() {
- getFragment().setIsNotificationPanelFullWidth(false);
+ mUnderTest.setIsNotificationPanelFullWidth(false);
}
private void setIsSmallScreen() {
- getFragment().setIsNotificationPanelFullWidth(true);
+ mUnderTest.setIsNotificationPanelFullWidth(true);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
index 9386d71..9a55f72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
@@ -23,15 +23,19 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -39,6 +43,20 @@
@RunWith(AndroidJUnit4::class)
class AutoAddSettingsRepositoryTest : SysuiTestCase() {
private val secureSettings = FakeSettings()
+ private val userAutoAddRepositoryFactory =
+ object : UserAutoAddRepository.Factory {
+ override fun create(userId: Int): UserAutoAddRepository {
+ return UserAutoAddRepository(
+ userId,
+ secureSettings,
+ logger,
+ testScope.backgroundScope,
+ testDispatcher,
+ )
+ }
+ }
+
+ @Mock private lateinit var logger: QSPipelineLogger
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -47,110 +65,37 @@
@Before
fun setUp() {
- underTest =
- AutoAddSettingRepository(
- secureSettings,
- testDispatcher,
- )
+ MockitoAnnotations.initMocks(this)
+
+ underTest = AutoAddSettingRepository(userAutoAddRepositoryFactory)
}
@Test
- fun nonExistentSetting_emptySet() =
- testScope.runTest {
- val specs by collectLastValue(underTest.autoAddedTiles(0))
-
- assertThat(specs).isEmpty()
- }
-
- @Test
- fun settingsChange_correctValues() =
- testScope.runTest {
- val userId = 0
- val specs by collectLastValue(underTest.autoAddedTiles(userId))
-
- val value = "a,custom(b/c)"
- storeForUser(value, userId)
-
- assertThat(specs).isEqualTo(value.toSet())
-
- val newValue = "a"
- storeForUser(newValue, userId)
-
- assertThat(specs).isEqualTo(newValue.toSet())
- }
-
- @Test
fun tilesForCorrectUsers() =
testScope.runTest {
- val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0))
- val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1))
-
val user0Tiles = "a"
val user1Tiles = "custom(b/c)"
storeForUser(user0Tiles, 0)
storeForUser(user1Tiles, 1)
+ val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0))
+ val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1))
+ runCurrent()
- assertThat(tilesFromUser0).isEqualTo(user0Tiles.toSet())
- assertThat(tilesFromUser1).isEqualTo(user1Tiles.toSet())
- }
-
- @Test
- fun noInvalidTileSpecs() =
- testScope.runTest {
- val userId = 0
- val tiles by collectLastValue(underTest.autoAddedTiles(userId))
-
- val specs = "d,custom(bad)"
- storeForUser(specs, userId)
-
- assertThat(tiles).isEqualTo("d".toSet())
- }
-
- @Test
- fun markAdded() =
- testScope.runTest {
- val userId = 0
- val specs = mutableSetOf(TileSpec.create("a"))
- underTest.markTileAdded(userId, TileSpec.create("a"))
-
- assertThat(loadForUser(userId).toSet()).containsExactlyElementsIn(specs)
-
- specs.add(TileSpec.create("b"))
- underTest.markTileAdded(userId, TileSpec.create("b"))
-
- assertThat(loadForUser(userId).toSet()).containsExactlyElementsIn(specs)
+ assertThat(tilesFromUser0).isEqualTo(user0Tiles.toTilesSet())
+ assertThat(tilesFromUser1).isEqualTo(user1Tiles.toTilesSet())
}
@Test
fun markAdded_multipleUsers() =
testScope.runTest {
+ val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0))
+ val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1))
+ runCurrent()
+
underTest.markTileAdded(userId = 1, TileSpec.create("a"))
- assertThat(loadForUser(0).toSet()).isEmpty()
- assertThat(loadForUser(1).toSet())
- .containsExactlyElementsIn(setOf(TileSpec.create("a")))
- }
-
- @Test
- fun markAdded_Invalid_noop() =
- testScope.runTest {
- val userId = 0
- underTest.markTileAdded(userId, TileSpec.Invalid)
-
- assertThat(loadForUser(userId).toSet()).isEmpty()
- }
-
- @Test
- fun unmarkAdded() =
- testScope.runTest {
- val userId = 0
- val specs = "a,custom(b/c)"
- storeForUser(specs, userId)
-
- underTest.unmarkTileAdded(userId, TileSpec.create("a"))
-
- assertThat(loadForUser(userId).toSet())
- .containsExactlyElementsIn(setOf(TileSpec.create("custom(b/c)")))
+ assertThat(tilesFromUser0).isEmpty()
+ assertThat(tilesFromUser1).containsExactlyElementsIn(setOf(TileSpec.create("a")))
}
@Test
@@ -159,33 +104,23 @@
val specs = "a,b"
storeForUser(specs, 0)
storeForUser(specs, 1)
+ val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0))
+ val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1))
+ runCurrent()
underTest.unmarkTileAdded(1, TileSpec.create("a"))
- assertThat(loadForUser(0).toSet()).isEqualTo(specs.toSet())
- assertThat(loadForUser(1).toSet()).isEqualTo(setOf(TileSpec.create("b")))
+ assertThat(tilesFromUser0).isEqualTo(specs.toTilesSet())
+ assertThat(tilesFromUser1).isEqualTo(setOf(TileSpec.create("b")))
}
private fun storeForUser(specs: String, userId: Int) {
secureSettings.putStringForUser(SETTING, specs, userId)
}
- private fun loadForUser(userId: Int): String {
- return secureSettings.getStringForUser(SETTING, userId) ?: ""
- }
-
companion object {
private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
- private const val DELIMITER = ","
- fun Set<TileSpec>.toSeparatedString() = joinToString(DELIMITER, transform = TileSpec::spec)
-
- fun String.toSet(): Set<TileSpec> {
- return if (isNullOrBlank()) {
- emptySet()
- } else {
- split(DELIMITER).map(TileSpec::create).toSet()
- }
- }
+ private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt
new file mode 100644
index 0000000..dc09a33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt
@@ -0,0 +1,220 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.Intent
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@RoboPilotTest
+class QSSettingsRestoredBroadcastRepositoryTest : SysuiTestCase() {
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ @Mock private lateinit var pipelineLogger: QSPipelineLogger
+
+ private lateinit var underTest: QSSettingsRestoredBroadcastRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ QSSettingsRestoredBroadcastRepository(
+ fakeBroadcastDispatcher,
+ pipelineLogger,
+ testScope.backgroundScope,
+ dispatcher,
+ )
+ }
+
+ @Test
+ fun restoreDataAfterBothIntents_tilesRestoredFirst() =
+ testScope.runTest {
+ runCurrent()
+ val restoreData by collectLastValue(underTest.restoreData)
+ val user = 0
+
+ val tilesIntent =
+ createRestoreIntent(
+ RestoreType.TILES,
+ CURRENT_TILES,
+ RESTORED_TILES,
+ )
+
+ val autoAddIntent =
+ createRestoreIntent(
+ RestoreType.AUTOADD,
+ CURRENT_AUTO_ADDED_TILES,
+ RESTORED_AUTO_ADDED_TILES,
+ )
+
+ sendIntentForUser(tilesIntent, user)
+
+ // No restore data yet as we are missing one of the broadcasts
+ assertThat(restoreData).isNull()
+
+ // After the second event, we see the corresponding restore
+ sendIntentForUser(autoAddIntent, user)
+
+ with(restoreData!!) {
+ assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList())
+ assertThat(restoredAutoAddedTiles).isEqualTo(RESTORED_AUTO_ADDED_TILES.toTilesSet())
+ assertThat(userId).isEqualTo(user)
+ }
+ }
+
+ @Test
+ fun restoreDataAfterBothIntents_autoAddRestoredFirst() =
+ testScope.runTest {
+ runCurrent()
+ val restoreData by collectLastValue(underTest.restoreData)
+ val user = 0
+
+ val tilesIntent =
+ createRestoreIntent(
+ RestoreType.TILES,
+ CURRENT_TILES,
+ RESTORED_TILES,
+ )
+
+ val autoAddIntent =
+ createRestoreIntent(
+ RestoreType.AUTOADD,
+ CURRENT_AUTO_ADDED_TILES,
+ RESTORED_AUTO_ADDED_TILES,
+ )
+
+ sendIntentForUser(autoAddIntent, user)
+
+ // No restore data yet as we are missing one of the broadcasts
+ assertThat(restoreData).isNull()
+
+ // After the second event, we see the corresponding restore
+ sendIntentForUser(tilesIntent, user)
+
+ with(restoreData!!) {
+ assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList())
+ assertThat(restoredAutoAddedTiles).isEqualTo(RESTORED_AUTO_ADDED_TILES.toTilesSet())
+ assertThat(userId).isEqualTo(user)
+ }
+ }
+
+ @Test
+ fun interleavedBroadcastsFromDifferentUsers_onlysendDataForCorrectUser() =
+ testScope.runTest {
+ runCurrent()
+ val restoreData by collectLastValue(underTest.restoreData)
+
+ val user0 = 0
+ val user10 = 10
+
+ val currentTiles10 = "z,y,x"
+ val restoredTiles10 = "x"
+ val currentAutoAdded10 = "f"
+ val restoredAutoAdded10 = "f,g"
+
+ val tilesIntent0 =
+ createRestoreIntent(
+ RestoreType.TILES,
+ CURRENT_TILES,
+ RESTORED_TILES,
+ )
+ val autoAddIntent0 =
+ createRestoreIntent(
+ RestoreType.AUTOADD,
+ CURRENT_AUTO_ADDED_TILES,
+ RESTORED_AUTO_ADDED_TILES,
+ )
+ val tilesIntent10 =
+ createRestoreIntent(
+ RestoreType.TILES,
+ currentTiles10,
+ restoredTiles10,
+ )
+ val autoAddIntent10 =
+ createRestoreIntent(
+ RestoreType.AUTOADD,
+ currentAutoAdded10,
+ restoredAutoAdded10,
+ )
+
+ sendIntentForUser(tilesIntent0, user0)
+ sendIntentForUser(autoAddIntent10, user10)
+ assertThat(restoreData).isNull()
+
+ sendIntentForUser(tilesIntent10, user10)
+
+ with(restoreData!!) {
+ assertThat(restoredTiles).isEqualTo(restoredTiles10.toTilesList())
+ assertThat(restoredAutoAddedTiles).isEqualTo(restoredAutoAdded10.toTilesSet())
+ assertThat(userId).isEqualTo(user10)
+ }
+
+ sendIntentForUser(autoAddIntent0, user0)
+
+ with(restoreData!!) {
+ assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList())
+ assertThat(restoredAutoAddedTiles).isEqualTo(RESTORED_AUTO_ADDED_TILES.toTilesSet())
+ assertThat(userId).isEqualTo(user0)
+ }
+ }
+
+ private fun sendIntentForUser(intent: Intent, userId: Int) {
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ intent,
+ FakeBroadcastDispatcher.fakePendingResultForUser(userId)
+ )
+ }
+
+ companion object {
+ private const val CURRENT_TILES = "a,b,c,d"
+ private const val RESTORED_TILES = "b,a,c"
+ private const val CURRENT_AUTO_ADDED_TILES = "d"
+ private const val RESTORED_AUTO_ADDED_TILES = "e"
+
+ private fun createRestoreIntent(
+ type: RestoreType,
+ previousValue: String,
+ restoredValue: String,
+ ): Intent {
+ val setting =
+ when (type) {
+ RestoreType.TILES -> Settings.Secure.QS_TILES
+ RestoreType.AUTOADD -> Settings.Secure.QS_AUTO_ADDED_TILES
+ }
+ return Intent(Intent.ACTION_SETTING_RESTORED)
+ .putExtra(Intent.EXTRA_SETTING_NAME, setting)
+ .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue)
+ .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue)
+ }
+
+ private fun String.toTilesList() = TilesSettingConverter.toTilesList(this)
+
+ private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+
+ private enum class RestoreType {
+ TILES,
+ AUTOADD,
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 1c28e4c..08adebb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -23,14 +23,12 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.qs.QSHost
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.retail.data.repository.FakeRetailModeRepository
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -49,9 +47,28 @@
private lateinit var secureSettings: FakeSettings
private lateinit var retailModeRepository: FakeRetailModeRepository
+ private val defaultTilesRepository =
+ object : DefaultTilesRepository {
+ override val defaultTiles: List<TileSpec>
+ get() = DEFAULT_TILES.toTileSpecs()
+ }
@Mock private lateinit var logger: QSPipelineLogger
+ private val userTileSpecRepositoryFactory =
+ object : UserTileSpecRepository.Factory {
+ override fun create(userId: Int): UserTileSpecRepository {
+ return UserTileSpecRepository(
+ userId,
+ defaultTilesRepository,
+ secureSettings,
+ logger,
+ testScope.backgroundScope,
+ testDispatcher,
+ )
+ }
+ }
+
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -66,293 +83,85 @@
retailModeRepository.setRetailMode(false)
with(context.orCreateTestableResources) {
- addOverride(R.string.quick_settings_tiles_default, DEFAULT_TILES)
addOverride(R.string.quick_settings_tiles_retail_mode, RETAIL_TILES)
}
underTest =
TileSpecSettingsRepository(
- secureSettings,
context.resources,
logger,
retailModeRepository,
- testDispatcher,
+ userTileSpecRepositoryFactory
)
}
@Test
- fun emptySetting_usesDefaultValue() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
- assertThat(tiles).isEqualTo(getDefaultTileSpecs())
- }
-
- @Test
- fun changeInSettings_changesValue() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- storeTilesForUser("a", 0)
- assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
-
- storeTilesForUser("a,custom(b/c)", 0)
- assertThat(tiles)
- .isEqualTo(listOf(TileSpec.create("a"), TileSpec.create("custom(b/c)")))
- }
-
- @Test
fun tilesForCorrectUsers() =
testScope.runTest {
- val tilesFromUser0 by collectLastValue(underTest.tilesSpecs(0))
- val tilesFromUser1 by collectLastValue(underTest.tilesSpecs(1))
-
val user0Tiles = "a"
val user1Tiles = "custom(b/c)"
storeTilesForUser(user0Tiles, 0)
storeTilesForUser(user1Tiles, 1)
+ val tilesFromUser0 by collectLastValue(underTest.tilesSpecs(0))
+ val tilesFromUser1 by collectLastValue(underTest.tilesSpecs(1))
+
assertThat(tilesFromUser0).isEqualTo(user0Tiles.toTileSpecs())
assertThat(tilesFromUser1).isEqualTo(user1Tiles.toTileSpecs())
}
@Test
- fun invalidTilesAreNotPresent() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- val specs = "d,custom(bad)"
- storeTilesForUser(specs, 0)
-
- assertThat(tiles).isEqualTo(specs.toTileSpecs().filter { it != TileSpec.Invalid })
- }
-
- @Test
- fun noValidTiles_defaultSet() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- storeTilesForUser("custom(bad),custom()", 0)
-
- assertThat(tiles).isEqualTo(getDefaultTileSpecs())
- }
-
- @Test
- fun addTileAtEnd() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- storeTilesForUser("a", 0)
-
- underTest.addTile(userId = 0, TileSpec.create("b"))
-
- val expected = "a,b"
- assertThat(loadTilesForUser(0)).isEqualTo(expected)
- assertThat(tiles).isEqualTo(expected.toTileSpecs())
- }
-
- @Test
- fun addTileAtPosition() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- storeTilesForUser("a,custom(b/c)", 0)
-
- underTest.addTile(userId = 0, TileSpec.create("d"), position = 1)
-
- val expected = "a,d,custom(b/c)"
- assertThat(loadTilesForUser(0)).isEqualTo(expected)
- assertThat(tiles).isEqualTo(expected.toTileSpecs())
- }
-
- @Test
- fun addInvalidTile_noop() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- val specs = "a,custom(b/c)"
- storeTilesForUser(specs, 0)
-
- underTest.addTile(userId = 0, TileSpec.Invalid)
-
- assertThat(loadTilesForUser(0)).isEqualTo(specs)
- assertThat(tiles).isEqualTo(specs.toTileSpecs())
- }
-
- @Test
- fun addTileAtPosition_tooLarge_addedAtEnd() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- val specs = "a,custom(b/c)"
- storeTilesForUser(specs, 0)
-
- underTest.addTile(userId = 0, TileSpec.create("d"), position = 100)
-
- val expected = "a,custom(b/c),d"
- assertThat(loadTilesForUser(0)).isEqualTo(expected)
- assertThat(tiles).isEqualTo(expected.toTileSpecs())
- }
-
- @Test
fun addTileForOtherUser_addedInThatUser() =
testScope.runTest {
- val tilesUser0 by collectLastValue(underTest.tilesSpecs(0))
- val tilesUser1 by collectLastValue(underTest.tilesSpecs(1))
-
storeTilesForUser("a", 0)
storeTilesForUser("b", 1)
+ val tilesUser0 by collectLastValue(underTest.tilesSpecs(0))
+ val tilesUser1 by collectLastValue(underTest.tilesSpecs(1))
+ runCurrent()
underTest.addTile(userId = 1, TileSpec.create("c"))
- assertThat(loadTilesForUser(0)).isEqualTo("a")
assertThat(tilesUser0).isEqualTo("a".toTileSpecs())
- assertThat(loadTilesForUser(1)).isEqualTo("b,c")
+ assertThat(loadTilesForUser(0)).isEqualTo("a")
assertThat(tilesUser1).isEqualTo("b,c".toTileSpecs())
- }
-
- @Test
- fun removeTiles() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- storeTilesForUser("a,b", 0)
-
- underTest.removeTiles(userId = 0, listOf(TileSpec.create("a")))
-
- assertThat(loadTilesForUser(0)).isEqualTo("b")
- assertThat(tiles).isEqualTo("b".toTileSpecs())
- }
-
- @Test
- fun removeTilesNotThere_noop() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- val specs = "a,b"
- storeTilesForUser(specs, 0)
-
- underTest.removeTiles(userId = 0, listOf(TileSpec.create("c")))
-
- assertThat(loadTilesForUser(0)).isEqualTo(specs)
- assertThat(tiles).isEqualTo(specs.toTileSpecs())
- }
-
- @Test
- fun removeInvalidTile_noop() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- val specs = "a,b"
- storeTilesForUser(specs, 0)
-
- underTest.removeTiles(userId = 0, listOf(TileSpec.Invalid))
-
- assertThat(loadTilesForUser(0)).isEqualTo(specs)
- assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ assertThat(loadTilesForUser(1)).isEqualTo("b,c")
}
@Test
fun removeTileFromSecondaryUser_removedOnlyInCorrectUser() =
testScope.runTest {
- val user0Tiles by collectLastValue(underTest.tilesSpecs(0))
- val user1Tiles by collectLastValue(underTest.tilesSpecs(1))
-
val specs = "a,b"
storeTilesForUser(specs, 0)
storeTilesForUser(specs, 1)
+ val user0Tiles by collectLastValue(underTest.tilesSpecs(0))
+ val user1Tiles by collectLastValue(underTest.tilesSpecs(1))
+ runCurrent()
underTest.removeTiles(userId = 1, listOf(TileSpec.create("a")))
- assertThat(loadTilesForUser(0)).isEqualTo(specs)
assertThat(user0Tiles).isEqualTo(specs.toTileSpecs())
- assertThat(loadTilesForUser(1)).isEqualTo("b")
+ assertThat(loadTilesForUser(0)).isEqualTo(specs)
assertThat(user1Tiles).isEqualTo("b".toTileSpecs())
- }
-
- @Test
- fun removeMultipleTiles() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- storeTilesForUser("a,b,c,d", 0)
-
- underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"), TileSpec.create("c")))
-
- assertThat(loadTilesForUser(0)).isEqualTo("b,d")
- assertThat(tiles).isEqualTo("b,d".toTileSpecs())
- }
-
- @Test
- fun changeTiles() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- val specs = "a,custom(b/c)"
-
- underTest.setTiles(userId = 0, specs.toTileSpecs())
-
- assertThat(loadTilesForUser(0)).isEqualTo(specs)
- assertThat(tiles).isEqualTo(specs.toTileSpecs())
- }
-
- @Test
- fun changeTiles_ignoresInvalid() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- val specs = "a,custom(b/c)"
-
- underTest.setTiles(userId = 0, listOf(TileSpec.Invalid) + specs.toTileSpecs())
-
- assertThat(loadTilesForUser(0)).isEqualTo(specs)
- assertThat(tiles).isEqualTo(specs.toTileSpecs())
- }
-
- @Test
- fun changeTiles_empty_noChanges() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- underTest.setTiles(userId = 0, emptyList())
-
- assertThat(loadTilesForUser(0)).isNull()
- assertThat(tiles).isEqualTo(getDefaultTileSpecs())
+ assertThat(loadTilesForUser(1)).isEqualTo("b")
}
@Test
fun changeTiles_forCorrectUser() =
testScope.runTest {
- val user0Tiles by collectLastValue(underTest.tilesSpecs(0))
- val user1Tiles by collectLastValue(underTest.tilesSpecs(1))
-
val specs = "a"
storeTilesForUser(specs, 0)
storeTilesForUser(specs, 1)
+ val user0Tiles by collectLastValue(underTest.tilesSpecs(0))
+ val user1Tiles by collectLastValue(underTest.tilesSpecs(1))
+ runCurrent()
underTest.setTiles(userId = 1, "b".toTileSpecs())
- assertThat(loadTilesForUser(0)).isEqualTo("a")
assertThat(user0Tiles).isEqualTo(specs.toTileSpecs())
+ assertThat(loadTilesForUser(0)).isEqualTo("a")
- assertThat(loadTilesForUser(1)).isEqualTo("b")
assertThat(user1Tiles).isEqualTo("b".toTileSpecs())
- }
-
- @Test
- fun multipleConcurrentRemovals_bothRemoved() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tilesSpecs(0))
-
- val specs = "a,b,c"
- storeTilesForUser(specs, 0)
-
- coroutineScope {
- underTest.removeTiles(userId = 0, listOf(TileSpec.create("c")))
- underTest.removeTiles(userId = 0, listOf(TileSpec.create("a")))
- }
-
- assertThat(loadTilesForUser(0)).isEqualTo("b")
- assertThat(tiles).isEqualTo("b".toTileSpecs())
+ assertThat(loadTilesForUser(1)).isEqualTo("b")
}
@Test
@@ -361,6 +170,7 @@
retailModeRepository.setRetailMode(true)
val tiles by collectLastValue(underTest.tilesSpecs(0))
+ runCurrent()
assertThat(tiles).isEqualTo(RETAIL_TILES.toTileSpecs())
}
@@ -369,25 +179,13 @@
fun retailMode_cannotModifyTiles() =
testScope.runTest {
retailModeRepository.setRetailMode(true)
-
- underTest.setTiles(0, DEFAULT_TILES.toTileSpecs())
-
- assertThat(loadTilesForUser(0)).isNull()
- }
-
- @Test
- fun emptyTilesReplacedByDefaultInSettings() =
- testScope.runTest {
val tiles by collectLastValue(underTest.tilesSpecs(0))
runCurrent()
- assertThat(loadTilesForUser(0))
- .isEqualTo(getDefaultTileSpecs().map { it.spec }.joinToString(","))
- }
+ underTest.setTiles(0, listOf(TileSpec.create("a")))
- private fun getDefaultTileSpecs(): List<TileSpec> {
- return QSHost.getDefaultSpecs(context.resources).map(TileSpec::create)
- }
+ assertThat(loadTilesForUser(0)).isEqualTo(DEFAULT_TILES)
+ }
private fun TestScope.storeTilesForUser(specs: String, forUser: Int) {
secureSettings.putStringForUser(SETTING, specs, forUser)
@@ -403,8 +201,6 @@
private const val RETAIL_TILES = "d"
private const val SETTING = Settings.Secure.QS_TILES
- private fun String.toTileSpecs(): List<TileSpec> {
- return split(",").map(TileSpec::create)
- }
+ private fun String.toTileSpecs() = TilesSettingConverter.toTilesList(this)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
new file mode 100644
index 0000000..2087623
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
@@ -0,0 +1,100 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RoboPilotTest
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TilesSettingConverterTest : SysuiTestCase() {
+
+ @Test
+ fun toTilesList_correctContentAndOrdering() {
+ val specString =
+ listOf(
+ "c",
+ "b",
+ "custom(x/y)",
+ "d",
+ )
+ .joinToString(DELIMITER)
+
+ val expected =
+ listOf(
+ TileSpec.create("c"),
+ TileSpec.create("b"),
+ TileSpec.create("custom(x/y)"),
+ TileSpec.create("d"),
+ )
+
+ assertThat(TilesSettingConverter.toTilesList(specString)).isEqualTo(expected)
+ }
+
+ @Test
+ fun toTilesList_removesInvalid() {
+ val specString =
+ listOf(
+ "a",
+ "",
+ "b",
+ )
+ .joinToString(DELIMITER)
+ assertThat(TileSpec.create("")).isEqualTo(TileSpec.Invalid)
+ val expected =
+ listOf(
+ TileSpec.create("a"),
+ TileSpec.create("b"),
+ )
+ assertThat(TilesSettingConverter.toTilesList(specString)).isEqualTo(expected)
+ }
+
+ @Test
+ fun toTilesSet_correctContent() {
+ val specString =
+ listOf(
+ "c",
+ "b",
+ "custom(x/y)",
+ "d",
+ )
+ .joinToString(DELIMITER)
+
+ val expected =
+ setOf(
+ TileSpec.create("c"),
+ TileSpec.create("b"),
+ TileSpec.create("custom(x/y)"),
+ TileSpec.create("d"),
+ )
+
+ assertThat(TilesSettingConverter.toTilesSet(specString)).isEqualTo(expected)
+ }
+
+ @Test
+ fun toTilesSet_removesInvalid() {
+ val specString =
+ listOf(
+ "a",
+ "",
+ "b",
+ )
+ .joinToString(DELIMITER)
+ assertThat(TileSpec.create("")).isEqualTo(TileSpec.Invalid)
+ val expected =
+ setOf(
+ TileSpec.create("a"),
+ TileSpec.create("b"),
+ )
+ assertThat(TilesSettingConverter.toTilesSet(specString)).isEqualTo(expected)
+ }
+
+ companion object {
+ private const val DELIMITER = ","
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
new file mode 100644
index 0000000..81fd72b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
@@ -0,0 +1,160 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RoboPilotTest
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserAutoAddRepositoryTest : SysuiTestCase() {
+ private val secureSettings = FakeSettings()
+
+ @Mock private lateinit var logger: QSPipelineLogger
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private lateinit var underTest: UserAutoAddRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ UserAutoAddRepository(
+ USER,
+ secureSettings,
+ logger,
+ testScope.backgroundScope,
+ testDispatcher,
+ )
+ }
+
+ @Test
+ fun nonExistentSetting_emptySet() =
+ testScope.runTest {
+ val specs by collectLastValue(underTest.autoAdded())
+
+ assertThat(specs).isEmpty()
+ }
+
+ @Test
+ fun settingsChange_noChanges() =
+ testScope.runTest {
+ val value = "a,custom(b/c)"
+ store(value)
+ val specs by collectLastValue(underTest.autoAdded())
+ runCurrent()
+
+ assertThat(specs).isEqualTo(value.toTilesSet())
+
+ val newValue = "a"
+ store(newValue)
+
+ assertThat(specs).isEqualTo(value.toTilesSet())
+ }
+
+ @Test
+ fun noInvalidTileSpecs() =
+ testScope.runTest {
+ val specs = "d,custom(bad)"
+ store(specs)
+ val tiles by collectLastValue(underTest.autoAdded())
+ runCurrent()
+
+ assertThat(tiles).isEqualTo("d".toTilesSet())
+ }
+
+ @Test
+ fun markAdded() =
+ testScope.runTest {
+ val specs = mutableSetOf(TileSpec.create("a"))
+ val autoAdded by collectLastValue(underTest.autoAdded())
+ runCurrent()
+
+ underTest.markTileAdded(TileSpec.create("a"))
+
+ assertThat(autoAdded).containsExactlyElementsIn(specs)
+
+ specs.add(TileSpec.create("b"))
+ underTest.markTileAdded(TileSpec.create("b"))
+
+ assertThat(autoAdded).containsExactlyElementsIn(specs)
+ }
+
+ @Test
+ fun markAdded_Invalid_noop() =
+ testScope.runTest {
+ val autoAdded by collectLastValue(underTest.autoAdded())
+ runCurrent()
+
+ underTest.markTileAdded(TileSpec.Invalid)
+
+ Truth.assertThat(autoAdded).isEmpty()
+ }
+
+ @Test
+ fun unmarkAdded() =
+ testScope.runTest {
+ val specs = "a,custom(b/c)"
+ store(specs)
+ val autoAdded by collectLastValue(underTest.autoAdded())
+ runCurrent()
+
+ underTest.unmarkTileAdded(TileSpec.create("a"))
+
+ assertThat(autoAdded).containsExactlyElementsIn(setOf(TileSpec.create("custom(b/c)")))
+ }
+
+ @Test
+ fun restore_addsRestoredTiles() =
+ testScope.runTest {
+ val specs = "a,b"
+ val restored = "b,c"
+ store(specs)
+ val autoAdded by collectLastValue(underTest.autoAdded())
+ runCurrent()
+
+ val restoreData =
+ RestoreData(
+ emptyList(),
+ restored.toTilesSet(),
+ USER,
+ )
+ underTest.reconcileRestore(restoreData)
+
+ assertThat(autoAdded).containsExactlyElementsIn("a,b,c".toTilesSet())
+ }
+
+ private fun store(specs: String) {
+ secureSettings.putStringForUser(SETTING, specs, USER)
+ }
+
+ companion object {
+ private const val USER = 10
+ private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
+
+ private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
new file mode 100644
index 0000000..389580c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
@@ -0,0 +1,351 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RoboPilotTest
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class UserTileSpecRepositoryTest : SysuiTestCase() {
+ private val secureSettings = FakeSettings()
+ private val defaultTilesRepository =
+ object : DefaultTilesRepository {
+ override val defaultTiles: List<TileSpec>
+ get() = DEFAULT_TILES.toTileSpecs()
+ }
+
+ @Mock private lateinit var logger: QSPipelineLogger
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private lateinit var underTest: UserTileSpecRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ UserTileSpecRepository(
+ USER,
+ defaultTilesRepository,
+ secureSettings,
+ logger,
+ testScope.backgroundScope,
+ testDispatcher,
+ )
+ }
+
+ @Test
+ fun emptySetting_usesDefaultValue() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tiles())
+ assertThat(tiles).isEqualTo(getDefaultTileSpecs())
+ }
+
+ @Test
+ fun changeInSettings_valueDoesntChange() =
+ testScope.runTest {
+ storeTiles("a")
+ val tiles by collectLastValue(underTest.tiles())
+
+ assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
+
+ storeTiles("a,custom(b/c)")
+ assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
+ }
+
+ @Test
+ fun changeInSettings_settingIsRestored() =
+ testScope.runTest {
+ storeTiles("a")
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ storeTiles("a,custom(b/c)")
+ assertThat(loadTiles()).isEqualTo("a")
+ }
+
+ @Test
+ fun invalidTilesAreNotPresent() =
+ testScope.runTest {
+ val specs = "d,custom(bad)"
+ storeTiles(specs)
+
+ val tiles by collectLastValue(underTest.tiles())
+
+ assertThat(tiles).isEqualTo(specs.toTileSpecs().filter { it != TileSpec.Invalid })
+ }
+
+ @Test
+ fun noValidTiles_defaultSet() =
+ testScope.runTest {
+ storeTiles("custom(bad),custom()")
+
+ val tiles by collectLastValue(underTest.tiles())
+
+ assertThat(tiles).isEqualTo(getDefaultTileSpecs())
+ }
+
+ /*
+ * Following tests are for the possible actions that can be performed to the list of tiles.
+ * In general, the tests follow this scheme:
+ *
+ * 1. Set starting tiles in Settings
+ * 2. Start collection of flows
+ * 3. Call `runCurrent` so all collectors are started (side effects)
+ * 4. Perform operation
+ * 5. Check that the flow contains the right value
+ * 6. Check that settings contains the right value.
+ */
+
+ @Test
+ fun addTileAtEnd() =
+ testScope.runTest {
+ storeTiles("a")
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ underTest.addTile(TileSpec.create("b"))
+
+ val expected = "a,b"
+ assertThat(tiles).isEqualTo(expected.toTileSpecs())
+ assertThat(loadTiles()).isEqualTo(expected)
+ }
+
+ @Test
+ fun addTileAtPosition() =
+ testScope.runTest {
+ storeTiles("a,custom(b/c)")
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ underTest.addTile(TileSpec.create("d"), position = 1)
+
+ val expected = "a,d,custom(b/c)"
+ assertThat(tiles).isEqualTo(expected.toTileSpecs())
+ assertThat(loadTiles()).isEqualTo(expected)
+ }
+
+ @Test
+ fun addInvalidTile_noop() =
+ testScope.runTest {
+ val specs = "a,custom(b/c)"
+ storeTiles(specs)
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ underTest.addTile(TileSpec.Invalid)
+
+ assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ assertThat(loadTiles()).isEqualTo(specs)
+ }
+
+ @Test
+ fun addTileAtPosition_tooLarge_addedAtEnd() =
+ testScope.runTest {
+ val specs = "a,custom(b/c)"
+ storeTiles(specs)
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ underTest.addTile(TileSpec.create("d"), position = 100)
+
+ val expected = "a,custom(b/c),d"
+ assertThat(tiles).isEqualTo(expected.toTileSpecs())
+ assertThat(loadTiles()).isEqualTo(expected)
+ }
+
+ @Test
+ fun removeTiles() =
+ testScope.runTest {
+ storeTiles("a,b")
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ underTest.removeTiles(listOf(TileSpec.create("a")))
+
+ assertThat(tiles).isEqualTo("b".toTileSpecs())
+ assertThat(loadTiles()).isEqualTo("b")
+ }
+
+ @Test
+ fun removeTilesNotThere_noop() =
+ testScope.runTest {
+ val specs = "a,b"
+ storeTiles(specs)
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ underTest.removeTiles(listOf(TileSpec.create("c")))
+
+ assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ assertThat(loadTiles()).isEqualTo(specs)
+ }
+
+ @Test
+ fun removeInvalidTile_noop() =
+ testScope.runTest {
+ val specs = "a,b"
+ storeTiles(specs)
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ underTest.removeTiles(listOf(TileSpec.Invalid))
+
+ assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ assertThat(loadTiles()).isEqualTo(specs)
+ }
+
+ @Test
+ fun removeMultipleTiles() =
+ testScope.runTest {
+ storeTiles("a,b,c,d")
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ underTest.removeTiles(listOf(TileSpec.create("a"), TileSpec.create("c")))
+
+ assertThat(tiles).isEqualTo("b,d".toTileSpecs())
+ assertThat(loadTiles()).isEqualTo("b,d")
+ }
+
+ @Test
+ fun changeTiles() =
+ testScope.runTest {
+ val specs = "a,custom(b/c)"
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ underTest.setTiles(specs.toTileSpecs())
+
+ assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ assertThat(loadTiles()).isEqualTo(specs)
+ }
+
+ @Test
+ fun changeTiles_ignoresInvalid() =
+ testScope.runTest {
+ val specs = "a,custom(b/c)"
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ underTest.setTiles(listOf(TileSpec.Invalid) + specs.toTileSpecs())
+
+ assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ assertThat(loadTiles()).isEqualTo(specs)
+ }
+
+ @Test
+ fun changeTiles_empty_noChanges() =
+ testScope.runTest {
+ val specs = "a,b,c,d"
+ storeTiles(specs)
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ underTest.setTiles(emptyList())
+
+ assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ assertThat(loadTiles()).isEqualTo(specs)
+ }
+
+ @Test
+ fun multipleConcurrentRemovals_bothRemoved() =
+ testScope.runTest {
+ val specs = "a,b,c"
+ storeTiles(specs)
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ coroutineScope {
+ underTest.removeTiles(listOf(TileSpec.create("c")))
+ underTest.removeTiles(listOf(TileSpec.create("a")))
+ }
+
+ assertThat(tiles).isEqualTo("b".toTileSpecs())
+ assertThat(loadTiles()).isEqualTo("b")
+ }
+
+ @Test
+ fun emptyTilesReplacedByDefaultInSettings() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ assertThat(loadTiles())
+ .isEqualTo(getDefaultTileSpecs().map { it.spec }.joinToString(","))
+ }
+
+ @Test
+ fun restoreDataIsProperlyReconciled() =
+ testScope.runTest {
+ // Tile b was just auto-added, so we should re-add it in position 1
+ // Tile e was auto-added before, but the user had removed it (not in the restored set).
+ // It should not be re-added
+ val specsBeforeRestore = "a,b,c,d,e"
+ val restoredSpecs = "a,c,d,f"
+ val autoAddedBeforeRestore = "b,d"
+ val restoredAutoAdded = "d,e"
+
+ storeTiles(specsBeforeRestore)
+ val tiles by collectLastValue(underTest.tiles())
+ runCurrent()
+
+ val restoreData =
+ RestoreData(
+ restoredSpecs.toTileSpecs(),
+ restoredAutoAdded.toTilesSet(),
+ USER,
+ )
+ underTest.reconcileRestore(restoreData, autoAddedBeforeRestore.toTilesSet())
+ runCurrent()
+
+ val expected = "a,b,c,d,f"
+ assertThat(tiles).isEqualTo(expected.toTileSpecs())
+ assertThat(loadTiles()).isEqualTo(expected)
+ }
+
+ private fun getDefaultTileSpecs(): List<TileSpec> {
+ return defaultTilesRepository.defaultTiles
+ }
+
+ private fun TestScope.storeTiles(specs: String) {
+ secureSettings.putStringForUser(SETTING, specs, USER)
+ runCurrent()
+ }
+
+ private fun loadTiles(): String? {
+ return secureSettings.getStringForUser(SETTING, USER)
+ }
+
+ companion object {
+ private const val USER = 10
+ private const val DEFAULT_TILES = "a,b,c"
+ private const val SETTING = Settings.Secure.QS_TILES
+
+ private fun String.toTileSpecs() = TilesSettingConverter.toTilesList(this)
+ private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
new file mode 100644
index 0000000..5630b9d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
@@ -0,0 +1,94 @@
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeQSSettingsRestoredRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
+import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@RoboPilotTest
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class RestoreReconciliationInteractorTest : SysuiTestCase() {
+
+ private val tileSpecRepository = FakeTileSpecRepository()
+ private val autoAddRepository = FakeAutoAddRepository()
+
+ private val qsSettingsRestoredRepository = FakeQSSettingsRestoredRepository()
+
+ private lateinit var underTest: RestoreReconciliationInteractor
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ RestoreReconciliationInteractor(
+ tileSpecRepository,
+ autoAddRepository,
+ qsSettingsRestoredRepository,
+ testScope.backgroundScope,
+ testDispatcher
+ )
+ underTest.start()
+ }
+
+ @Test
+ fun reconciliationInCorrectOrder_hascurrentAutoAdded() =
+ testScope.runTest {
+ val user = 10
+ val tiles by collectLastValue(tileSpecRepository.tilesSpecs(user))
+ val autoAdd by collectLastValue(autoAddRepository.autoAddedTiles(user))
+
+ // Tile b was just auto-added, so we should re-add it in position 1
+ // Tile e was auto-added before, but the user had removed it (not in the restored set).
+ // It should not be re-added
+ val specsBeforeRestore = "a,b,c,d,e"
+ val restoredSpecs = "a,c,d,f"
+ val autoAddedBeforeRestore = "b,d"
+ val restoredAutoAdded = "d,e"
+
+ val restoreData =
+ RestoreData(
+ restoredSpecs.toTilesList(),
+ restoredAutoAdded.toTilesSet(),
+ user,
+ )
+
+ autoAddedBeforeRestore.toTilesSet().forEach {
+ autoAddRepository.markTileAdded(user, it)
+ }
+ tileSpecRepository.setTiles(user, specsBeforeRestore.toTilesList())
+
+ qsSettingsRestoredRepository.onDataRestored(restoreData)
+ runCurrent()
+
+ val expectedTiles = "a,b,c,d,f"
+ assertThat(tiles).isEqualTo(expectedTiles.toTilesList())
+
+ val expectedAutoAdd = "b,d,e"
+ assertThat(autoAdd).isEqualTo(expectedAutoAdd.toTilesSet())
+ }
+
+ companion object {
+ private fun String.toTilesList() = TilesSettingConverter.toTilesList(this)
+ private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
new file mode 100644
index 0000000..091531e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 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.screenshot
+
+import android.media.MediaPlayer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import java.lang.IllegalStateException
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+class ScreenshotSoundControllerTest : SysuiTestCase() {
+
+ private val soundProvider = mock<ScreenshotSoundProvider>()
+ private val mediaPlayer = mock<MediaPlayer>()
+ private val bgDispatcher = UnconfinedTestDispatcher()
+ private val scope = TestScope(bgDispatcher)
+ @Before
+ fun setup() {
+ whenever(soundProvider.getScreenshotSound()).thenReturn(mediaPlayer)
+ }
+
+ @Test
+ fun init_soundLoading() {
+ createController()
+ bgDispatcher.scheduler.runCurrent()
+
+ verify(soundProvider).getScreenshotSound()
+ }
+
+ @Test
+ fun init_soundLoadingException_playAndReleaseDoNotThrow() = runTest {
+ whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
+
+ val controller = createController()
+
+ controller.playCameraSound().await()
+ controller.releaseScreenshotSound().await()
+
+ verify(mediaPlayer, never()).start()
+ verify(mediaPlayer, never()).release()
+ }
+
+ @Test
+ fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() = runTest {
+ val controller = createController()
+
+ controller.playCameraSound().await()
+
+ verify(mediaPlayer).start()
+ }
+
+ @Test
+ fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() = runTest {
+ val controller = createController()
+
+ controller.releaseScreenshotSound().await()
+
+ verify(mediaPlayer).release()
+ }
+
+ private fun createController() =
+ ScreenshotSoundControllerImpl(soundProvider, scope, bgDispatcher)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 31c8a3d7..ed731dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -118,7 +118,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qs.QSFragment;
+import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeRepository;
@@ -291,7 +291,7 @@
@Mock protected UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@Mock protected ShadeTransitionController mShadeTransitionController;
@Mock protected QS mQs;
- @Mock protected QSFragment mQSFragment;
+ @Mock protected QSFragmentLegacy mQSFragment;
@Mock protected ViewGroup mQsHeader;
@Mock protected ViewParent mViewParent;
@Mock protected ViewTreeObserver mViewTreeObserver;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt
index f7d2497..0c3af03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt
@@ -22,9 +22,9 @@
import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.qs.QSFragment
+import com.android.systemui.qs.QSFragmentLegacy
+import com.android.systemui.res.R
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -40,7 +40,7 @@
@Mock private lateinit var qsFrame: View
@Mock private lateinit var stackScroller: View
@Mock private lateinit var keyguardStatusBar: View
- @Mock private lateinit var qsFragment: QSFragment
+ @Mock private lateinit var qsFragment: QSFragmentLegacy
private lateinit var qsView: ViewGroup
private lateinit var qsContainer: View
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index fb0d4db..8138b32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -34,7 +34,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.dump.DumpManager;
@@ -46,7 +45,8 @@
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qs.QSFragment;
+import com.android.systemui.qs.QSFragmentLegacy;
+import com.android.systemui.res.R;
import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
import com.android.systemui.screenrecord.RecordingController;
@@ -75,18 +75,17 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.user.domain.interactor.UserInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
-import dagger.Lazy;
-
import org.junit.After;
import org.junit.Before;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import dagger.Lazy;
import kotlinx.coroutines.test.TestScope;
public class QuickSettingsControllerBaseTest extends SysuiTestCase {
@@ -109,7 +108,7 @@
@Mock protected KeyguardBottomAreaView mQsFrame;
@Mock protected KeyguardStatusBarView mKeyguardStatusBar;
@Mock protected QS mQs;
- @Mock protected QSFragment mQSFragment;
+ @Mock protected QSFragmentLegacy mQSFragment;
@Mock protected Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
@Mock protected NotificationPanelViewController mNotificationPanelViewController;
@Mock protected NotificationPanelView mPanelView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 99e4030..b54fbd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -90,6 +90,8 @@
private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
override val defaultMobileIconGroup = _defaultMobileIconGroup
+ override val isAnySimSecure = MutableStateFlow(false)
+
fun setSubscriptions(subs: List<SubscriptionModel>) {
_subscriptions.value = subs
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index d005972..4d4f33b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -135,6 +135,7 @@
FakeAirplaneModeRepository(),
wifiRepository,
mock(),
+ mock(),
)
demoRepo =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 6f9764a..9148c75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -37,6 +37,8 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.telephony.PhoneConstants
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.R
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
@@ -104,6 +106,7 @@
@Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var summaryLogger: TableLogBuffer
@Mock private lateinit var logBufferFactory: TableLogBufferFactory
+ @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
private val mobileMappings = FakeMobileMappingsProxy()
private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
@@ -214,6 +217,7 @@
airplaneModeRepository,
wifiRepository,
fullConnectionFactory,
+ updateMonitor,
)
testScope.runCurrent()
@@ -1048,6 +1052,7 @@
airplaneModeRepository,
wifiRepository,
fullConnectionFactory,
+ updateMonitor
)
val latest by collectLastValue(underTest.defaultDataSubRatConfig)
@@ -1103,7 +1108,6 @@
@Test
fun carrierConfig_initialValueIsFetched() =
testScope.runTest {
-
// Value starts out false
assertThat(underTest.defaultDataSubRatConfig.value.showAtLeast3G).isFalse()
@@ -1151,6 +1155,26 @@
assertThat(latest).isEqualTo(null)
}
+ @Test
+ fun anySimSecure_propagatesStateFromKeyguardUpdateMonitor() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isAnySimSecure)
+ assertThat(latest).isFalse()
+
+ val updateMonitorCallback = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
+
+ whenever(updateMonitor.isSimPinSecure).thenReturn(true)
+ updateMonitorCallback.value.onSimStateChanged(0, 0, 0)
+
+ assertThat(latest).isTrue()
+
+ whenever(updateMonitor.isSimPinSecure).thenReturn(false)
+ updateMonitorCallback.value.onSimStateChanged(0, 0, 0)
+
+ assertThat(latest).isFalse()
+ }
+
private fun TestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
runCurrent()
val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index 21a5eb7..28557d3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -17,6 +17,7 @@
package com.android.systemui.broadcast
import android.content.BroadcastReceiver
+import android.content.BroadcastReceiver.PendingResult
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
@@ -28,7 +29,6 @@
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserTracker
-import java.lang.IllegalStateException
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor
@@ -96,8 +96,14 @@
/**
* Sends the given [intent] to *only* the receivers that were registered with an [IntentFilter]
* that matches the intent.
+ *
+ * A non-null [pendingResult] can be used to pass the sending user.
*/
- fun sendIntentToMatchingReceiversOnly(context: Context, intent: Intent) {
+ fun sendIntentToMatchingReceiversOnly(
+ context: Context,
+ intent: Intent,
+ pendingResult: PendingResult? = null
+ ) {
receivers.forEach {
if (
it.filter.match(
@@ -107,6 +113,9 @@
/* logTag= */ "FakeBroadcastDispatcher",
) > 0
) {
+ if (pendingResult != null) {
+ it.receiver.pendingResult = pendingResult
+ }
it.receiver.onReceive(context, intent)
}
}
@@ -130,4 +139,19 @@
val receiver: BroadcastReceiver,
val filter: IntentFilter,
)
+
+ companion object {
+ fun fakePendingResultForUser(userId: Int) =
+ PendingResult(
+ /* resultCode = */ 0,
+ /* resultData = */ "",
+ /* resultExtras = */ null,
+ /* type = */ PendingResult.TYPE_REGISTERED,
+ /* ordered = */ false,
+ /* sticky = */ false,
+ /* token = */ null,
+ userId,
+ /* flags = */ 0,
+ )
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
index 9ea079f..57ad2828 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
@@ -16,15 +16,16 @@
package com.android.systemui.qs.pipeline.data.repository
+import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.shared.TileSpec
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
class FakeAutoAddRepository : AutoAddRepository {
private val autoAddedTilesPerUser = mutableMapOf<Int, MutableStateFlow<Set<TileSpec>>>()
- override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
+ override suspend fun autoAddedTiles(userId: Int): StateFlow<Set<TileSpec>> {
return getFlow(userId)
}
@@ -39,4 +40,8 @@
private fun getFlow(userId: Int): MutableStateFlow<Set<TileSpec>> =
autoAddedTilesPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
+
+ override suspend fun reconcileRestore(restoreData: RestoreData) {
+ with(getFlow(restoreData.userId)) { value = value + restoreData.restoredAutoAddedTiles }
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeQSSettingsRestoredRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeQSSettingsRestoredRepository.kt
new file mode 100644
index 0000000..e0c2154
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeQSSettingsRestoredRepository.kt
@@ -0,0 +1,16 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+
+class FakeQSSettingsRestoredRepository : QSSettingsRestoredRepository {
+ private val _restoreData = MutableSharedFlow<RestoreData>()
+
+ override val restoreData: Flow<RestoreData>
+ get() = _restoreData
+
+ suspend fun onDataRestored(restoreData: RestoreData) {
+ _restoreData.emit(restoreData)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
index aa8dbe1..ae4cf3a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.data.repository
+import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.shared.TileSpec
import kotlinx.coroutines.flow.Flow
@@ -26,7 +27,7 @@
private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>()
- override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+ override suspend fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
return getFlow(userId).asStateFlow()
}
@@ -57,4 +58,13 @@
private fun getFlow(userId: Int): MutableStateFlow<List<TileSpec>> =
tilesPerUser.getOrPut(userId) { MutableStateFlow(emptyList()) }
+
+ override suspend fun reconcileRestore(
+ restoreData: RestoreData,
+ currentAutoAdded: Set<TileSpec>
+ ) {
+ with(getFlow(restoreData.userId)) {
+ value = UserTileSpecRepository.reconcileTiles(value, currentAutoAdded, restoreData)
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index a3ccb16..b56b47f 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -113,6 +113,8 @@
private final boolean mCrossTaskNavigationAllowedByDefault;
@NonNull
private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
+ @Nullable
+ private final ComponentName mPermissionDialogComponent;
private final Object mGenericWindowPolicyControllerLock = new Object();
@Nullable private final ActivityBlockedCallback mActivityBlockedCallback;
private int mDisplayId = Display.INVALID_DISPLAY;
@@ -171,6 +173,7 @@
@NonNull Set<ComponentName> activityPolicyExemptions,
boolean crossTaskNavigationAllowedByDefault,
@NonNull Set<ComponentName> crossTaskNavigationExemptions,
+ @Nullable ComponentName permissionDialogComponent,
@Nullable ActivityListener activityListener,
@Nullable PipBlockedCallback pipBlockedCallback,
@Nullable ActivityBlockedCallback activityBlockedCallback,
@@ -185,6 +188,7 @@
mActivityPolicyExemptions = activityPolicyExemptions;
mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault;
mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions);
+ mPermissionDialogComponent = permissionDialogComponent;
mActivityBlockedCallback = activityBlockedCallback;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
@@ -309,6 +313,13 @@
return false;
}
+ // mPermissionDialogComponent being null means we don't want to block permission Dialogs
+ // based on FLAG_STREAM_PERMISSIONS
+ if (mPermissionDialogComponent != null
+ && mPermissionDialogComponent.equals(activityComponent)) {
+ return false;
+ }
+
return true;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 203a152..a2e4d2c 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -24,6 +24,7 @@
import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
+import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -204,6 +205,7 @@
@GuardedBy("mVirtualDeviceLock")
@NonNull
private final Set<ComponentName> mActivityPolicyExemptions;
+ private final ComponentName mPermissionDialogComponent;
private ActivityListener createListenerAdapter() {
return new ActivityListener() {
@@ -317,6 +319,11 @@
mParams.getVirtualSensorCallback(), mParams.getVirtualSensorConfigs());
mCameraAccessController = cameraAccessController;
mCameraAccessController.startObservingIfNeeded();
+ if (!Flags.streamPermissions()) {
+ mPermissionDialogComponent = getPermissionDialogComponent();
+ } else {
+ mPermissionDialogComponent = null;
+ }
try {
token.linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -324,8 +331,14 @@
}
mVirtualDeviceLog.logCreated(deviceId, mOwnerUid);
- mPublicVirtualDeviceObject = new VirtualDevice(
- this, getDeviceId(), getPersistentDeviceId(), mParams.getName());
+ if (Flags.vdmPublicApis()) {
+ mPublicVirtualDeviceObject = new VirtualDevice(
+ this, getDeviceId(), getPersistentDeviceId(), mParams.getName(),
+ getDisplayName());
+ } else {
+ mPublicVirtualDeviceObject = new VirtualDevice(
+ this, getDeviceId(), getPersistentDeviceId(), mParams.getName());
+ }
if (Flags.dynamicPolicy()) {
mActivityPolicyExemptions = new ArraySet<>(
@@ -951,6 +964,7 @@
/*crossTaskNavigationExemptions=*/crossTaskNavigationAllowedByDefault
? mParams.getBlockedCrossTaskNavigations()
: mParams.getAllowedCrossTaskNavigations(),
+ mPermissionDialogComponent,
createListenerAdapter(),
this::onEnteringPipBlocked,
this::onActivityBlocked,
@@ -963,6 +977,13 @@
return gwpc;
}
+ private ComponentName getPermissionDialogComponent() {
+ Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
+ PackageManager packageManager = mContext.getPackageManager();
+ intent.setPackage(packageManager.getPermissionControllerPackageName());
+ return intent.resolveActivity(packageManager);
+ }
+
int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
@NonNull IVirtualDisplayCallback callback, String packageName) {
GenericWindowPolicyController gwpc;
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 25ca509c..8cc2665 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -265,17 +265,17 @@
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
- mStorageManagerService.onUnlockUser(user.getUserIdentifier());
+ mStorageManagerService.onUserUnlocking(user.getUserIdentifier());
}
@Override
public void onUserStopped(@NonNull TargetUser user) {
- mStorageManagerService.onCleanupUser(user.getUserIdentifier());
+ mStorageManagerService.onUserStopped(user.getUserIdentifier());
}
@Override
public void onUserStopping(@NonNull TargetUser user) {
- mStorageManagerService.onStopUser(user.getUserIdentifier());
+ mStorageManagerService.onUserStopping(user.getUserIdentifier());
}
@Override
@@ -1163,8 +1163,8 @@
}
}
- private void onUnlockUser(int userId) {
- Slog.d(TAG, "onUnlockUser " + userId);
+ private void onUserUnlocking(int userId) {
+ Slog.d(TAG, "onUserUnlocking " + userId);
if (userId != UserHandle.USER_SYSTEM) {
// Check if this user shares media with another user
@@ -1227,8 +1227,8 @@
}
}
- private void onCleanupUser(int userId) {
- Slog.d(TAG, "onCleanupUser " + userId);
+ private void onUserStopped(int userId) {
+ Slog.d(TAG, "onUserStopped " + userId);
try {
mVold.onUserStopped(userId);
@@ -1242,8 +1242,8 @@
}
}
- private void onStopUser(int userId) {
- Slog.i(TAG, "onStopUser " + userId);
+ private void onUserStopping(int userId) {
+ Slog.i(TAG, "onUserStopping " + userId);
try {
mStorageSessionController.onUserStopping(userId);
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 128bbdf..907069d 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -82,6 +82,7 @@
import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
import com.android.server.pm.UserManagerInternal;
+import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.util.Arrays;
@@ -571,8 +572,18 @@
builder = new BatteryUsageStatsQuery.Builder()
.includeProcessStateData()
.aggregateSnapshots(lastUidBatteryUsageStartTs, curStart);
- updateBatteryUsageStatsOnceInternal(0, buf, builder, userIds, batteryStatsInternal);
+ final BatteryUsageStats statsCommit =
+ updateBatteryUsageStatsOnceInternal(0,
+ buf,
+ builder,
+ userIds,
+ batteryStatsInternal);
curDuration += curStart - lastUidBatteryUsageStartTs;
+ try {
+ statsCommit.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to close a stat");
+ }
}
if (needUpdateUidBatteryUsageInWindow && curDuration >= windowSize) {
// If we do have long enough data for the window, save it.
@@ -648,8 +659,14 @@
}
}
}
+ try {
+ stats.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to close a stat");
+ }
}
+ // The BatteryUsageStats object MUST BE CLOSED when finished using
private BatteryUsageStats updateBatteryUsageStatsOnceInternal(long expectedDuration,
SparseArray<BatteryUsage> buf, BatteryUsageStatsQuery.Builder builder,
ArraySet<UserHandle> userIds, BatteryStatsInternal batteryStatsInternal) {
@@ -662,7 +679,16 @@
// Shouldn't happen unless in test.
return null;
}
+ // We need the first stat in the list, so we should
+ // close out the others.
final BatteryUsageStats stats = statsList.get(0);
+ for (int i = 1; i < statsList.size(); i++) {
+ try {
+ statsList.get(i).close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to close a stat in BatteryUsageStats List");
+ }
+ }
final List<UidBatteryConsumer> uidConsumers = stats.getUidBatteryConsumers();
if (uidConsumers != null) {
final long start = stats.getStatsStartTimestamp();
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index e84fed7..4b622f5 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -173,6 +173,16 @@
TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
+
+ // Register all text aconfig flags.
+ for (String flag : TextFlags.TEXT_ACONFIGS_FLAGS) {
+ sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
+ TextFlags.NAMESPACE,
+ flag,
+ TextFlags.getKeyForFlag(flag),
+ boolean.class,
+ false)); // All aconfig flags are false by default.
+ }
// add other device configs here...
}
private static volatile boolean sDeviceConfigContextEntriesLoaded = false;
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 99a5398..debf828 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -33,6 +33,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessUtils;
import com.android.internal.util.Preconditions;
import com.android.server.display.utils.Plog;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 13ee47e..40b2f5a 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -43,14 +43,16 @@
BrightnessRangeController(HighBrightnessModeController hbmController,
Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler,
- DisplayManagerFlags flags) {
+ DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
this(hbmController, modeChangeCallback, displayDeviceConfig,
- new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags);
+ new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags,
+ displayToken, info);
}
BrightnessRangeController(HighBrightnessModeController hbmController,
Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
- HdrClamper hdrClamper, DisplayManagerFlags flags) {
+ HdrClamper hdrClamper, DisplayManagerFlags flags, IBinder displayToken,
+ DisplayDeviceInfo info) {
mHbmController = hbmController;
mModeChangeCallback = modeChangeCallback;
mHdrClamper = hdrClamper;
@@ -60,10 +62,7 @@
mNormalBrightnessModeController.resetNbmData(
displayDeviceConfig.getLuxThrottlingData());
}
- if (mUseHdrClamper) {
- mHdrClamper.resetHdrConfig(displayDeviceConfig.getHdrBrightnessData());
- }
-
+ updateHdrClamper(info, displayToken, displayDeviceConfig);
}
void dump(PrintWriter pw) {
@@ -101,13 +100,12 @@
displayDeviceConfig::getHdrBrightnessFromSdr);
}
);
- if (mUseHdrClamper) {
- mHdrClamper.resetHdrConfig(displayDeviceConfig.getHdrBrightnessData());
- }
+ updateHdrClamper(info, token, displayDeviceConfig);
}
void stop() {
mHbmController.stop();
+ mHdrClamper.stop();
}
void setAutoBrightnessEnabled(int state) {
@@ -151,6 +149,18 @@
return mHbmController.getTransitionPoint();
}
+ private void updateHdrClamper(DisplayDeviceInfo info, IBinder token,
+ DisplayDeviceConfig displayDeviceConfig) {
+ if (mUseHdrClamper) {
+ DisplayDeviceConfig.HighBrightnessModeData hbmData =
+ displayDeviceConfig.getHighBrightnessModeData();
+ float minimumHdrPercentOfScreen =
+ hbmData == null ? -1f : hbmData.minimumHdrPercentOfScreen;
+ mHdrClamper.resetHdrConfig(displayDeviceConfig.getHdrBrightnessData(), info.width,
+ info.height, minimumHdrPercentOfScreen, token);
+ }
+ }
+
private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) {
if (mUseNbmController) {
boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean();
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 8642fb8..098cb87 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayViewport;
import android.os.IBinder;
import android.util.Slog;
@@ -205,6 +206,24 @@
*/
public Runnable requestDisplayStateLocked(int state, float brightnessState,
float sdrBrightnessState) {
+ return requestDisplayStateLocked(state, brightnessState, sdrBrightnessState, null);
+ }
+
+ /**
+ * Sets the display state, if supported.
+ *
+ * @param state The new display state.
+ * @param brightnessState The new display brightnessState.
+ * @param sdrBrightnessState The new display brightnessState for SDR layers.
+ * @param displayOffloadSession {@link DisplayOffloadSession} associated with current device.
+ * @return A runnable containing work to be deferred until after we have exited the critical
+ * section, or null if none.
+ */
+ public Runnable requestDisplayStateLocked(
+ int state,
+ float brightnessState,
+ float sdrBrightnessState,
+ @Nullable DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) {
return null;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9ef84cb..e942c17 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1772,7 +1772,7 @@
synchronized (mSyncRoot) {
// main display adapter
registerDisplayAdapterLocked(mInjector.getLocalDisplayAdapter(mSyncRoot, mContext,
- mHandler, mDisplayDeviceRepo));
+ mHandler, mDisplayDeviceRepo, mFlags));
// Standalone VR devices rely on a virtual display as their primary display for
// 2D UI. We register virtual display adapter along side the main display adapter
@@ -2093,8 +2093,11 @@
// Only send a request for display state if display state has already been initialized.
if (state != Display.STATE_UNKNOWN) {
final BrightnessPair brightnessPair = mDisplayBrightnesses.get(displayId);
- return device.requestDisplayStateLocked(state, brightnessPair.brightness,
- brightnessPair.sdrBrightness);
+ return device.requestDisplayStateLocked(
+ state,
+ brightnessPair.brightness,
+ brightnessPair.sdrBrightness,
+ display.getDisplayOffloadSessionLocked());
}
}
return null;
@@ -3183,9 +3186,10 @@
}
LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
- Handler handler,
- DisplayAdapter.Listener displayAdapterListener) {
- return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener);
+ Handler handler, DisplayAdapter.Listener displayAdapterListener,
+ DisplayManagerFlags flags) {
+ return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
+ flags);
}
long getDefaultDisplayDelayTimeout() {
@@ -4806,6 +4810,49 @@
}
return displayGroupIds;
}
+
+ @Override
+ public DisplayManagerInternal.DisplayOffloadSession registerDisplayOffloader(
+ int displayId, @NonNull DisplayManagerInternal.DisplayOffloader displayOffloader) {
+ if (!mFlags.isDisplayOffloadEnabled()) {
+ return null;
+ }
+ synchronized (mSyncRoot) {
+ LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (logicalDisplay == null) {
+ Slog.w(TAG, "registering DisplayOffloader: LogicalDisplay for displayId="
+ + displayId + " is not found. No Op.");
+ return null;
+ }
+
+ DisplayPowerControllerInterface displayPowerController =
+ mDisplayPowerControllers.get(logicalDisplay.getDisplayIdLocked());
+ if (displayPowerController == null) {
+ Slog.w(TAG,
+ "setting doze state override: DisplayPowerController for displayId="
+ + displayId + " is unavailable. No Op.");
+ return null;
+ }
+
+ DisplayOffloadSession session =
+ new DisplayOffloadSession() {
+ @Override
+ public void setDozeStateOverride(int displayState) {
+ synchronized (mSyncRoot) {
+ displayPowerController.overrideDozeScreenState(displayState);
+ }
+ }
+
+ @Override
+ public DisplayOffloader getDisplayOffloader() {
+ return displayOffloader;
+ }
+ };
+ logicalDisplay.setDisplayOffloadSessionLocked(session);
+ displayPowerController.setDisplayOffloadSession(session);
+ return session;
+ }
+ }
}
class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 83f4df9..ce98559 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -34,6 +34,8 @@
import android.hardware.display.BrightnessChangeEvent;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.metrics.LogMaker;
@@ -588,8 +590,9 @@
new SparseArray<>();
private boolean mBootCompleted;
-
private final DisplayManagerFlags mFlags;
+ private int mDozeStateOverride = Display.STATE_UNKNOWN;
+ private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
/**
* Creates the display power controller.
@@ -684,7 +687,9 @@
HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback);
mBrightnessRangeController = new BrightnessRangeController(hbmController,
- modeChangeCallback, mDisplayDeviceConfig, mHandler, flags);
+ modeChangeCallback, mDisplayDeviceConfig, mHandler, flags,
+ mDisplayDevice.getDisplayTokenLocked(),
+ mDisplayDevice.getDisplayDeviceInfoLocked());
mBrightnessThrottler = createBrightnessThrottlerLocked();
@@ -957,6 +962,23 @@
}
@Override
+ public void overrideDozeScreenState(int displayState) {
+ synchronized (mLock) {
+ if (mDisplayOffloadSession == null ||
+ !DisplayOffloadSession.isSupportedOffloadState(displayState)) {
+ return;
+ }
+ mDozeStateOverride = displayState;
+ sendUpdatePowerState();
+ }
+ }
+
+ @Override
+ public void setDisplayOffloadSession(DisplayOffloadSession session) {
+ mDisplayOffloadSession = session;
+ }
+
+ @Override
public BrightnessConfiguration getDefaultBrightnessConfiguration() {
if (mAutomaticBrightnessController == null) {
return null;
@@ -1518,6 +1540,7 @@
} else {
state = Display.STATE_DOZE;
}
+ state = mDozeStateOverride == Display.STATE_UNKNOWN ? state : mDozeStateOverride;
if (!mAllowAutoBrightnessWhileDozingConfig) {
brightnessState = mPowerRequest.dozeScreenBrightness;
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
@@ -1937,6 +1960,7 @@
// We want to scale HDR brightness level with the SDR level, we also need to restore
// SDR brightness immediately when entering dim or low power mode.
animateValue = mBrightnessRangeController.getHdrBrightnessValue();
+ mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
}
final float currentBrightness = mPowerState.getScreenBrightness();
@@ -3001,6 +3025,7 @@
pw.println(" mLeadDisplayId=" + mLeadDisplayId);
pw.println(" mLightSensor=" + mLightSensor);
pw.println(" mDisplayBrightnessFollowers=" + mDisplayBrightnessFollowers);
+ pw.println(" mDozeStateOverride=" + mDozeStateOverride);
pw.println();
pw.println("Display Power Controller Locked State:");
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index b0d293a..1652871 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -31,6 +31,8 @@
import android.hardware.display.BrightnessChangeEvent;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.metrics.LogMaker;
@@ -470,9 +472,10 @@
new SparseArray();
private boolean mBootCompleted;
-
private final DisplayManagerFlags mFlags;
+ private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+
/**
* Creates the display power controller.
*/
@@ -549,7 +552,9 @@
mBrightnessThrottler = createBrightnessThrottlerLocked();
mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController,
- modeChangeCallback, mDisplayDeviceConfig, mHandler, flags);
+ modeChangeCallback, mDisplayDeviceConfig, mHandler, flags,
+ mDisplayDevice.getDisplayTokenLocked(),
+ mDisplayDevice.getDisplayDeviceInfoLocked());
mDisplayBrightnessController =
new DisplayBrightnessController(context, null,
@@ -588,21 +593,24 @@
if (mDisplayId == Display.DEFAULT_DISPLAY) {
mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class);
- boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() {
- @Override
- public void onReduceBrightColorsActivationChanged(boolean activated,
- boolean userInitiated) {
- applyReduceBrightColorsSplineAdjustment();
+ if (mCdsi != null) {
+ boolean active = mCdsi.setReduceBrightColorsListener(
+ new ReduceBrightColorsListener() {
+ @Override
+ public void onReduceBrightColorsActivationChanged(boolean activated,
+ boolean userInitiated) {
+ applyReduceBrightColorsSplineAdjustment();
- }
+ }
- @Override
- public void onReduceBrightColorsStrengthChanged(int strength) {
+ @Override
+ public void onReduceBrightColorsStrengthChanged(int strength) {
+ applyReduceBrightColorsSplineAdjustment();
+ }
+ });
+ if (active) {
applyReduceBrightColorsSplineAdjustment();
}
- });
- if (active) {
- applyReduceBrightColorsSplineAdjustment();
}
} else {
mCdsi = null;
@@ -760,6 +768,24 @@
}
@Override
+ public void overrideDozeScreenState(int displayState) {
+ mHandler.postAtTime(() -> {
+ if (mDisplayOffloadSession == null
+ || !(DisplayOffloadSession.isSupportedOffloadState(displayState)
+ || displayState == Display.STATE_UNKNOWN)) {
+ return;
+ }
+ mDisplayStateController.overrideDozeScreenState(displayState);
+ sendUpdatePowerState();
+ }, mClock.uptimeMillis());
+ }
+
+ @Override
+ public void setDisplayOffloadSession(DisplayOffloadSession session) {
+ mDisplayOffloadSession = session;
+ }
+
+ @Override
public BrightnessConfiguration getDefaultBrightnessConfiguration() {
if (mAutomaticBrightnessController == null) {
return null;
@@ -1540,6 +1566,7 @@
// SDR brightness immediately when entering dim or low power mode.
animateValue = mBrightnessRangeController.getHdrBrightnessValue();
customTransitionRate = mBrightnessRangeController.getHdrTransitionRate();
+ mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
}
final float currentBrightness = mPowerState.getScreenBrightness();
@@ -1871,15 +1898,12 @@
private HighBrightnessModeController createHbmControllerLocked(
HighBrightnessModeMetadata hbmMetadata, Runnable modeChangeCallback) {
- final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
- final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
- final IBinder displayToken =
- mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
- final String displayUniqueId =
- mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+ final DisplayDeviceConfig ddConfig = mDisplayDevice.getDisplayDeviceConfig();
+ final IBinder displayToken = mDisplayDevice.getDisplayTokenLocked();
+ final String displayUniqueId = mDisplayDevice.getUniqueId();
final DisplayDeviceConfig.HighBrightnessModeData hbmData =
ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
- final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ final DisplayDeviceInfo info = mDisplayDevice.getDisplayDeviceInfoLocked();
return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN,
PowerManager.BRIGHTNESS_MAX, hbmData, (sdrBrightness, maxDesiredHdrSdrRatio) ->
@@ -3025,9 +3049,9 @@
BrightnessRangeController getBrightnessRangeController(
HighBrightnessModeController hbmController, Runnable modeChangeCallback,
DisplayDeviceConfig displayDeviceConfig, Handler handler,
- DisplayManagerFlags flags) {
+ DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
return new BrightnessRangeController(hbmController,
- modeChangeCallback, displayDeviceConfig, handler, flags);
+ modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info);
}
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index e3108c9..181386a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -139,6 +139,10 @@
boolean requestPowerState(DisplayManagerInternal.DisplayPowerRequest request,
boolean waitForNegativeProximity);
+ void overrideDozeScreenState(int displayState);
+
+ void setDisplayOffloadSession(DisplayManagerInternal.DisplayOffloadSession session);
+
/**
* Sets up the temporary autobrightness adjustment when the user is yet to settle down to a
* value.
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 0f3e7c5..0a1f316 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -22,6 +22,9 @@
import android.app.ActivityThread;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.sidekick.SidekickInternal;
import android.os.Build;
import android.os.Handler;
@@ -47,6 +50,7 @@
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
@@ -80,21 +84,24 @@
private final boolean mIsBootDisplayModeSupported;
+ private final DisplayManagerFlags mFlags;
+
private Context mOverlayContext;
// Called with SyncRoot lock held.
LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context,
- Handler handler, Listener listener) {
- this(syncRoot, context, handler, listener, new Injector());
+ Handler handler, Listener listener, DisplayManagerFlags flags) {
+ this(syncRoot, context, handler, listener, flags, new Injector());
}
@VisibleForTesting
LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler,
- Listener listener, Injector injector) {
+ Listener listener, DisplayManagerFlags flags, Injector injector) {
super(syncRoot, context, handler, listener, TAG);
mInjector = injector;
mSurfaceControlProxy = mInjector.getSurfaceControlProxy();
mIsBootDisplayModeSupported = mSurfaceControlProxy.getBootDisplayModeSupport();
+ mFlags = flags;
}
@Override
@@ -224,6 +231,7 @@
private boolean mAllmRequested;
private boolean mGameContentTypeRequested;
private boolean mSidekickActive;
+ private boolean mDisplayOffloadActive;
private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo;
// The supported display modes according to SurfaceFlinger
private SurfaceControl.DisplayMode[] mSfDisplayModes;
@@ -746,8 +754,12 @@
}
@Override
- public Runnable requestDisplayStateLocked(final int state, final float brightnessState,
- final float sdrBrightnessState) {
+ public Runnable requestDisplayStateLocked(
+ final int state,
+ final float brightnessState,
+ final float sdrBrightnessState,
+ DisplayOffloadSession displayOffloadSession) {
+
// Assume that the brightness is off if the display is being turned off.
assert state != Display.STATE_OFF
|| brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT;
@@ -813,18 +825,39 @@
+ ", state=" + Display.stateToString(state) + ")");
}
- // We must tell sidekick to stop controlling the display before we
- // can change its power mode, so do that first.
- if (mSidekickActive) {
- Trace.traceBegin(Trace.TRACE_TAG_POWER,
- "SidekickInternal#endDisplayControl");
- try {
- mSidekickInternal.endDisplayControl();
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ DisplayOffloader displayOffloader =
+ displayOffloadSession == null
+ ? null
+ : displayOffloadSession.getDisplayOffloader();
+
+ boolean isDisplayOffloadEnabled = mFlags.isDisplayOffloadEnabled();
+
+ // We must tell sidekick/displayoffload to stop controlling the display
+ // before we can change its power mode, so do that first.
+ if (isDisplayOffloadEnabled) {
+ if (mDisplayOffloadActive && displayOffloader != null) {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER,
+ "DisplayOffloader#stopOffload");
+ try {
+ displayOffloader.stopOffload();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ }
+ mDisplayOffloadActive = false;
}
- mSidekickActive = false;
+ } else {
+ if (mSidekickActive) {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER,
+ "SidekickInternal#endDisplayControl");
+ try {
+ mSidekickInternal.endDisplayControl();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ }
+ mSidekickActive = false;
+ }
}
+
final int mode = getPowerModeForState(state);
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayState("
+ "id=" + physicalDisplayId
@@ -836,16 +869,32 @@
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
setCommittedState(state);
+
// If we're entering a suspended (but not OFF) power state and we
- // have a sidekick available, tell it now that it can take control.
- if (Display.isSuspendedState(state) && state != Display.STATE_OFF
- && mSidekickInternal != null && !mSidekickActive) {
- Trace.traceBegin(Trace.TRACE_TAG_POWER,
- "SidekickInternal#startDisplayControl");
- try {
- mSidekickActive = mSidekickInternal.startDisplayControl(state);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ // have a sidekick/displayoffload available, tell it now that it can take
+ // control.
+ if (isDisplayOffloadEnabled) {
+ if (DisplayOffloadSession.isSupportedOffloadState(state) &&
+ displayOffloader != null
+ && !mDisplayOffloadActive) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_POWER, "DisplayOffloader#startOffload");
+ try {
+ mDisplayOffloadActive = displayOffloader.startOffload();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ }
+ }
+ } else {
+ if (Display.isSuspendedState(state) && state != Display.STATE_OFF
+ && mSidekickInternal != null && !mSidekickActive) {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER,
+ "SidekickInternal#startDisplayControl");
+ try {
+ mSidekickActive = mSidekickInternal.startDisplayControl(state);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ }
}
}
}
@@ -858,6 +907,7 @@
}
}
+
private void setDisplayBrightness(float brightnessState,
float sdrBrightnessState) {
// brightnessState includes invalid, off and full range.
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index d4d104e..bd82b81 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -137,6 +137,9 @@
private final Rect mTempLayerStackRect = new Rect();
private final Rect mTempDisplayRect = new Rect();
+ /** A session token that controls the offloading operations of this logical display. */
+ private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+
/**
* Name of a display group to which the display is assigned.
*/
@@ -941,6 +944,15 @@
return mDisplayGroupName;
}
+ public void setDisplayOffloadSessionLocked(
+ DisplayManagerInternal.DisplayOffloadSession session) {
+ mDisplayOffloadSession = session;
+ }
+
+ public DisplayManagerInternal.DisplayOffloadSession getDisplayOffloadSessionLocked() {
+ return mDisplayOffloadSession;
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
pw.println("mIsEnabled=" + mIsEnabled);
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 5ba042c..e38c2c5 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -20,6 +20,8 @@
import android.util.FloatProperty;
import android.view.Choreographer;
+import com.android.internal.display.BrightnessUtils;
+
/**
* A custom animator that progressively updates a property value at
* a given variable rate until it reaches a particular target value.
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index a514136..e46edd9 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -17,9 +17,13 @@
package com.android.server.display.brightness.clamper;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.os.Handler;
+import android.os.IBinder;
import android.os.PowerManager;
+import android.view.SurfaceControlHdrLayerInfoListener;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.config.HdrBrightnessData;
import java.io.PrintWriter;
@@ -33,11 +37,18 @@
private final Runnable mDebouncer;
+ private final HdrLayerInfoListener mHdrListener;
+
@Nullable
private HdrBrightnessData mHdrBrightnessData = null;
+ @Nullable
+ private IBinder mRegisteredDisplayToken = null;
+
private float mAmbientLux = Float.MAX_VALUE;
+ private boolean mHdrVisible = false;
+
private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
private float mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX;
@@ -47,6 +58,12 @@
public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener,
Handler handler) {
+ this(clamperChangeListener, handler, new Injector());
+ }
+
+ @VisibleForTesting
+ public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ Handler handler, Injector injector) {
mClamperChangeListener = clamperChangeListener;
mHandler = handler;
mDebouncer = () -> {
@@ -54,6 +71,10 @@
mMaxBrightness = mDesiredMaxBrightness;
mClamperChangeListener.onChanged();
};
+ mHdrListener = injector.getHdrListener((visible) -> {
+ mHdrVisible = visible;
+ recalculateBrightnessCap(mHdrBrightnessData, mAmbientLux, mHdrVisible);
+ }, handler);
}
// Called in same looper: mHandler.getLooper()
@@ -72,16 +93,37 @@
*/
public void onAmbientLuxChange(float ambientLux) {
mAmbientLux = ambientLux;
- recalculateBrightnessCap(mHdrBrightnessData, ambientLux);
+ recalculateBrightnessCap(mHdrBrightnessData, ambientLux, mHdrVisible);
}
/**
* Updates brightness cap config.
* Called in same looper: mHandler.getLooper()
*/
- public void resetHdrConfig(HdrBrightnessData data) {
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public void resetHdrConfig(HdrBrightnessData data, int width, int height,
+ float minimumHdrPercentOfScreen, IBinder displayToken) {
mHdrBrightnessData = data;
- recalculateBrightnessCap(data, mAmbientLux);
+ mHdrListener.mHdrMinPixels = (float) (width * height) * minimumHdrPercentOfScreen;
+ if (displayToken != mRegisteredDisplayToken) { // token changed, resubscribe
+ if (mRegisteredDisplayToken != null) { // previous token not null, unsubscribe
+ mHdrListener.unregister(mRegisteredDisplayToken);
+ mHdrVisible = false;
+ }
+ if (displayToken != null) { // new token not null, subscribe
+ mHdrListener.register(displayToken);
+ }
+ mRegisteredDisplayToken = displayToken;
+ }
+ recalculateBrightnessCap(data, mAmbientLux, mHdrVisible);
+ }
+
+ /** Clean up all resources */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public void stop() {
+ if (mRegisteredDisplayToken != null) {
+ mHdrListener.unregister(mRegisteredDisplayToken);
+ }
}
/**
@@ -98,13 +140,28 @@
pw.println(" mAmbientLux=" + mAmbientLux);
}
- private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux) {
- if (data == null) {
- mHandler.removeCallbacks(mDebouncer);
+ private void reset() {
+ if (mMaxBrightness == PowerManager.BRIGHTNESS_MAX
+ && mDesiredMaxBrightness == PowerManager.BRIGHTNESS_MAX && mTransitionRate == -1f
+ && mDesiredTransitionRate == -1f) { // already done reset, do nothing
return;
}
- float expectedMaxBrightness = findBrightnessLimit(data, ambientLux);
+ mHandler.removeCallbacks(mDebouncer);
+ mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+ mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+ mDesiredTransitionRate = -1f;
+ mTransitionRate = 1f;
+ mClamperChangeListener.onChanged();
+ }
+ private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux,
+ boolean hdrVisible) {
+ if (data == null || !hdrVisible) {
+ reset();
+ return;
+ }
+
+ float expectedMaxBrightness = findBrightnessLimit(data, ambientLux);
if (mMaxBrightness == expectedMaxBrightness) {
mDesiredMaxBrightness = mMaxBrightness;
mDesiredTransitionRate = -1f;
@@ -127,6 +184,8 @@
mHandler.removeCallbacks(mDebouncer);
mHandler.postDelayed(mDebouncer, debounceTime);
}
+ // do nothing if expectedMaxBrightness == mDesiredMaxBrightness
+ // && expectedMaxBrightness != mMaxBrightness
}
private float findBrightnessLimit(HdrBrightnessData data, float ambientLux) {
@@ -143,4 +202,36 @@
}
return foundMaxBrightness;
}
+
+ @FunctionalInterface
+ interface HdrListener {
+ void onHdrVisible(boolean visible);
+ }
+
+ static class HdrLayerInfoListener extends SurfaceControlHdrLayerInfoListener {
+ private final HdrListener mHdrListener;
+
+ private final Handler mHandler;
+
+ private float mHdrMinPixels = Float.MAX_VALUE;
+
+ HdrLayerInfoListener(HdrListener hdrListener, Handler handler) {
+ mHdrListener = hdrListener;
+ mHandler = handler;
+ }
+
+ @Override
+ public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW,
+ int maxH, int flags, float maxDesiredHdrSdrRatio) {
+ mHandler.post(() ->
+ mHdrListener.onHdrVisible(
+ numberOfHdrLayers > 0 && (float) (maxW * maxH) >= mHdrMinPixels));
+ }
+ }
+
+ static class Injector {
+ HdrLayerInfoListener getHdrListener(HdrListener hdrListener, Handler handler) {
+ return new HdrLayerInfoListener(hdrListener, handler);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index ff768d6..b6273e1 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -47,6 +47,10 @@
Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1,
Flags::enableAdaptiveToneImprovements1);
+ private final FlagState mDisplayOffloadFlagState = new FlagState(
+ Flags.FLAG_ENABLE_DISPLAY_OFFLOAD,
+ Flags::enableDisplayOffload);
+
private final FlagState mDisplayResolutionRangeVotingState = new FlagState(
Flags.FLAG_ENABLE_DISPLAY_RESOLUTION_RANGE_VOTING,
Flags::enableDisplayResolutionRangeVoting);
@@ -111,6 +115,11 @@
return mDisplaysRefreshRatesSynchronizationState.isEnabled();
}
+ /** Returns whether displayoffload is enabled on not */
+ public boolean isDisplayOffloadEnabled() {
+ return mDisplayOffloadFlagState.isEnabled();
+ }
+
private static class FlagState {
private final String mName;
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index a5b8cbb..542f26c 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -63,5 +63,12 @@
namespace: "display_manager"
description: "Enables synchronization of refresh rates across displays"
bug: "294015845"
+}
+
+flag {
+ name: "enable_display_offload"
+ namespace: "display_manager"
+ description: "Feature flag for DisplayOffload"
+ bug: "299521647"
is_fixed_read_only: true
}
diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java
index b1a1c60..5d6e650 100644
--- a/services/core/java/com/android/server/display/state/DisplayStateController.java
+++ b/services/core/java/com/android/server/display/state/DisplayStateController.java
@@ -32,6 +32,7 @@
public class DisplayStateController {
private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
private boolean mPerformScreenOffTransition = false;
+ private int mDozeStateOverride = Display.STATE_UNKNOWN;
public DisplayStateController(DisplayPowerProximityStateController
displayPowerProximityStateController) {
@@ -65,6 +66,7 @@
} else {
state = Display.STATE_DOZE;
}
+ state = mDozeStateOverride == Display.STATE_UNKNOWN ? state : mDozeStateOverride;
break;
case DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM:
case DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT:
@@ -84,6 +86,10 @@
return state;
}
+ public void overrideDozeScreenState(int displayState) {
+ mDozeStateOverride = displayState;
+ }
+
/**
* Checks if the screen off transition is to be performed or not.
*/
@@ -100,6 +106,8 @@
pw.println();
pw.println("DisplayStateController:");
pw.println(" mPerformScreenOffTransition:" + mPerformScreenOffTransition);
+ pw.println(" mDozeStateOverride=" + mDozeStateOverride);
+
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
if (mDisplayPowerProximityStateController != null) {
mDisplayPowerProximityStateController.dumpLocal(ipw);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index c3abfc1..f168f43 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -349,17 +349,17 @@
@Override
public void onUserStarting(@NonNull TargetUser user) {
- mLockSettingsService.onStartUser(user.getUserIdentifier());
+ mLockSettingsService.onUserStarting(user.getUserIdentifier());
}
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
- mLockSettingsService.onUnlockUser(user.getUserIdentifier());
+ mLockSettingsService.onUserUnlocking(user.getUserIdentifier());
}
@Override
public void onUserStopped(@NonNull TargetUser user) {
- mLockSettingsService.onCleanupUser(user.getUserIdentifier());
+ mLockSettingsService.onUserStopped(user.getUserIdentifier());
}
}
@@ -784,7 +784,7 @@
}
@VisibleForTesting
- void onCleanupUser(int userId) {
+ void onUserStopped(int userId) {
hideEncryptionNotification(new UserHandle(userId));
// User is stopped with its CE key evicted. Restore strong auth requirement to the default
// flags after boot since stopping and restarting a user later is equivalent to rebooting
@@ -796,7 +796,7 @@
}
}
- private void onStartUser(final int userId) {
+ private void onUserStarting(final int userId) {
maybeShowEncryptionNotificationForUser(userId, "user started");
}
@@ -832,7 +832,7 @@
}
}
- private void onUnlockUser(final int userId) {
+ private void onUserUnlocking(final int userId) {
// Perform tasks which require locks in LSS on a handler, as we are callbacks from
// ActivityManager.unlockUser()
mHandler.post(new Runnable() {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 4892c22..83a3125 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -22,6 +22,7 @@
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
import static android.media.MediaRouter2Utils.getOriginalId;
import static android.media.MediaRouter2Utils.getProviderId;
+
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.media.MediaFeatureFlagManager.FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE;
@@ -487,12 +488,13 @@
final int callerUid = Binder.getCallingUid();
final int callerPid = Binder.getCallingPid();
- final int userId = UserHandle.getUserHandleForUid(callerUid).getIdentifier();
+ final int callerUserId = UserHandle.getUserHandleForUid(callerUid).getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- registerManagerLocked(manager, callerUid, callerPid, callerPackageName, userId);
+ registerManagerLocked(
+ manager, callerUid, callerPid, callerPackageName, callerUserId);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1156,8 +1158,12 @@
}
@GuardedBy("mLock")
- private void registerManagerLocked(@NonNull IMediaRouter2Manager manager,
- int callerUid, int callerPid, @NonNull String callerPackageName, int userId) {
+ private void registerManagerLocked(
+ @NonNull IMediaRouter2Manager manager,
+ int callerUid,
+ int callerPid,
+ @NonNull String callerPackageName,
+ int callerUserId) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -1167,14 +1173,17 @@
return;
}
- Slog.i(TAG, TextUtils.formatSimple(
- "registerManager | callerUid: %d, callerPid: %d, package: %s, user: %d",
- callerUid, callerPid, callerPackageName, userId));
+ Slog.i(
+ TAG,
+ TextUtils.formatSimple(
+ "registerManager | callerUid: %d, callerPid: %d, callerPackage: %s,"
+ + " callerUserId: %d",
+ callerUid, callerPid, callerPackageName, callerUserId));
mContext.enforcePermission(Manifest.permission.MEDIA_CONTENT_CONTROL, callerPid, callerUid,
"Must hold MEDIA_CONTENT_CONTROL permission.");
- UserRecord userRecord = getOrCreateUserRecordLocked(userId);
+ UserRecord userRecord = getOrCreateUserRecordLocked(callerUserId);
managerRecord = new ManagerRecord(
userRecord, manager, callerUid, callerPid, callerPackageName);
try {
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 9a69d77..e367609 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -17,9 +17,14 @@
package com.android.server.pm;
import static android.os.PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED;
+import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.Process.SYSTEM_UID;
import static android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED;
+
+import static com.android.server.pm.PackageManagerService.DEBUG_BACKUP;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
+import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
import static com.android.server.pm.PackageManagerService.PACKAGE_SCHEME;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.PackageManagerService.TAG;
@@ -28,6 +33,7 @@
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.BroadcastOptions;
@@ -38,12 +44,18 @@
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.os.PowerExemptionManager;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
import android.provider.DeviceConfig;
+import android.stats.storage.StorageEnums;
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
@@ -51,10 +63,15 @@
import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.function.BiFunction;
-import java.util.function.Supplier;
/**
* Helper class to send broadcasts for various situations.
@@ -70,14 +87,20 @@
private final UserManagerInternal mUmInternal;
private final ActivityManagerInternal mAmInternal;
private final Context mContext;
+ private final Handler mHandler;
+ private final PackageMonitorCallbackHelper mPackageMonitorCallbackHelper;
+ private final AppsFilterSnapshot mAppsFilter;
BroadcastHelper(PackageManagerServiceInjector injector) {
mUmInternal = injector.getUserManagerInternal();
mAmInternal = injector.getActivityManagerInternal();
mContext = injector.getContext();
+ mHandler = injector.getHandler();
+ mPackageMonitorCallbackHelper = injector.getPackageMonitorCallbackHelper();
+ mAppsFilter = injector.getAppsFilter();
}
- public void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
+ void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
final int[] userIds, int[] instantUserIds,
@Nullable SparseArray<int[]> broadcastAllowList,
@@ -114,9 +137,16 @@
* the system and applications allowed to see instant applications to receive package
* lifecycle events for instant applications.
*/
- public void doSendBroadcast(String action, String pkg, Bundle extras,
- int flags, String targetPkg, IIntentReceiver finishedReceiver,
- int[] userIds, boolean isInstantApp, @Nullable SparseArray<int[]> broadcastAllowList,
+ private void doSendBroadcast(
+ @NonNull String action,
+ @Nullable String pkg,
+ @Nullable Bundle extras,
+ int flags,
+ @Nullable String targetPkg,
+ @Nullable IIntentReceiver finishedReceiver,
+ @NonNull int[] userIds,
+ boolean isInstantApp,
+ @Nullable SparseArray<int[]> broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
for (int userId : userIds) {
@@ -166,9 +196,11 @@
}
}
- public void sendResourcesChangedBroadcast(@NonNull Supplier<Computer> snapshotComputer,
- boolean mediaStatus, boolean replacing, @NonNull String[] pkgNames,
- @NonNull int[] uids) {
+ void sendResourcesChangedBroadcast(@NonNull Computer snapshot,
+ boolean mediaStatus,
+ boolean replacing,
+ @NonNull String[] pkgNames,
+ @NonNull int[] uids) {
if (ArrayUtils.isEmpty(pkgNames) || ArrayUtils.isEmpty(uids)) {
return;
}
@@ -184,7 +216,7 @@
null /* targetPkg */, null /* finishedReceiver */, null /* userIds */,
null /* instantUserIds */, null /* broadcastAllowList */,
(callingUid, intentExtras) -> filterExtrasChangedPackageList(
- snapshotComputer.get(), callingUid, intentExtras),
+ snapshot, callingUid, intentExtras),
null /* bOptions */);
}
@@ -193,8 +225,9 @@
* automatically without needing an explicit launch.
* Send it a LOCKED_BOOT_COMPLETED/BOOT_COMPLETED if it would ordinarily have gotten ones.
*/
- public void sendBootCompletedBroadcastToSystemApp(
- String packageName, boolean includeStopped, int userId) {
+ private void sendBootCompletedBroadcastToSystemApp(@NonNull String packageName,
+ boolean includeStopped,
+ int userId) {
// If user is not running, the app didn't miss any broadcast
if (!mUmInternal.isUserRunning(userId)) {
return;
@@ -229,7 +262,7 @@
}
}
- public @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
+ private @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
@PowerExemptionManager.ReasonCode int reasonCode) {
long duration = 10_000;
if (mAmInternal != null) {
@@ -242,9 +275,14 @@
return bOptions;
}
- public void sendPackageChangedBroadcast(String packageName, boolean dontKillApp,
- ArrayList<String> componentNames, int packageUid, String reason,
- int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
+ private void sendPackageChangedBroadcast(@NonNull String packageName,
+ boolean dontKillApp,
+ @NonNull ArrayList<String> componentNames,
+ int packageUid,
+ @Nullable String reason,
+ @Nullable int[] userIds,
+ @Nullable int[] instantUserIds,
+ @Nullable SparseArray<int[]> broadcastAllowList) {
if (DEBUG_INSTALL) {
Log.v(TAG, "Sending package changed: package=" + packageName + " components="
+ componentNames);
@@ -269,7 +307,7 @@
null /* bOptions */);
}
- public static void sendDeviceCustomizationReadyBroadcast() {
+ static void sendDeviceCustomizationReadyBroadcast() {
final Intent intent = new Intent(Intent.ACTION_DEVICE_CUSTOMIZATION_READY);
intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
final IActivityManager am = ActivityManager.getService();
@@ -285,15 +323,23 @@
}
}
- public void sendSessionCommitBroadcast(PackageInstaller.SessionInfo sessionInfo, int userId,
- int launcherUid, @Nullable ComponentName launcherComponent,
- @Nullable String appPredictionServicePackage) {
+ void sendSessionCommitBroadcast(@NonNull Computer snapshot,
+ @NonNull PackageInstaller.SessionInfo sessionInfo,
+ int userId,
+ @Nullable String appPredictionServicePackage) {
+ UserManagerService ums = UserManagerService.getInstance();
+ if (ums == null || sessionInfo.isStaged()) {
+ return;
+ }
+ final UserInfo parent = ums.getProfileParent(userId);
+ final int launcherUserId = (parent != null) ? parent.id : userId;
+ final ComponentName launcherComponent = snapshot.getDefaultHomeActivity(launcherUserId);
if (launcherComponent != null) {
Intent launcherIntent = new Intent(PackageInstaller.ACTION_SESSION_COMMITTED)
.putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo)
.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
.setPackage(launcherComponent.getPackageName());
- mContext.sendBroadcastAsUser(launcherIntent, UserHandle.of(launcherUid));
+ mContext.sendBroadcastAsUser(launcherIntent, UserHandle.of(launcherUserId));
}
// TODO(b/122900055) Change/Remove this and replace with new permission role.
if (appPredictionServicePackage != null) {
@@ -301,30 +347,278 @@
.putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo)
.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
.setPackage(appPredictionServicePackage);
- mContext.sendBroadcastAsUser(predictorIntent, UserHandle.of(launcherUid));
+ mContext.sendBroadcastAsUser(predictorIntent, UserHandle.of(launcherUserId));
}
}
- public void sendPreferredActivityChangedBroadcast(int userId) {
- final IActivityManager am = ActivityManager.getService();
- if (am == null) {
+ void sendPreferredActivityChangedBroadcast(int userId) {
+ mHandler.post(() -> {
+ final IActivityManager am = ActivityManager.getService();
+ if (am == null) {
+ return;
+ }
+
+ final Intent intent = new Intent(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ try {
+ am.broadcastIntentWithFeature(null, null, intent, null, null,
+ 0, null, null, null, null, null, android.app.AppOpsManager.OP_NONE,
+ null, false, false, userId);
+ } catch (RemoteException e) {
+ }
+ });
+ }
+
+ void sendPostInstallBroadcasts(@NonNull Computer snapshot,
+ @NonNull InstallRequest request,
+ @NonNull String packageName,
+ @NonNull String requiredPermissionControllerPackage,
+ @NonNull String[] requiredVerifierPackages,
+ @NonNull String requiredInstallerPackage,
+ @NonNull PackageSender packageSender,
+ boolean isLaunchedForRestore,
+ boolean isKillApp,
+ boolean isUpdate,
+ boolean isArchived) {
+ // Send the removed broadcasts
+ if (request.getRemovedInfo() != null) {
+ if (request.getRemovedInfo().mIsExternal) {
+ if (DEBUG_INSTALL) {
+ Slog.i(TAG, "upgrading pkg " + request.getRemovedInfo().mRemovedPackage
+ + " is ASEC-hosted -> UNAVAILABLE");
+ }
+ final String[] pkgNames = new String[]{
+ request.getRemovedInfo().mRemovedPackage};
+ final int[] uids = new int[]{request.getRemovedInfo().mUid};
+ notifyResourcesChanged(
+ false /* mediaStatus */, true /* replacing */, pkgNames, uids);
+ sendResourcesChangedBroadcast(
+ snapshot, false /* mediaStatus */, true /* replacing */, pkgNames, uids);
+ }
+ sendPackageRemovedBroadcasts(
+ request.getRemovedInfo(), packageSender, isKillApp, false /*removedBySystem*/,
+ false /*isArchived*/);
+ }
+
+ final int[] firstUserIds = request.getFirstTimeBroadcastUserIds();
+ final int[] firstInstantUserIds = request.getFirstTimeBroadcastInstantUserIds();
+ final int[] updateUserIds = request.getUpdateBroadcastUserIds();
+ final int[] instantUserIds = request.getUpdateBroadcastInstantUserIds();
+
+ final String installerPackageName =
+ request.getInstallerPackageName() != null
+ ? request.getInstallerPackageName()
+ : request.getRemovedInfo() != null
+ ? request.getRemovedInfo().mInstallerPackageName
+ : null;
+
+ Bundle extras = new Bundle();
+ extras.putInt(Intent.EXTRA_UID, request.getAppId());
+ if (isUpdate) {
+ extras.putBoolean(Intent.EXTRA_REPLACING, true);
+ }
+ if (isArchived) {
+ extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
+ }
+ extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, request.getDataLoaderType());
+
+ final String staticSharedLibraryName = request.getPkg().getStaticSharedLibraryName();
+ // If a package is a static shared library, then only the installer of the package
+ // should get the broadcast.
+ if (installerPackageName != null && staticSharedLibraryName != null) {
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName,
+ extras, 0 /*flags*/,
+ installerPackageName, null /*finishedReceiver*/,
+ request.getNewUsers(), null /* instantUserIds*/,
+ null /* broadcastAllowList */, null);
+ }
+
+ // Send installed broadcasts if the package is not a static shared lib.
+ if (staticSharedLibraryName == null) {
+ // Send PACKAGE_ADDED broadcast for users that see the package for the first time
+ // sendPackageAddedForNewUsers also deals with system apps
+ final int appId = UserHandle.getAppId(request.getAppId());
+ final boolean isSystem = request.isInstallSystem();
+ final boolean isVirtualPreload =
+ ((request.getInstallFlags() & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
+ sendPackageAddedForNewUsers(snapshot, packageName,
+ isSystem || isVirtualPreload,
+ isVirtualPreload /*startReceiver*/, appId,
+ firstUserIds, firstInstantUserIds, isArchived, request.getDataLoaderType());
+
+ // Send PACKAGE_ADDED broadcast for users that don't see
+ // the package for the first time
+
+ // Send to all running apps.
+ final SparseArray<int[]> newBroadcastAllowList =
+ mAppsFilter.getVisibilityAllowList(snapshot,
+ snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
+ updateUserIds, snapshot.getPackageStates());
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName,
+ extras, 0 /*flags*/,
+ null /*targetPackage*/, null /*finishedReceiver*/,
+ updateUserIds, instantUserIds, newBroadcastAllowList, null);
+ // Send to the installer, even if it's not running.
+ if (installerPackageName != null) {
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName,
+ extras, 0 /*flags*/,
+ installerPackageName, null /*finishedReceiver*/,
+ updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
+ }
+ // Send to PermissionController for all update users, even if it may not be running
+ // for some users
+ if (isPrivacySafetyLabelChangeNotificationsEnabled(mContext)) {
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName,
+ extras, 0 /*flags*/,
+ requiredPermissionControllerPackage, null /*finishedReceiver*/,
+ updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
+ }
+ // Notify required verifier(s) that are not the installer of record for the package.
+ for (String verifierPackageName : requiredVerifierPackages) {
+ if (verifierPackageName != null && !verifierPackageName.equals(
+ installerPackageName)) {
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED,
+ packageName,
+ extras, 0 /*flags*/,
+ verifierPackageName, null /*finishedReceiver*/,
+ updateUserIds, instantUserIds, null /* broadcastAllowList */,
+ null);
+ }
+ }
+ // If package installer is defined, notify package installer about new
+ // app installed
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName,
+ extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/,
+ requiredInstallerPackage, null /*finishedReceiver*/,
+ firstUserIds, instantUserIds, null /* broadcastAllowList */, null);
+
+ // Send replaced for users that don't see the package for the first time
+ if (isUpdate) {
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED,
+ packageName, extras, 0 /*flags*/,
+ null /*targetPackage*/, null /*finishedReceiver*/,
+ updateUserIds, instantUserIds,
+ request.getRemovedInfo().mBroadcastAllowList, null);
+ if (installerPackageName != null) {
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED, packageName,
+ extras, 0 /*flags*/,
+ installerPackageName, null /*finishedReceiver*/,
+ updateUserIds, instantUserIds, null /*broadcastAllowList*/,
+ null);
+ }
+ for (String verifierPackageName : requiredVerifierPackages) {
+ if (verifierPackageName != null && !verifierPackageName.equals(
+ installerPackageName)) {
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED,
+ packageName, extras, 0 /*flags*/, verifierPackageName,
+ null /*finishedReceiver*/, updateUserIds, instantUserIds,
+ null /*broadcastAllowList*/, null);
+ }
+ }
+ sendPackageBroadcastAndNotify(Intent.ACTION_MY_PACKAGE_REPLACED,
+ null /*package*/, null /*extras*/, 0 /*flags*/,
+ packageName /*targetPackage*/,
+ null /*finishedReceiver*/, updateUserIds, instantUserIds,
+ null /*broadcastAllowList*/,
+ getTemporaryAppAllowlistBroadcastOptions(
+ REASON_PACKAGE_REPLACED).toBundle());
+ } else if (isLaunchedForRestore && !request.isInstallSystem()) {
+ // First-install and we did a restore, so we're responsible for the
+ // first-launch broadcast.
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "Post-restore of " + packageName
+ + " sending FIRST_LAUNCH in " + Arrays.toString(firstUserIds));
+ }
+ sendFirstLaunchBroadcast(packageName, installerPackageName,
+ firstUserIds, firstInstantUserIds);
+ }
+
+ // Send broadcast package appeared if external for all users
+ if (request.getPkg().isExternalStorage()) {
+ if (!isUpdate) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ VolumeInfo volume =
+ storage.findVolumeByUuid(
+ StorageManager.convert(
+ request.getPkg().getVolumeUuid()).toString());
+ int packageExternalStorageType =
+ PackageManagerServiceUtils.getPackageExternalStorageType(volume,
+ /* isExternalStorage */ true);
+ // If the package was installed externally, log it.
+ if (packageExternalStorageType != StorageEnums.UNKNOWN) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED,
+ packageExternalStorageType, packageName);
+ }
+ }
+ if (DEBUG_INSTALL) {
+ Slog.i(TAG, "upgrading pkg " + packageName + " is external");
+ }
+ if (!isArchived) {
+ final String[] pkgNames = new String[]{packageName};
+ final int[] uids = new int[]{request.getPkg().getUid()};
+ sendResourcesChangedBroadcast(snapshot,
+ true /* mediaStatus */, true /* replacing */, pkgNames, uids);
+ notifyResourcesChanged(true /* mediaStatus */,
+ true /* replacing */, pkgNames, uids);
+ }
+ }
+ } else { // if static shared lib
+ final ArrayList<AndroidPackage> libraryConsumers = request.getLibraryConsumers();
+ if (!ArrayUtils.isEmpty(libraryConsumers)) {
+ // No need to kill consumers if it's installation of new version static shared lib.
+ final boolean dontKillApp = !isUpdate;
+ for (int i = 0; i < libraryConsumers.size(); i++) {
+ AndroidPackage pkg = libraryConsumers.get(i);
+ // send broadcast that all consumers of the static shared library have changed
+ sendPackageChangedBroadcast(snapshot, pkg.getPackageName(),
+ dontKillApp,
+ new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
+ pkg.getUid(), null);
+ }
+ }
+ }
+ }
+
+ private void sendPackageAddedForNewUsers(@NonNull Computer snapshot,
+ @NonNull String packageName,
+ boolean sendBootCompleted,
+ boolean includeStopped,
+ @AppIdInt int appId,
+ int[] userIds,
+ int[] instantUserIds,
+ boolean isArchived,
+ int dataLoaderType) {
+ if (ArrayUtils.isEmpty(userIds) && ArrayUtils.isEmpty(instantUserIds)) {
return;
}
-
- final Intent intent = new Intent(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- try {
- am.broadcastIntentWithFeature(null, null, intent, null, null,
- 0, null, null, null, null, null, android.app.AppOpsManager.OP_NONE,
- null, false, false, userId);
- } catch (RemoteException e) {
+ SparseArray<int[]> broadcastAllowList = mAppsFilter.getVisibilityAllowList(snapshot,
+ snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
+ userIds, snapshot.getPackageStates());
+ mHandler.post(
+ () -> sendPackageAddedForNewUsers(packageName, appId, userIds,
+ instantUserIds, isArchived, dataLoaderType, broadcastAllowList));
+ mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(packageName, appId, userIds,
+ instantUserIds, isArchived, dataLoaderType, broadcastAllowList, mHandler);
+ if (sendBootCompleted && !ArrayUtils.isEmpty(userIds)) {
+ mHandler.post(() -> {
+ for (int userId : userIds) {
+ sendBootCompletedBroadcastToSystemApp(
+ packageName, includeStopped, userId);
+ }
+ }
+ );
}
}
- public void sendPackageAddedForNewUsers(String packageName, @AppIdInt int appId, int[] userIds,
- int[] instantUserIds, boolean isArchived, int dataLoaderType,
- SparseArray<int[]> broadcastAllowlist) {
+ private void sendPackageAddedForNewUsers(@NonNull String packageName,
+ @AppIdInt int appId,
+ int[] userIds,
+ int[] instantUserIds,
+ boolean isArchived,
+ int dataLoaderType,
+ @NonNull SparseArray<int[]> broadcastAllowlist) {
Bundle extras = new Bundle(1);
// Set to UID of the first user, EXTRA_UID is automatically updated in sendPackageBroadcast
final int uid = UserHandle.getUid(
@@ -349,7 +643,30 @@
}
}
- public void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
+ void sendPackageAddedForUser(@NonNull Computer snapshot,
+ @NonNull String packageName,
+ @NonNull PackageStateInternal packageState,
+ int userId,
+ boolean isArchived,
+ int dataLoaderType,
+ @Nullable String appPredictionServicePackage) {
+ final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
+ final boolean isSystem = packageState.isSystem();
+ final boolean isInstantApp = userState.isInstantApp();
+ final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
+ final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
+ sendPackageAddedForNewUsers(snapshot, packageName, isSystem /*sendBootCompleted*/,
+ false /*startReceiver*/, packageState.getAppId(), userIds, instantUserIds,
+ isArchived, dataLoaderType);
+
+ // Send a session commit broadcast
+ final PackageInstaller.SessionInfo info = new PackageInstaller.SessionInfo();
+ info.installReason = userState.getInstallReason();
+ info.appPackageName = packageName;
+ sendSessionCommitBroadcast(snapshot, info, userId, appPredictionServicePackage);
+ }
+
+ void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
int[] userIds, int[] instantUserIds) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */,
@@ -366,7 +683,7 @@
* access all the packages in the extras.
*/
@Nullable
- public static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid,
+ private static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid,
@NonNull Bundle extras) {
if (UserHandle.isCore(callingUid)) {
// see all
@@ -392,7 +709,7 @@
}
/** Returns whether the Safety Label Change notification, a privacy feature, is enabled. */
- public static boolean isPrivacySafetyLabelChangeNotificationsEnabled(Context context) {
+ private static boolean isPrivacySafetyLabelChangeNotificationsEnabled(Context context) {
PackageManager packageManager = context.getPackageManager();
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, true)
@@ -424,4 +741,323 @@
pkgList.size() > 0 ? pkgList.toArray(new String[pkgList.size()]) : null,
uidList != null && uidList.size() > 0 ? uidList.toArray() : null);
}
+
+ void sendApplicationHiddenForUser(@NonNull String packageName,
+ @NonNull PackageStateInternal packageState,
+ int userId,
+ @NonNull PackageSender packageSender) {
+ final PackageRemovedInfo info = new PackageRemovedInfo();
+ info.mRemovedPackage = packageName;
+ info.mInstallerPackageName = packageState.getInstallSource().mInstallerPackageName;
+ info.mRemovedUsers = new int[] {userId};
+ info.mBroadcastUsers = new int[] {userId};
+ info.mUid = UserHandle.getUid(userId, packageState.getAppId());
+ info.mRemovedPackageVersionCode = packageState.getVersionCode();
+ sendPackageRemovedBroadcasts(info, packageSender, true /*killApp*/,
+ false /*removedBySystem*/, false /*isArchived*/);
+ }
+
+ void sendPackageChangedBroadcast(@NonNull Computer snapshot,
+ @NonNull String packageName,
+ boolean dontKillApp,
+ @NonNull ArrayList<String> componentNames,
+ int packageUid,
+ @NonNull String reason) {
+ PackageStateInternal setting = snapshot.getPackageStateInternal(packageName,
+ Process.SYSTEM_UID);
+ if (setting == null) {
+ return;
+ }
+ final int userId = UserHandle.getUserId(packageUid);
+ final boolean isInstantApp =
+ snapshot.isInstantAppInternal(packageName, userId, Process.SYSTEM_UID);
+ final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
+ final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
+ final SparseArray<int[]> broadcastAllowList =
+ isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds);
+ mHandler.post(() -> sendPackageChangedBroadcast(
+ packageName, dontKillApp, componentNames, packageUid, reason, userIds,
+ instantUserIds, broadcastAllowList));
+ mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
+ packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler);
+ }
+
+ private void sendPackageBroadcastAndNotify(@NonNull String action,
+ @NonNull String pkg,
+ @NonNull Bundle extras,
+ int flags,
+ @Nullable String targetPkg,
+ @Nullable IIntentReceiver finishedReceiver,
+ @NonNull int[] userIds,
+ @NonNull int[] instantUserIds,
+ @Nullable SparseArray<int[]> broadcastAllowList,
+ @Nullable Bundle bOptions) {
+ mHandler.post(() -> sendPackageBroadcast(action, pkg, extras, flags,
+ targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList,
+ null /* filterExtrasForReceiver */, bOptions));
+ if (targetPkg == null) {
+ // For some broadcast action, e.g. ACTION_PACKAGE_ADDED, this method will be called
+ // many times to different targets, e.g. installer app, permission controller, other
+ // registered apps. We should filter it to avoid calling back many times for the same
+ // action. When the targetPkg is set, it sends the broadcast to specific app, e.g.
+ // installer app or null for registered apps. The callback only need to send back to the
+ // registered apps so we check the null condition here.
+ notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList);
+ }
+ }
+
+ void sendSystemPackageUpdatedBroadcasts(@NonNull PackageRemovedInfo packageRemovedInfo) {
+ if (!packageRemovedInfo.mIsRemovedPackageSystemUpdate) {
+ return;
+ }
+
+ final String removedPackage = packageRemovedInfo.mRemovedPackage;
+ final int removedAppId = packageRemovedInfo.mRemovedAppId;
+ final int uid = packageRemovedInfo.mUid;
+ final String installerPackageName = packageRemovedInfo.mInstallerPackageName;
+ final SparseArray<int[]> broadcastAllowList = packageRemovedInfo.mBroadcastAllowList;
+
+ Bundle extras = new Bundle(2);
+ extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid);
+ extras.putBoolean(Intent.EXTRA_REPLACING, true);
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, removedPackage, extras,
+ 0, null /*targetPackage*/, null, null, null, broadcastAllowList, null);
+
+ if (installerPackageName != null) {
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED,
+ removedPackage, extras, 0 /*flags*/,
+ installerPackageName, null, null, null, null /* broadcastAllowList */,
+ null);
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED,
+ removedPackage, extras, 0 /*flags*/,
+ installerPackageName, null, null, null, null /* broadcastAllowList */,
+ null);
+ }
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED, removedPackage,
+ extras, 0, null /*targetPackage*/, null, null, null, broadcastAllowList, null);
+ sendPackageBroadcastAndNotify(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0,
+ removedPackage, null, null, null, null /* broadcastAllowList */,
+ getTemporaryBroadcastOptionsForSystemPackageUpdate(REASON_PACKAGE_REPLACED)
+ .toBundle());
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private @NonNull BroadcastOptions getTemporaryBroadcastOptionsForSystemPackageUpdate(
+ @PowerExemptionManager.ReasonCode int reasonCode) {
+ long duration = 10_000;
+ if (mAmInternal != null) {
+ duration = mAmInternal.getBootTimeTempAllowListDuration();
+ }
+ final BroadcastOptions bOptions = BroadcastOptions.makeBasic();
+ bOptions.setTemporaryAppAllowlist(duration,
+ TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ reasonCode, "");
+ return bOptions;
+ }
+
+
+ void sendPackageRemovedBroadcasts(
+ @NonNull PackageRemovedInfo packageRemovedInfo,
+ @NonNull PackageSender packageSender,
+ boolean killApp,
+ boolean removedBySystem,
+ boolean isArchived) {
+ final String removedPackage = packageRemovedInfo.mRemovedPackage;
+ final int removedAppId = packageRemovedInfo.mRemovedAppId;
+ final int uid = packageRemovedInfo.mUid;
+ final String installerPackageName = packageRemovedInfo.mInstallerPackageName;
+ final int[] broadcastUserIds = packageRemovedInfo.mBroadcastUsers;
+ final int[] instantUserIds = packageRemovedInfo.mInstantUserIds;
+ final SparseArray<int[]> broadcastAllowList = packageRemovedInfo.mBroadcastAllowList;
+ final boolean dataRemoved = packageRemovedInfo.mDataRemoved;
+ final boolean isUpdate = packageRemovedInfo.mIsUpdate;
+ final boolean isRemovedPackageSystemUpdate =
+ packageRemovedInfo.mIsRemovedPackageSystemUpdate;
+ final boolean isRemovedForAllUsers = packageRemovedInfo.mRemovedForAllUsers;
+ final boolean isStaticSharedLib = packageRemovedInfo.mIsStaticSharedLib;
+
+ Bundle extras = new Bundle();
+ final int removedUid = removedAppId >= 0 ? removedAppId : uid;
+ extras.putInt(Intent.EXTRA_UID, removedUid);
+ extras.putBoolean(Intent.EXTRA_DATA_REMOVED, dataRemoved);
+ extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, isRemovedPackageSystemUpdate);
+ extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
+ extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem);
+ final boolean isReplace = isUpdate || isRemovedPackageSystemUpdate;
+ if (isReplace || isArchived) {
+ extras.putBoolean(Intent.EXTRA_REPLACING, true);
+ }
+ if (isArchived) {
+ extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
+ }
+ extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, isRemovedForAllUsers);
+
+ // Send PACKAGE_REMOVED broadcast to the respective installer.
+ if (removedPackage != null && installerPackageName != null) {
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REMOVED,
+ removedPackage, extras, 0 /*flags*/,
+ installerPackageName, null, broadcastUserIds, instantUserIds, null, null);
+ }
+ if (isStaticSharedLib) {
+ // When uninstalling static shared libraries, only the package's installer needs to be
+ // sent a PACKAGE_REMOVED broadcast. There are no other intended recipients.
+ return;
+ }
+ if (removedPackage != null) {
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REMOVED,
+ removedPackage, extras, 0, null /*targetPackage*/, null,
+ broadcastUserIds, instantUserIds, broadcastAllowList, null);
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REMOVED_INTERNAL,
+ removedPackage, extras, 0 /*flags*/, PLATFORM_PACKAGE_NAME,
+ null /*finishedReceiver*/, broadcastUserIds, instantUserIds,
+ broadcastAllowList, null /*bOptions*/);
+ if (dataRemoved && !isRemovedPackageSystemUpdate) {
+ sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_FULLY_REMOVED,
+ removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null,
+ null, broadcastUserIds, instantUserIds, broadcastAllowList, null);
+ packageSender.notifyPackageRemoved(removedPackage, removedUid);
+ }
+ }
+ if (removedAppId >= 0) {
+ // If a system app's updates are uninstalled the UID is not actually removed. Some
+ // services need to know the package name affected.
+ if (isReplace) {
+ extras.putString(Intent.EXTRA_PACKAGE_NAME, removedPackage);
+ }
+
+ sendPackageBroadcastAndNotify(Intent.ACTION_UID_REMOVED,
+ null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
+ null, null, broadcastUserIds, instantUserIds, broadcastAllowList, null);
+ }
+ }
+
+ /**
+ * Send broadcast intents for packages suspension changes.
+ *
+ * @param intent The action name of the suspension intent.
+ * @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 sendPackagesSuspendedOrUnsuspendedForUser(@NonNull Computer snapshot,
+ @NonNull String intent,
+ @NonNull String[] pkgList,
+ @NonNull int[] uidList,
+ boolean quarantined,
+ int userId) {
+ final Bundle extras = new Bundle(3);
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+ if (quarantined) {
+ extras.putBoolean(Intent.EXTRA_QUARANTINED, true);
+ }
+ final int flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND;
+ final Bundle options = new BroadcastOptions()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+ .toBundle();
+ mHandler.post(() -> sendPackageBroadcast(intent, null /* pkg */,
+ extras, flags, null /* targetPkg */, null /* finishedReceiver */,
+ new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
+ (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+ snapshot, callingUid, intentExtras),
+ options));
+ notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
+ null /* instantUserIds */, null /* broadcastAllowList */);
+ }
+
+ void sendMyPackageSuspendedOrUnsuspended(@NonNull Computer snapshot,
+ @NonNull String[] affectedPackages,
+ boolean suspended,
+ int userId) {
+ final String action = suspended
+ ? Intent.ACTION_MY_PACKAGE_SUSPENDED
+ : Intent.ACTION_MY_PACKAGE_UNSUSPENDED;
+ mHandler.post(() -> {
+ final IActivityManager am = ActivityManager.getService();
+ if (am == null) {
+ Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ "
+ + (suspended ? "" : "UN") + "SUSPENDED broadcasts");
+ return;
+ }
+ final int[] targetUserIds = new int[] {userId};
+ for (String packageName : affectedPackages) {
+ final Bundle appExtras = suspended
+ ? SuspendPackageHelper.getSuspendedPackageAppExtras(
+ snapshot, packageName, userId, SYSTEM_UID)
+ : null;
+ final Bundle intentExtras;
+ if (appExtras != null) {
+ intentExtras = new Bundle(1);
+ intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras);
+ } else {
+ intentExtras = null;
+ }
+ doSendBroadcast(action, null, intentExtras,
+ Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
+ targetUserIds, false, null, null, null);
+ }
+ });
+ }
+
+ /**
+ * 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 Computer snapshot,
+ @NonNull String[] pkgList,
+ @NonNull int[] uidList,
+ int userId,
+ int distractionFlags) {
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+ extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
+
+ mHandler.post(() -> sendPackageBroadcast(
+ Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */,
+ extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+ null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+ null /* broadcastAllowList */,
+ (callingUid, intentExtras) -> filterExtrasChangedPackageList(
+ snapshot, callingUid, intentExtras),
+ null /* bOptions */));
+ }
+
+ void sendResourcesChangedBroadcastAndNotify(@NonNull Computer snapshot,
+ boolean mediaStatus,
+ boolean replacing,
+ @NonNull ArrayList<AndroidPackage> packages) {
+ final int size = packages.size();
+ final String[] packageNames = new String[size];
+ final int[] packageUids = new int[size];
+ for (int i = 0; i < size; i++) {
+ final AndroidPackage pkg = packages.get(i);
+ packageNames[i] = pkg.getPackageName();
+ packageUids[i] = pkg.getUid();
+ }
+ sendResourcesChangedBroadcast(snapshot, mediaStatus,
+ replacing, packageNames, packageUids);
+ notifyResourcesChanged(mediaStatus, replacing, packageNames, packageUids);
+ }
+
+ private void notifyPackageMonitor(@NonNull String action,
+ @NonNull String pkg,
+ @Nullable Bundle extras,
+ @NonNull int[] userIds,
+ @NonNull int[] instantUserIds,
+ @Nullable SparseArray<int[]> broadcastAllowList) {
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds,
+ instantUserIds, broadcastAllowList, mHandler);
+ }
+
+ private void notifyResourcesChanged(boolean mediaStatus,
+ boolean replacing,
+ @NonNull String[] pkgNames,
+ @NonNull int[] uids) {
+ mPackageMonitorCallbackHelper.notifyResourcesChanged(mediaStatus, replacing, pkgNames,
+ uids, mHandler);
+ }
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 83f90a1..8e767e7 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -63,7 +63,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -87,19 +86,16 @@
private final PackageManagerService mPm;
private final UserManagerInternal mUserManagerInternal;
- private final PermissionManagerServiceInternal mPermissionManager;
private final RemovePackageHelper mRemovePackageHelper;
+ private final BroadcastHelper mBroadcastHelper;
// TODO(b/198166813): remove PMS dependency
- DeletePackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper) {
+ DeletePackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper,
+ BroadcastHelper broadcastHelper) {
mPm = pm;
mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
- mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
mRemovePackageHelper = removePackageHelper;
- }
-
- DeletePackageHelper(PackageManagerService pm) {
- this(pm, new RemovePackageHelper(pm));
+ mBroadcastHelper = broadcastHelper;
}
/**
@@ -121,7 +117,7 @@
*/
public int deletePackageX(String packageName, long versionCode, int userId, int deleteFlags,
boolean removedBySystem) {
- final PackageRemovedInfo info = new PackageRemovedInfo(mPm);
+ final PackageRemovedInfo info = new PackageRemovedInfo();
final boolean res;
final int removeUser = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0
@@ -251,8 +247,9 @@
if (res) {
final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
final boolean isArchived = (deleteFlags & PackageManager.DELETE_ARCHIVE) != 0;
- info.sendPackageRemovedBroadcasts(killApp, removedBySystem, isArchived);
- info.sendSystemPackageUpdatedBroadcasts();
+ mBroadcastHelper.sendPackageRemovedBroadcasts(info, mPm, killApp,
+ removedBySystem, isArchived);
+ mBroadcastHelper.sendSystemPackageUpdatedBroadcasts(info);
PackageMetrics.onUninstallSucceeded(info, deleteFlags, removeUser);
}
@@ -314,7 +311,7 @@
Slog.i(TAG, "Enabling system stub after removal; pkg: "
+ stubPkg.getPackageName());
}
- new InstallPackageHelper(mPm).enableCompressedPackage(stubPkg, stubPs);
+ mPm.enableCompressedPackage(stubPkg, stubPs);
} else if (DEBUG_COMPRESSION) {
Slog.i(TAG, "System stub disabled for all users, leaving uncompressed "
+ "after removal; pkg: " + stubPkg.getPackageName());
@@ -491,8 +488,7 @@
// When an updated system application is deleted we delete the existing resources
// as well and fall back to existing code in system partition
deleteInstalledSystemPackage(action, allUserHandles, writeSettings);
- new InstallPackageHelper(mPm).restoreDisabledSystemPackageLIF(
- action, allUserHandles, writeSettings);
+ mPm.restoreDisabledSystemPackageLIF(action, allUserHandles, writeSettings);
} else {
if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.getPackageName());
if (ps.isIncremental()) {
diff --git a/services/core/java/com/android/server/pm/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
index 8ebb6ea..c5ec73b 100644
--- a/services/core/java/com/android/server/pm/DistractingPackageHelper.java
+++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
@@ -19,10 +19,7 @@
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;
@@ -42,17 +39,16 @@
// 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) {
+ DistractingPackageHelper(PackageManagerService pm,
+ BroadcastHelper broadcastHelper,
+ SuspendPackageHelper suspendPackageHelper) {
mPm = pm;
- mInjector = injector;
mBroadcastHelper = broadcastHelper;
mSuspendPackageHelper = suspendPackageHelper;
}
@@ -127,8 +123,8 @@
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(
new String[changedPackagesList.size()]);
- sendDistractingPackagesChanged(changedPackages, changedUids.toArray(), userId,
- restrictionFlags);
+ mBroadcastHelper.sendDistractingPackagesChanged(mPm.snapshotComputer(),
+ changedPackages, changedUids.toArray(), userId, restrictionFlags);
mPm.scheduleWritePackageRestrictions(userId);
}
return unactionedPackages.toArray(new String[0]);
@@ -202,34 +198,9 @@
if (!changedPackages.isEmpty()) {
final String[] packageArray = changedPackages.toArray(
new String[changedPackages.size()]);
- sendDistractingPackagesChanged(packageArray, changedUids.toArray(), userId,
- RESTRICTION_NONE);
+ mBroadcastHelper.sendDistractingPackagesChanged(mPm.snapshotComputer(),
+ 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();
- 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 /* broadcastAllowList */,
- (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
- mPm.snapshotComputer(), callingUid, intentExtras),
- null /* bOptions */));
- }
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index e1e5e6d..8f71a9b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -38,7 +38,6 @@
import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
-import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.os.incremental.IncrementalManager.isIncrementalPath;
import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
@@ -50,7 +49,6 @@
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
-import static com.android.server.pm.PackageManagerService.DEBUG_BACKUP;
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
@@ -130,7 +128,6 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
-import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Message;
@@ -143,9 +140,6 @@
import android.os.UserManager;
import android.os.incremental.IncrementalManager;
import android.os.incremental.IncrementalStorage;
-import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
-import android.stats.storage.StorageEnums;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
@@ -163,7 +157,6 @@
import com.android.internal.security.VerityUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
import com.android.server.LocalManagerRegistry;
import com.android.server.SystemConfig;
@@ -220,6 +213,7 @@
private final AppDataHelper mAppDataHelper;
private final BroadcastHelper mBroadcastHelper;
private final RemovePackageHelper mRemovePackageHelper;
+ private final DeletePackageHelper mDeletePackageHelper;
private final IncrementalManager mIncrementalManager;
private final ApexManager mApexManager;
private final DexManager mDexManager;
@@ -233,12 +227,17 @@
private final UpdateOwnershipHelper mUpdateOwnershipHelper;
// TODO(b/198166813): remove PMS dependency
- InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) {
+ InstallPackageHelper(PackageManagerService pm,
+ AppDataHelper appDataHelper,
+ RemovePackageHelper removePackageHelper,
+ DeletePackageHelper deletePackageHelper,
+ BroadcastHelper broadcastHelper) {
mPm = pm;
mInjector = pm.mInjector;
mAppDataHelper = appDataHelper;
- mBroadcastHelper = new BroadcastHelper(pm.mInjector);
- mRemovePackageHelper = new RemovePackageHelper(pm);
+ mBroadcastHelper = broadcastHelper;
+ mRemovePackageHelper = removePackageHelper;
+ mDeletePackageHelper = deletePackageHelper;
mIncrementalManager = pm.mInjector.getIncrementalManager();
mApexManager = pm.mInjector.getApexManager();
mDexManager = pm.mInjector.getDexManager();
@@ -251,10 +250,6 @@
mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
}
- InstallPackageHelper(PackageManagerService pm) {
- this(pm, new AppDataHelper(pm));
- }
-
/**
* Commits the package scan and modifies system state.
* <p><em>WARNING:</em> The method may throw an exception in the middle
@@ -263,7 +258,7 @@
* possible and the system is not left in an inconsistent state.
*/
@GuardedBy("mPm.mLock")
- public AndroidPackage commitReconciledScanResultLocked(
+ private AndroidPackage commitReconciledScanResultLocked(
@NonNull ReconciledPackage reconciledPkg, int[] allUsers) {
final InstallRequest request = reconciledPkg.mInstallRequest;
// TODO(b/135203078): Move this even further away
@@ -731,8 +726,9 @@
}
// TODO(b/278553670) Store archive state for the user.
boolean isArchived = (pkgSetting.getPkg() == null);
- mPm.sendPackageAddedForUser(mPm.snapshotComputer(), packageName, pkgSetting, userId,
- isArchived, DataLoaderType.NONE);
+ mBroadcastHelper.sendPackageAddedForUser(mPm.snapshotComputer(), packageName,
+ pkgSetting, userId, isArchived, DataLoaderType.NONE,
+ mPm.mAppPredictionServicePackage);
synchronized (mPm.mLock) {
mPm.updateSequenceNumberLP(pkgSetting, new int[]{ userId });
}
@@ -1745,7 +1741,7 @@
}
// Update what is removed
- PackageRemovedInfo removedInfo = new PackageRemovedInfo(mPm);
+ PackageRemovedInfo removedInfo = new PackageRemovedInfo();
removedInfo.mUid = ps.getAppId();
removedInfo.mRemovedPackage = ps.getPackageName();
removedInfo.mInstallerPackageName =
@@ -2074,8 +2070,6 @@
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
final ParsedPackage parsedPackage = installRequest.getParsedPackage();
final String packageName = parsedPackage.getPackageName();
- final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
- final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
installRequest.onCommitStarted();
if (installRequest.isInstallReplace()) {
@@ -2097,7 +2091,7 @@
allUsers, mPm.mSettings.getPackagesLocked());
if (installRequest.isInstallSystem()) {
// Remove existing system package
- removePackageHelper.removePackage(oldPackage, true);
+ mRemovePackageHelper.removePackage(oldPackage, true);
if (!disableSystemPackageLPw(oldPackage)) {
// We didn't need to disable the .apk as a current system package,
// which means we are replacing another update that is already
@@ -2113,7 +2107,7 @@
} else {
try {
// Settings will be written during the call to updateSettingsLI().
- deletePackageHelper.executeDeletePackage(
+ mDeletePackageHelper.executeDeletePackage(
reconciledPkg.mDeletePackageAction, packageName,
true, allUsers, false);
} catch (SystemDeleteException e) {
@@ -2200,12 +2194,19 @@
final String installerPackageName = installRequest.getInstallerPackageName();
if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath());
+ final int userId = installRequest.getUserId();
+ if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT
+ && !mPm.mUserManager.exists(userId)) {
+ installRequest.setError(PackageManagerException.ofInternalError(
+ "User " + userId + " doesn't exist or has been removed",
+ PackageManagerException.INTERNAL_ERROR_MISSING_USER));
+ return;
+ }
synchronized (mPm.mLock) {
// For system-bundled packages, we assume that installing an upgraded version
// of the package implies that the user actually wants to run that new code,
// so we enable the package.
final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
- final int userId = installRequest.getUserId();
if (ps != null) {
if (ps.isSystem()) {
if (DEBUG_INSTALL) {
@@ -2796,24 +2797,21 @@
final Computer snapshot = mPm.snapshotComputer();
// Send broadcasts
for (int i = 0; i < numBroadcasts; i++) {
- mPm.sendPackageChangedBroadcast(snapshot, packages[i], true /* dontKillApp */,
- components[i], uids[i], null /* reason */);
+ mBroadcastHelper.sendPackageChangedBroadcast(snapshot, packages[i],
+ true /* dontKillApp */, components[i], uids[i], null /* reason */);
}
}
void handlePackagePostInstall(InstallRequest request, boolean launchedForRestore) {
final boolean killApp =
(request.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) == 0;
- final boolean virtualPreload =
- ((request.getInstallFlags() & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
- final String installerPackage = request.getInstallerPackageName();
- final int dataLoaderType = request.getDataLoaderType();
final boolean succeeded = request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED;
final boolean update = request.isUpdate();
final boolean archived = request.isArchived();
final String packageName = request.getName();
+ final Computer snapshot = mPm.snapshotComputer();
final PackageStateInternal pkgSetting =
- succeeded ? mPm.snapshotComputer().getPackageStateInternal(packageName) : null;
+ succeeded ? snapshot.getPackageStateInternal(packageName) : null;
final boolean removedBeforeUpdate = (pkgSetting == null)
|| (pkgSetting.isSystem() && !pkgSetting.getPath().getPath().equals(
request.getPkg().getPath()));
@@ -2834,208 +2832,22 @@
// Clear the uid cache after we installed a new package.
mPm.mPerUidReadTimeoutsCache = null;
- // Send the removed broadcasts
- if (request.getRemovedInfo() != null) {
- if (request.getRemovedInfo().mIsExternal) {
- if (DEBUG_INSTALL) {
- Slog.i(TAG, "upgrading pkg " + request.getRemovedInfo().mRemovedPackage
- + " is ASEC-hosted -> UNAVAILABLE");
- }
- final String[] pkgNames = new String[]{
- request.getRemovedInfo().mRemovedPackage};
- final int[] uids = new int[]{request.getRemovedInfo().mUid};
- mPm.notifyResourcesChanged(false /* mediaStatus */,
- true /* replacing */, pkgNames, uids);
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
- false /* mediaStatus */, true /* replacing */, pkgNames, uids);
- }
- request.getRemovedInfo().sendPackageRemovedBroadcasts(
- killApp, false /*removedBySystem*/, false /*isArchived*/);
- }
-
- final String installerPackageName =
- request.getInstallerPackageName() != null
- ? request.getInstallerPackageName()
- : request.getRemovedInfo() != null
- ? request.getRemovedInfo().mInstallerPackageName
- : null;
-
mPm.notifyInstantAppPackageInstalled(request.getPkg().getPackageName(),
request.getNewUsers());
request.populateBroadcastUsers();
final int[] firstUserIds = request.getFirstTimeBroadcastUserIds();
- final int[] firstInstantUserIds = request.getFirstTimeBroadcastInstantUserIds();
- final int[] updateUserIds = request.getUpdateBroadcastUserIds();
- final int[] instantUserIds = request.getUpdateBroadcastInstantUserIds();
- Bundle extras = new Bundle();
- extras.putInt(Intent.EXTRA_UID, request.getAppId());
- if (update) {
- extras.putBoolean(Intent.EXTRA_REPLACING, true);
- }
- if (archived) {
- extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
- }
- extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
-
- // If a package is a static shared library, then only the installer of the package
- // should get the broadcast.
- if (installerPackageName != null
- && request.getPkg().getStaticSharedLibraryName() != null) {
- mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
- extras, 0 /*flags*/,
- installerPackageName, null /*finishedReceiver*/,
- request.getNewUsers(), null /* instantUserIds*/,
- null /* broadcastAllowList */, null);
- }
-
- // Send installed broadcasts if the package is not a static shared lib.
if (request.getPkg().getStaticSharedLibraryName() == null) {
mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath());
-
- // Send PACKAGE_ADDED broadcast for users that see the package for the first time
- // sendPackageAddedForNewUsers also deals with system apps
- int appId = UserHandle.getAppId(request.getAppId());
- boolean isSystem = request.isInstallSystem();
- mPm.sendPackageAddedForNewUsers(mPm.snapshotComputer(), packageName,
- isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId,
- firstUserIds, firstInstantUserIds, archived, dataLoaderType);
-
- // Send PACKAGE_ADDED broadcast for users that don't see
- // the package for the first time
-
- // Send to all running apps.
- final SparseArray<int[]> newBroadcastAllowList;
- synchronized (mPm.mLock) {
- final Computer snapshot = mPm.snapshotComputer();
- newBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(snapshot,
- snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
- updateUserIds, mPm.mSettings.getPackagesLocked());
- }
- mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
- extras, 0 /*flags*/,
- null /*targetPackage*/, null /*finishedReceiver*/,
- updateUserIds, instantUserIds, newBroadcastAllowList, null);
- // Send to the installer, even if it's not running.
- if (installerPackageName != null) {
- mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
- extras, 0 /*flags*/,
- installerPackageName, null /*finishedReceiver*/,
- updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
- }
- // Send to PermissionController for all update users, even if it may not be running
- // for some users
- if (BroadcastHelper.isPrivacySafetyLabelChangeNotificationsEnabled(mContext)) {
- mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
- extras, 0 /*flags*/,
- mPm.mRequiredPermissionControllerPackage, null /*finishedReceiver*/,
- updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
- }
- // Notify required verifier(s) that are not the installer of record for the package.
- for (String verifierPackageName : mPm.mRequiredVerifierPackages) {
- if (verifierPackageName != null && !verifierPackageName.equals(
- installerPackageName)) {
- mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
- extras, 0 /*flags*/,
- verifierPackageName, null /*finishedReceiver*/,
- updateUserIds, instantUserIds, null /* broadcastAllowList */,
- null);
- }
- }
- // If package installer is defined, notify package installer about new
- // app installed
- mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
- extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/,
- mPm.mRequiredInstallerPackage, null /*finishedReceiver*/,
- firstUserIds, instantUserIds, null /* broadcastAllowList */, null);
-
- // Send replaced for users that don't see the package for the first time
- if (update) {
- mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
- packageName, extras, 0 /*flags*/,
- null /*targetPackage*/, null /*finishedReceiver*/,
- updateUserIds, instantUserIds,
- request.getRemovedInfo().mBroadcastAllowList, null);
- if (installerPackageName != null) {
- mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
- extras, 0 /*flags*/,
- installerPackageName, null /*finishedReceiver*/,
- updateUserIds, instantUserIds, null /*broadcastAllowList*/,
- null);
- }
- for (String verifierPackageName : mPm.mRequiredVerifierPackages) {
- if (verifierPackageName != null && !verifierPackageName.equals(
- installerPackageName)) {
- mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
- packageName, extras, 0 /*flags*/, verifierPackageName,
- null /*finishedReceiver*/, updateUserIds, instantUserIds,
- null /*broadcastAllowList*/, null);
- }
- }
- mPm.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
- null /*package*/, null /*extras*/, 0 /*flags*/,
- packageName /*targetPackage*/,
- null /*finishedReceiver*/, updateUserIds, instantUserIds,
- null /*broadcastAllowList*/,
- mBroadcastHelper.getTemporaryAppAllowlistBroadcastOptions(
- REASON_PACKAGE_REPLACED).toBundle());
- } else if (launchedForRestore && !request.isInstallSystem()) {
- // First-install and we did a restore, so we're responsible for the
- // first-launch broadcast.
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "Post-restore of " + packageName
- + " sending FIRST_LAUNCH in " + Arrays.toString(firstUserIds));
- }
- mBroadcastHelper.sendFirstLaunchBroadcast(packageName, installerPackage,
- firstUserIds, firstInstantUserIds);
- }
-
- // Send broadcast package appeared if external for all users
- if (request.getPkg().isExternalStorage()) {
- if (!update) {
- final StorageManager storageManager =
- mInjector.getSystemService(StorageManager.class);
- VolumeInfo volume =
- storageManager.findVolumeByUuid(
- StorageManager.convert(
- request.getPkg().getVolumeUuid()).toString());
- int packageExternalStorageType =
- PackageManagerServiceUtils.getPackageExternalStorageType(volume,
- request.getPkg().isExternalStorage());
- // If the package was installed externally, log it.
- if (packageExternalStorageType != StorageEnums.UNKNOWN) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED,
- packageExternalStorageType, packageName);
- }
- }
- if (DEBUG_INSTALL) {
- Slog.i(TAG, "upgrading pkg " + request.getPkg() + " is external");
- }
- if (!archived) {
- final String[] pkgNames = new String[]{packageName};
- final int[] uids = new int[]{request.getPkg().getUid()};
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
- true /* mediaStatus */, true /* replacing */, pkgNames, uids);
- mPm.notifyResourcesChanged(true /* mediaStatus */, true /* replacing */,
- pkgNames, uids);
- }
- }
- } else if (!ArrayUtils.isEmpty(request.getLibraryConsumers())) { // if static shared lib
- // No need to kill consumers if it's installation of new version static shared lib.
- final Computer snapshot = mPm.snapshotComputer();
- final boolean dontKillApp = !update
- && request.getPkg().getStaticSharedLibraryName() != null;
- for (int i = 0; i < request.getLibraryConsumers().size(); i++) {
- AndroidPackage pkg = request.getLibraryConsumers().get(i);
- // send broadcast that all consumers of the static shared library have changed
- mPm.sendPackageChangedBroadcast(snapshot, pkg.getPackageName(), dontKillApp,
- new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
- pkg.getUid(), null);
- }
}
+ mBroadcastHelper.sendPostInstallBroadcasts(mPm.snapshotComputer(), request, packageName,
+ mPm.mRequiredPermissionControllerPackage, mPm.mRequiredVerifierPackages,
+ mPm.mRequiredInstallerPackage,
+ /* packageSender= */ mPm, launchedForRestore, killApp, update, archived);
+
+
// Work that needs to happen on first install within each user
if (firstUserIds.length > 0) {
for (int userId : firstUserIds) {
@@ -3074,7 +2886,6 @@
}
if (!archived) {
- final Computer snapshot = mPm.snapshotComputer();
// Notify DexManager that the package was installed for new users.
// The updated users should already be indexed and the package code paths
// should not change.
@@ -3090,7 +2901,7 @@
}
} else {
// Now send PACKAGE_REMOVED + EXTRA_REPLACING broadcast.
- final PackageRemovedInfo info = new PackageRemovedInfo(mPm);
+ final PackageRemovedInfo info = new PackageRemovedInfo();
info.mRemovedPackage = packageName;
info.mInstallerPackageName = request.getInstallerPackageName();
info.mRemovedUsers = firstUserIds;
@@ -3099,8 +2910,8 @@
info.mRemovedPackageVersionCode = request.getPkg().getLongVersionCode();
info.mRemovedForAllUsers = true;
- info.sendPackageRemovedBroadcasts(false /*killApp*/,
- false /*removedBySystem*/, true /*isArchived*/);
+ mBroadcastHelper.sendPackageRemovedBroadcasts(info, mPm,
+ false /*killApp*/, false /*removedBySystem*/, true /*isArchived*/);
}
}
@@ -3291,15 +3102,14 @@
synchronized (mPm.mLock) {
mPm.mSettings.disableSystemPackageLPw(stubPkg.getPackageName(), true /*replaced*/);
}
- final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
- removePackageHelper.removePackage(stubPkg, true /*chatty*/);
+ mRemovePackageHelper.removePackage(stubPkg, true /*chatty*/);
try {
return initPackageTracedLI(scanFile, parseFlags, scanFlags);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
e);
// Remove the failed install
- removePackageHelper.removeCodePath(scanFile);
+ mRemovePackageHelper.removeCodePath(scanFile);
throw e;
}
}
@@ -3342,7 +3152,7 @@
if (!dstCodePath.exists()) {
return null;
}
- new RemovePackageHelper(mPm).removeCodePath(dstCodePath);
+ mRemovePackageHelper.removeCodePath(dstCodePath);
return null;
}
@@ -4298,8 +4108,8 @@
parsedPackage.getPackageName(), UserHandle.USER_ALL,
"scanPackageInternalLI", ApplicationExitInfo.REASON_OTHER,
null /* request */)) {
- DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
- deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true,
+ mDeletePackageHelper.deletePackageLIF(
+ parsedPackage.getPackageName(), null, true,
mPm.mUserManager.getUserIds(), 0, null, false);
}
} else if (newPkgVersionGreater || newSharedUserSetting) {
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index fe6a8a1..ca8dc29 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -94,8 +94,6 @@
private final UserHandle mUser;
@NonNull
final PackageManagerService mPm;
- final InstallPackageHelper mInstallPackageHelper;
- final RemovePackageHelper mRemovePackageHelper;
final boolean mIsInherit;
final int mSessionId;
final int mRequireUserAction;
@@ -108,8 +106,6 @@
PackageLite packageLite, PackageManagerService pm) {
mPm = pm;
mUser = user;
- mInstallPackageHelper = new InstallPackageHelper(mPm);
- mRemovePackageHelper = new RemovePackageHelper(mPm);
mOriginInfo = originInfo;
mMoveInfo = moveInfo;
mObserver = observer;
@@ -142,8 +138,6 @@
PackageLite packageLite, PackageManagerService pm) {
mPm = pm;
mUser = user;
- mInstallPackageHelper = new InstallPackageHelper(mPm);
- mRemovePackageHelper = new RemovePackageHelper(mPm);
mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
mMoveInfo = null;
mInstallReason = fixUpInstallReason(
@@ -242,7 +236,7 @@
// state can change within this delay and hence we need to re-verify certain conditions.
boolean isStaged = (mInstallFlags & INSTALL_STAGED) != 0;
if (isStaged) {
- Pair<Integer, String> ret = mInstallPackageHelper.verifyReplacingVersionCode(
+ Pair<Integer, String> ret = mPm.verifyReplacingVersionCode(
pkgLite, mRequiredInstalledVersionCode, mInstallFlags);
mRet = ret.first;
if (mRet != INSTALL_SUCCEEDED) {
@@ -540,39 +534,39 @@
}
}
} else {
- mInstallPackageHelper.installPackagesTraced(installRequests);
+ mPm.installPackagesTraced(installRequests);
for (InstallRequest request : installRequests) {
doPostInstall(request);
}
}
for (InstallRequest request : installRequests) {
- mInstallPackageHelper.restoreAndPostInstall(request);
+ mPm.restoreAndPostInstall(request);
}
}
private void doPostInstall(InstallRequest request) {
if (mMoveInfo != null) {
if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
- mRemovePackageHelper.cleanUpForMoveInstall(mMoveInfo.mFromUuid,
+ mPm.cleanUpForMoveInstall(mMoveInfo.mFromUuid,
mMoveInfo.mPackageName, mMoveInfo.mFromCodePath);
} else {
- mRemovePackageHelper.cleanUpForMoveInstall(mMoveInfo.mToUuid,
+ mPm.cleanUpForMoveInstall(mMoveInfo.mToUuid,
mMoveInfo.mPackageName, mMoveInfo.mFromCodePath);
}
} else {
if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
- mRemovePackageHelper.removeCodePath(request.getCodeFile());
+ mPm.removeCodePath(request.getCodeFile());
}
}
}
private void cleanUpForFailedInstall(InstallRequest request) {
if (request.isInstallMove()) {
- mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(),
+ mPm.cleanUpForMoveInstall(request.getMoveToUuid(),
request.getMovePackageName(), request.getMoveFromCodePath());
} else {
- mRemovePackageHelper.removeCodePath(request.getCodeFile());
+ mPm.removeCodePath(request.getCodeFile());
}
}
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index b4ca477..4ecbd15 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -60,14 +60,10 @@
*/
final class PackageHandler extends Handler {
private final PackageManagerService mPm;
- private final InstallPackageHelper mInstallPackageHelper;
- private final RemovePackageHelper mRemovePackageHelper;
PackageHandler(Looper looper, PackageManagerService pm) {
super(looper);
mPm = pm;
- mInstallPackageHelper = new InstallPackageHelper(mPm);
- mRemovePackageHelper = new RemovePackageHelper(mPm);
}
@Override
@@ -82,7 +78,7 @@
void doHandleMessage(Message msg) {
switch (msg.what) {
case SEND_PENDING_BROADCAST: {
- mInstallPackageHelper.sendPendingBroadcasts();
+ mPm.sendPendingBroadcasts();
break;
}
case POST_INSTALL: {
@@ -96,7 +92,7 @@
request.onInstallCompleted();
request.runPostInstallRunnable();
if (!request.isInstallExistingForUser()) {
- mInstallPackageHelper.handlePackagePostInstall(request, didRestore);
+ mPm.handlePackagePostInstall(request, didRestore);
} else if (DEBUG_INSTALL) {
// No post-install when we run restore from installExistingPackageForUser
Slog.i(TAG, "Nothing to do for post-install token " + msg.arg1);
@@ -107,7 +103,7 @@
case DEFERRED_NO_KILL_POST_DELETE: {
InstallArgs args = (InstallArgs) msg.obj;
if (args != null) {
- mRemovePackageHelper.cleanUpResources(args.mCodeFile, args.mInstructionSets);
+ mPm.cleanUpResources(args.mCodeFile, args.mInstructionSets);
}
} break;
case DEFERRED_NO_KILL_INSTALL_OBSERVER:
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 95b565d..1bb20b47 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -18,7 +18,9 @@
import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
import static android.os.Process.INVALID_UID;
+
import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
+
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -407,11 +409,10 @@
}
private void removeStagingDirs(ArraySet<File> stagingDirsToRemove) {
- final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
// Clean up orphaned staging directories
for (File stage : stagingDirsToRemove) {
Slog.w(TAG, "Deleting orphan stage " + stage);
- removePackageHelper.removeCodePath(stage);
+ mPm.removeCodePath(stage);
}
}
@@ -1320,9 +1321,8 @@
@Override
public void installExistingPackage(String packageName, int installFlags, int installReason,
IntentSender statusReceiver, int userId, List<String> allowListedPermissions) {
- final InstallPackageHelper installPackageHelper = new InstallPackageHelper(mPm);
- var result = installPackageHelper.installExistingPackageAsUser(packageName, userId,
+ var result = mPm.installExistingPackageAsUser(packageName, userId,
installFlags, installReason, allowListedPermissions, statusReceiver);
int returnCode = result.first;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 54a2e3ad..d0e5f96 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2449,14 +2449,13 @@
}
private void onSystemDataLoaderUnrecoverable() {
- final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
final String packageName = getPackageName();
if (TextUtils.isEmpty(packageName)) {
// The package has not been installed.
return;
}
mHandler.post(() -> {
- if (deletePackageHelper.deletePackageX(packageName,
+ if (mPm.deletePackageX(packageName,
PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/)
!= PackageManager.DELETE_SUCCEEDED) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerException.java b/services/core/java/com/android/server/pm/PackageManagerException.java
index dea6659..d69737a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerException.java
+++ b/services/core/java/com/android/server/pm/PackageManagerException.java
@@ -63,6 +63,7 @@
public static final int INTERNAL_ERROR_STATIC_SHARED_LIB_OVERLAY_TARGETS = -35;
public static final int INTERNAL_ERROR_APEX_NOT_DIRECTORY = -36;
public static final int INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE = -37;
+ public static final int INTERNAL_ERROR_MISSING_USER = -38;
@IntDef(prefix = { "INTERNAL_ERROR_" }, value = {
INTERNAL_ERROR_NATIVE_LIBRARY_COPY,
@@ -101,7 +102,8 @@
INTERNAL_ERROR_STATIC_SHARED_LIB_PROTECTED_BROADCAST,
INTERNAL_ERROR_STATIC_SHARED_LIB_OVERLAY_TARGETS,
INTERNAL_ERROR_APEX_NOT_DIRECTORY,
- INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE
+ INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE,
+ INTERNAL_ERROR_MISSING_USER
})
@Retention(RetentionPolicy.SOURCE)
public @interface InternalErrorCode {}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 33cb85c..ddc8369 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -70,7 +70,6 @@
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -98,6 +97,7 @@
import android.content.pm.InstantAppRequest;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ComponentEnabledSetting;
@@ -115,6 +115,7 @@
import android.content.pm.UserInfo;
import android.content.pm.UserPackage;
import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VerifierInfo;
import android.content.pm.VersionedPackage;
import android.content.pm.overlay.OverlayPaths;
import android.content.pm.parsing.PackageLite;
@@ -985,7 +986,7 @@
private final DeletePackageHelper mDeletePackageHelper;
private final InitAppsHelper mInitAppsHelper;
private final AppDataHelper mAppDataHelper;
- private final InstallPackageHelper mInstallPackageHelper;
+ @NonNull private final InstallPackageHelper mInstallPackageHelper;
private final PreferredActivityHelper mPreferredActivityHelper;
private final ResolveIntentHelper mResolveIntentHelper;
private final DexOptHelper mDexOptHelper;
@@ -1715,7 +1716,8 @@
(i, pm) -> new CrossProfileIntentFilterHelper(i.getSettings(),
i.getUserManagerService(), i.getLock(), i.getUserManagerInternal(),
context),
- (i, pm) -> new UpdateOwnershipHelper());
+ (i, pm) -> new UpdateOwnershipHelper(),
+ (i, pm) -> new PackageMonitorCallbackHelper());
if (Build.VERSION.SDK_INT <= 0) {
Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -2067,17 +2069,19 @@
mDomainVerificationManager.setConnection(mDomainVerificationConnection);
mBroadcastHelper = new BroadcastHelper(mInjector);
- mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper(mInjector);
+ mPackageMonitorCallbackHelper = injector.getPackageMonitorCallbackHelper();
mAppDataHelper = new AppDataHelper(this);
- mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
- mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
- mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper);
+ mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper, mBroadcastHelper);
+ mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
+ mBroadcastHelper);
+ mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper, mRemovePackageHelper,
+ mDeletePackageHelper, mBroadcastHelper);
mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
mInjector.getUserManagerInternal(), mDeletePackageHelper);
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
- mPreferredActivityHelper = new PreferredActivityHelper(this);
+ mPreferredActivityHelper = new PreferredActivityHelper(this, mBroadcastHelper);
mResolveIntentHelper = new ResolveIntentHelper(mContext, mPreferredActivityHelper,
injector.getCompatibility(), mUserManager, mDomainVerificationManager,
mUserNeedsBadging, () -> mResolveInfo, () -> mInstantAppInstallerActivity,
@@ -2085,7 +2089,7 @@
mDexOptHelper = new DexOptHelper(this);
mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mUserManager,
mBroadcastHelper, mProtectedPackages);
- mDistractingPackageHelper = new DistractingPackageHelper(this, mInjector, mBroadcastHelper,
+ mDistractingPackageHelper = new DistractingPackageHelper(this, mBroadcastHelper,
mSuspendPackageHelper);
mStorageEventHelper = new StorageEventHelper(this, mDeletePackageHelper,
mRemovePackageHelper);
@@ -3078,38 +3082,6 @@
}
@Override
- public void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
- final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
- final int[] userIds, int[] instantUserIds,
- @Nullable SparseArray<int[]> broadcastAllowList,
- @Nullable Bundle bOptions) {
- mHandler.post(() -> mBroadcastHelper.sendPackageBroadcast(action, pkg, extras, flags,
- targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList,
- null /* filterExtrasForReceiver */, bOptions));
- if (targetPkg == null) {
- // For some broadcast action, e.g. ACTION_PACKAGE_ADDED, this method will be called
- // many times to different targets, e.g. installer app, permission controller, other
- // registered apps. We should filter it to avoid calling back many times for the same
- // action. When the targetPkg is set, it sends the broadcast to specific app, e.g.
- // installer app or null for registered apps. The callback only need to send back to the
- // registered apps so we check the null condition here.
- notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList);
- }
- }
-
- void notifyPackageMonitor(String action, String pkg, Bundle extras, int[] userIds,
- int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
- mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds,
- instantUserIds, broadcastAllowList);
- }
-
- void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
- @NonNull String[] pkgNames, @NonNull int[] uids) {
- mPackageMonitorCallbackHelper.notifyResourcesChanged(mediaStatus, replacing, pkgNames,
- uids);
- }
-
- @Override
public void notifyPackageAdded(String packageName, int uid) {
mPackageObserverHelper.notifyAdded(packageName, uid);
}
@@ -3125,64 +3097,6 @@
UserPackage.removeFromCache(UserHandle.getUserId(uid), packageName);
}
- void sendPackageAddedForUser(@NonNull Computer snapshot, String packageName,
- @NonNull PackageStateInternal packageState, int userId, boolean isArchived,
- int dataLoaderType) {
- final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
- final boolean isSystem = packageState.isSystem();
- final boolean isInstantApp = userState.isInstantApp();
- final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
- final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
- sendPackageAddedForNewUsers(snapshot, packageName, isSystem /*sendBootCompleted*/,
- false /*startReceiver*/, packageState.getAppId(), userIds, instantUserIds,
- isArchived, dataLoaderType);
-
- // Send a session commit broadcast
- final PackageInstaller.SessionInfo info = new PackageInstaller.SessionInfo();
- info.installReason = userState.getInstallReason();
- info.appPackageName = packageName;
- sendSessionCommitBroadcast(info, userId);
- }
-
- @Override
- public void sendPackageAddedForNewUsers(@NonNull Computer snapshot, String packageName,
- boolean sendBootCompleted, boolean includeStopped, @AppIdInt int appId, int[] userIds,
- int[] instantUserIds, boolean isArchived, int dataLoaderType) {
- if (ArrayUtils.isEmpty(userIds) && ArrayUtils.isEmpty(instantUserIds)) {
- return;
- }
- SparseArray<int[]> broadcastAllowList = mAppsFilter.getVisibilityAllowList(snapshot,
- snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
- userIds, snapshot.getPackageStates());
- mHandler.post(
- () -> mBroadcastHelper.sendPackageAddedForNewUsers(packageName, appId, userIds,
- instantUserIds, isArchived, dataLoaderType, broadcastAllowList));
- mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(packageName, appId, userIds,
- instantUserIds, isArchived, dataLoaderType, broadcastAllowList);
- if (sendBootCompleted && !ArrayUtils.isEmpty(userIds)) {
- mHandler.post(() -> {
- for (int userId : userIds) {
- mBroadcastHelper.sendBootCompletedBroadcastToSystemApp(
- packageName, includeStopped, userId);
- }
- }
- );
- }
- }
-
- private void sendApplicationHiddenForUser(String packageName, PackageStateInternal packageState,
- int userId) {
- final PackageRemovedInfo info = new PackageRemovedInfo(this);
- info.mRemovedPackage = packageName;
- info.mInstallerPackageName = packageState.getInstallSource().mInstallerPackageName;
- info.mRemovedUsers = new int[] {userId};
- info.mBroadcastUsers = new int[] {userId};
- info.mUid = UserHandle.getUid(userId, packageState.getAppId());
- info.mRemovedPackageVersionCode = packageState.getVersionCode();
- info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/,
- false /*isArchived*/);
- }
-
boolean isUserRestricted(int userId, String restrictionKey) {
Bundle restrictions = mUserManager.getUserRestrictions(userId);
if (restrictions.getBoolean(restrictionKey, false)) {
@@ -3505,11 +3419,6 @@
}
}
- void postPreferredActivityChangedBroadcast(int userId) {
- mHandler.post(() -> mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId));
- }
-
-
/** This method takes a specific user id as well as UserHandle.USER_ALL. */
@GuardedBy("mLock")
void clearPackagePreferredActivitiesLPw(String packageName,
@@ -3609,17 +3518,8 @@
}
public void sendSessionCommitBroadcast(PackageInstaller.SessionInfo sessionInfo, int userId) {
- UserManagerService ums = UserManagerService.getInstance();
- if (ums == null || sessionInfo.isStaged()) {
- return;
- }
- final UserInfo parent = ums.getProfileParent(userId);
- final int launcherUid = (parent != null) ? parent.id : userId;
- // TODO: Should this snapshot be moved further up?
- final ComponentName launcherComponent = snapshotComputer()
- .getDefaultHomeActivity(launcherUid);
- mBroadcastHelper.sendSessionCommitBroadcast(sessionInfo, userId, launcherUid,
- launcherComponent, mAppPredictionServicePackage);
+ mBroadcastHelper.sendSessionCommitBroadcast(snapshotComputer(), sessionInfo, userId,
+ mAppPredictionServicePackage);
}
private @Nullable String getSetupWizardPackageNameImpl(@NonNull Computer computer) {
@@ -3988,7 +3888,7 @@
if (isSystemStub
&& (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
- if (!mInstallPackageHelper.enableCompressedPackage(deletedPkg, pkgSetting)) {
+ if (!enableCompressedPackage(deletedPkg, pkgSetting)) {
Slog.w(TAG, "Failed setApplicationEnabledSetting: failed to enable "
+ "commpressed package " + setting.getPackageName());
updateAllowed[i] = false;
@@ -4070,8 +3970,8 @@
final ArrayList<String> components = sendNowBroadcasts.valueAt(i);
final int packageUid = UserHandle.getUid(
userId, pkgSettings.get(packageName).getAppId());
- sendPackageChangedBroadcast(newSnapshot, packageName, false /* dontKillApp */,
- components, packageUid, null /* reason */);
+ mBroadcastHelper.sendPackageChangedBroadcast(newSnapshot, packageName,
+ false /* dontKillApp */, components, packageUid, null /* reason */);
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -4147,27 +4047,6 @@
}
}
- void sendPackageChangedBroadcast(@NonNull Computer snapshot, String packageName,
- boolean dontKillApp, ArrayList<String> componentNames, int packageUid, String reason) {
- PackageStateInternal setting = snapshot.getPackageStateInternal(packageName,
- Process.SYSTEM_UID);
- if (setting == null) {
- return;
- }
- final int userId = UserHandle.getUserId(packageUid);
- final boolean isInstantApp =
- snapshot.isInstantAppInternal(packageName, userId, Process.SYSTEM_UID);
- final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
- final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
- final SparseArray<int[]> broadcastAllowList =
- isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds);
- mHandler.post(() -> mBroadcastHelper.sendPackageChangedBroadcast(
- packageName, dontKillApp, componentNames, packageUid, reason, userIds,
- instantUserIds, broadcastAllowList));
- mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
- packageUid, reason, userIds, instantUserIds, broadcastAllowList);
- }
-
/**
* Used by SystemServer
*/
@@ -4312,7 +4191,7 @@
if (pkg == null) {
return;
}
- sendPackageChangedBroadcast(snapshot, pkg.getPackageName(),
+ mBroadcastHelper.sendPackageChangedBroadcast(snapshot, pkg.getPackageName(),
true /* dontKillApp */,
new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
pkg.getUid(),
@@ -5294,7 +5173,7 @@
throw new SecurityException("Calling package " + packageName
+ " does not belong to calling uid " + callingUid);
}
- return mSuspendPackageHelper
+ return SuspendPackageHelper
.getSuspendedPackageAppExtras(snapshot, packageName, userId, callingUid);
}
@@ -5857,10 +5736,14 @@
if (hidden) {
killApplication(packageName, newPackageState.getAppId(), userId, "hiding pkg",
ApplicationExitInfo.REASON_OTHER);
- sendApplicationHiddenForUser(packageName, newPackageState, userId);
+ mBroadcastHelper.sendApplicationHiddenForUser(
+ packageName, newPackageState, userId,
+ /* packageSender= */ PackageManagerService.this);
} else {
- sendPackageAddedForUser(newSnapshot, packageName, newPackageState, userId,
- false /* isArchived */, DataLoaderType.NONE);
+ mBroadcastHelper.sendPackageAddedForUser(
+ newSnapshot, packageName, newPackageState, userId,
+ false /* isArchived */, DataLoaderType.NONE,
+ mAppPredictionServicePackage);
}
scheduleWritePackageRestrictions(userId);
@@ -7929,4 +7812,75 @@
}
}
}
+
+ void removeCodePath(@Nullable File codePath) {
+ mRemovePackageHelper.removeCodePath(codePath);
+ }
+
+ void cleanUpResources(@Nullable File codeFile, @Nullable String[] instructionSets) {
+ mRemovePackageHelper.cleanUpResources(codeFile, instructionSets);
+ }
+
+ void cleanUpForMoveInstall(String volumeUuid, String packageName, String fromCodePath) {
+ mRemovePackageHelper.cleanUpForMoveInstall(volumeUuid, packageName, fromCodePath);
+ }
+
+ void sendPendingBroadcasts() {
+ mInstallPackageHelper.sendPendingBroadcasts();
+ }
+
+ void handlePackagePostInstall(@NonNull InstallRequest request, boolean launchedForRestore) {
+ mInstallPackageHelper.handlePackagePostInstall(request, launchedForRestore);
+ }
+
+ Pair<Integer, IntentSender> installExistingPackageAsUser(
+ @Nullable String packageName,
+ @UserIdInt int userId, @PackageManager.InstallFlags int installFlags,
+ @PackageManager.InstallReason int installReason,
+ @Nullable List<String> allowlistedRestrictedPermissions,
+ @Nullable IntentSender intentSender) {
+ return mInstallPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
+ installReason, allowlistedRestrictedPermissions, intentSender);
+ }
+ AndroidPackage initPackageTracedLI(File scanFile, final int parseFlags, int scanFlags)
+ throws PackageManagerException {
+ return mInstallPackageHelper.initPackageTracedLI(scanFile, parseFlags, scanFlags);
+ }
+
+ void restoreDisabledSystemPackageLIF(@NonNull DeletePackageAction action,
+ @NonNull int[] allUserHandles,
+ boolean writeSettings) throws SystemDeleteException {
+ mInstallPackageHelper.restoreDisabledSystemPackageLIF(
+ action, allUserHandles, writeSettings);
+ }
+ boolean enableCompressedPackage(@NonNull AndroidPackage stubPkg,
+ @NonNull PackageSetting stubPs) {
+ return mInstallPackageHelper.enableCompressedPackage(stubPkg, stubPs);
+ }
+
+ void installPackagesTraced(List<InstallRequest> requests) {
+ mInstallPackageHelper.installPackagesTraced(requests);
+ }
+
+ void restoreAndPostInstall(InstallRequest request) {
+ mInstallPackageHelper.restoreAndPostInstall(request);
+ }
+
+ Pair<Integer, String> verifyReplacingVersionCode(@NonNull PackageInfoLite pkgLite,
+ long requiredInstalledVersionCode,
+ int installFlags) {
+ return mInstallPackageHelper.verifyReplacingVersionCode(
+ pkgLite, requiredInstalledVersionCode, installFlags);
+ }
+
+ int getUidForVerifier(VerifierInfo verifierInfo) {
+ return mInstallPackageHelper.getUidForVerifier(verifierInfo);
+ }
+
+ int deletePackageX(String packageName, long versionCode, int userId, int deleteFlags,
+ boolean removedBySystem) {
+ return mDeletePackageHelper.deletePackageX(packageName,
+ PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
+ PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/);
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 0c2e082..5b770aab 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -146,6 +146,7 @@
private final Singleton<SharedLibrariesImpl> mSharedLibrariesProducer;
private final Singleton<CrossProfileIntentFilterHelper> mCrossProfileIntentFilterHelperProducer;
private final Singleton<UpdateOwnershipHelper> mUpdateOwnershipHelperProducer;
+ private final Singleton<PackageMonitorCallbackHelper> mPackageMonitorCallbackHelper;
PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock,
Installer installer, Object installLock, PackageAbiHelper abiHelper,
@@ -186,7 +187,8 @@
Producer<IBackupManager> iBackupManager,
Producer<SharedLibrariesImpl> sharedLibrariesProducer,
Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer,
- Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer) {
+ Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer,
+ Producer<PackageMonitorCallbackHelper> packageMonitorCallbackHelper) {
mContext = context;
mLock = lock;
mInstaller = installer;
@@ -242,6 +244,7 @@
mCrossProfileIntentFilterHelperProducer = new Singleton<>(
crossProfileIntentFilterHelperProducer);
mUpdateOwnershipHelperProducer = new Singleton<>(updateOwnershipHelperProducer);
+ mPackageMonitorCallbackHelper = new Singleton<>(packageMonitorCallbackHelper);
}
/**
@@ -431,6 +434,10 @@
return mUpdateOwnershipHelperProducer.get(this, mPackageManager);
}
+ public PackageMonitorCallbackHelper getPackageMonitorCallbackHelper() {
+ return mPackageMonitorCallbackHelper.get(this, mPackageManager);
+ }
+
/** Provides an abstraction to static access to system state. */
public interface SystemWrapper {
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index bb3bf53..b8c2b86 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -52,12 +52,6 @@
private final Object mLock = new Object();
final IActivityManager mActivityManager = ActivityManager.getService();
- final Handler mHandler;
-
- PackageMonitorCallbackHelper(PackageManagerServiceInjector injector) {
- mHandler = injector.getHandler();
- }
-
@NonNull
@GuardedBy("mLock")
private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>();
@@ -100,7 +94,8 @@
public void notifyPackageAddedForNewUsers(String packageName,
@AppIdInt int appId, @NonNull int[] userIds, @NonNull int[] instantUserIds,
- boolean isArchived, int dataLoaderType, SparseArray<int[]> broadcastAllowList) {
+ boolean isArchived, int dataLoaderType, SparseArray<int[]> broadcastAllowList,
+ @NonNull Handler handler) {
Bundle extras = new Bundle(2);
// Set to UID of the first user, EXTRA_UID is automatically updated in sendPackageBroadcast
final int uid = UserHandle.getUid(
@@ -111,11 +106,11 @@
}
extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras ,
- userIds /* userIds */, instantUserIds, broadcastAllowList);
+ userIds /* userIds */, instantUserIds, broadcastAllowList, handler);
}
public void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
- @NonNull String[] pkgNames, @NonNull int[] uids) {
+ @NonNull String[] pkgNames, @NonNull int[] uids, @NonNull Handler handler) {
Bundle extras = new Bundle();
extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgNames);
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids);
@@ -125,12 +120,12 @@
String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
: Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */);
+ null /* instantUserIds */, null /* broadcastAllowList */, handler);
}
public void notifyPackageChanged(String packageName, boolean dontKillApp,
ArrayList<String> componentNames, int packageUid, String reason, int[] userIds,
- int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
+ int[] instantUserIds, SparseArray<int[]> broadcastAllowList, Handler handler) {
Bundle extras = new Bundle(4);
extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0));
String[] nameList = new String[componentNames.size()];
@@ -142,11 +137,12 @@
extras.putString(Intent.EXTRA_REASON, reason);
}
notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds,
- instantUserIds, broadcastAllowList);
+ instantUserIds, broadcastAllowList, handler);
}
public void notifyPackageMonitor(String action, String pkg, Bundle extras,
- int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
+ int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList,
+ Handler handler) {
if (!isAllowedCallbackAction(action)) {
return;
}
@@ -160,9 +156,10 @@
}
if (ArrayUtils.isEmpty(instantUserIds)) {
- doNotifyCallbacks(action, pkg, extras, resolvedUserIds, broadcastAllowList);
+ doNotifyCallbacks(
+ action, pkg, extras, resolvedUserIds, broadcastAllowList, handler);
} else {
- doNotifyCallbacks(action, pkg, extras, instantUserIds, broadcastAllowList);
+ doNotifyCallbacks(action, pkg, extras, instantUserIds, broadcastAllowList, handler);
}
} catch (RemoteException e) {
// do nothing
@@ -181,7 +178,7 @@
}
private void doNotifyCallbacks(String action, String pkg, Bundle extras, int[] userIds,
- SparseArray<int[]> broadcastAllowList) {
+ SparseArray<int[]> broadcastAllowList, Handler handler) {
RemoteCallbackList<IRemoteCallback> callbacks;
synchronized (mLock) {
callbacks = mCallbacks;
@@ -202,7 +199,7 @@
final int[] allowUids =
broadcastAllowList != null ? broadcastAllowList.get(userId) : new int[]{};
- mHandler.post(() -> callbacks.broadcast((callback, user) -> {
+ handler.post(() -> callbacks.broadcast((callback, user) -> {
RegisterUser registerUser = (RegisterUser) user;
if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
!= userId)) {
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 9f02542..7ee1772 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -16,24 +16,12 @@
package com.android.server.pm;
-import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED;
-import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-
-import android.annotation.NonNull;
-import android.app.ActivityManagerInternal;
-import android.app.BroadcastOptions;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.PowerExemptionManager;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.util.ArrayUtils;
-import com.android.server.LocalServices;
final class PackageRemovedInfo {
- final PackageSender mPackageSender;
String mRemovedPackage;
String mInstallerPackageName;
int mUid = -1;
@@ -58,116 +46,6 @@
InstallArgs mArgs = null;
private static final int[] EMPTY_INT_ARRAY = new int[0];
- PackageRemovedInfo(PackageSender packageSender) {
- mPackageSender = packageSender;
- }
-
- void sendPackageRemovedBroadcasts(boolean killApp, boolean removedBySystem,
- boolean isArchived) {
- sendPackageRemovedBroadcastInternal(killApp, removedBySystem, isArchived);
- }
-
- void sendSystemPackageUpdatedBroadcasts() {
- if (mIsRemovedPackageSystemUpdate) {
- sendSystemPackageUpdatedBroadcastsInternal();
- }
- }
-
- private void sendSystemPackageUpdatedBroadcastsInternal() {
- Bundle extras = new Bundle(2);
- extras.putInt(Intent.EXTRA_UID, mRemovedAppId >= 0 ? mRemovedAppId : mUid);
- extras.putBoolean(Intent.EXTRA_REPLACING, true);
- mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, mRemovedPackage, extras,
- 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null);
- if (mInstallerPackageName != null) {
- mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
- mRemovedPackage, extras, 0 /*flags*/,
- mInstallerPackageName, null, null, null, null /* broadcastAllowList */,
- null);
- mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
- mRemovedPackage, extras, 0 /*flags*/,
- mInstallerPackageName, null, null, null, null /* broadcastAllowList */,
- null);
- }
- mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, mRemovedPackage,
- extras, 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null);
- mPackageSender.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0,
- mRemovedPackage, null, null, null, null /* broadcastAllowList */,
- getTemporaryAppAllowlistBroadcastOptions(REASON_PACKAGE_REPLACED).toBundle());
- }
-
- private static @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
- @PowerExemptionManager.ReasonCode int reasonCode) {
- long duration = 10_000;
- final ActivityManagerInternal amInternal =
- LocalServices.getService(ActivityManagerInternal.class);
- if (amInternal != null) {
- duration = amInternal.getBootTimeTempAllowListDuration();
- }
- final BroadcastOptions bOptions = BroadcastOptions.makeBasic();
- bOptions.setTemporaryAppAllowlist(duration,
- TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
- reasonCode, "");
- return bOptions;
- }
-
- private void sendPackageRemovedBroadcastInternal(boolean killApp, boolean removedBySystem,
- boolean isArchived) {
- Bundle extras = new Bundle();
- final int removedUid = mRemovedAppId >= 0 ? mRemovedAppId : mUid;
- extras.putInt(Intent.EXTRA_UID, removedUid);
- extras.putBoolean(Intent.EXTRA_DATA_REMOVED, mDataRemoved);
- extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, mIsRemovedPackageSystemUpdate);
- extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
- extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem);
- final boolean isReplace = mIsUpdate || mIsRemovedPackageSystemUpdate;
- if (isReplace || isArchived) {
- extras.putBoolean(Intent.EXTRA_REPLACING, true);
- }
- if (isArchived) {
- extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
- }
- extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, mRemovedForAllUsers);
-
- // Send PACKAGE_REMOVED broadcast to the respective installer.
- if (mRemovedPackage != null && mInstallerPackageName != null) {
- mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
- mRemovedPackage, extras, 0 /*flags*/,
- mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
- }
- if (mIsStaticSharedLib) {
- // When uninstalling static shared libraries, only the package's installer needs to be
- // sent a PACKAGE_REMOVED broadcast. There are no other intended recipients.
- return;
- }
- if (mRemovedPackage != null) {
- mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
- mRemovedPackage, extras, 0, null /*targetPackage*/, null,
- mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null);
- mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED_INTERNAL,
- mRemovedPackage, extras, 0 /*flags*/, PLATFORM_PACKAGE_NAME,
- null /*finishedReceiver*/, mBroadcastUsers, mInstantUserIds,
- mBroadcastAllowList, null /*bOptions*/);
- if (mDataRemoved && !mIsRemovedPackageSystemUpdate) {
- mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED,
- mRemovedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null,
- null, mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null);
- mPackageSender.notifyPackageRemoved(mRemovedPackage, removedUid);
- }
- }
- if (mRemovedAppId >= 0) {
- // If a system app's updates are uninstalled the UID is not actually removed. Some
- // services need to know the package name affected.
- if (isReplace) {
- extras.putString(Intent.EXTRA_PACKAGE_NAME, mRemovedPackage);
- }
-
- mPackageSender.sendPackageBroadcast(Intent.ACTION_UID_REMOVED,
- null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
- null, null, mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null);
- }
- }
-
public void populateBroadcastUsers(PackageSetting deletedPackageSetting) {
if (mRemovedUsers == null) {
mBroadcastUsers = null;
diff --git a/services/core/java/com/android/server/pm/PackageSender.java b/services/core/java/com/android/server/pm/PackageSender.java
index 82e1d5f3..db83f59 100644
--- a/services/core/java/com/android/server/pm/PackageSender.java
+++ b/services/core/java/com/android/server/pm/PackageSender.java
@@ -16,24 +16,7 @@
package com.android.server.pm;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.IIntentReceiver;
-import android.os.Bundle;
-import android.util.SparseArray;
-
interface PackageSender {
- /**
- * @param userIds User IDs where the action occurred on a full application
- * @param instantUserIds User IDs where the action occurred on an instant application
- */
- void sendPackageBroadcast(String action, String pkg,
- Bundle extras, int flags, String targetPkg,
- IIntentReceiver finishedReceiver, int[] userIds, int[] instantUserIds,
- @Nullable SparseArray<int[]> broadcastAllowList, @Nullable Bundle bOptions);
- void sendPackageAddedForNewUsers(@NonNull Computer snapshot, String packageName,
- boolean sendBootCompleted, boolean includeStopped, int appId, int[] userIds,
- int[] instantUserIds, boolean isArchived, int dataLoaderType);
void notifyPackageAdded(String packageName, int uid);
void notifyPackageChanged(String packageName, int uid);
void notifyPackageRemoved(String packageName, int uid);
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 571aab4..41d2aeb 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -69,10 +69,12 @@
private static final String TAG_DEFAULT_APPS = "da";
private final PackageManagerService mPm;
+ private final BroadcastHelper mBroadcastHelper;
// TODO(b/198166813): remove PMS dependency
- PreferredActivityHelper(PackageManagerService pm) {
+ PreferredActivityHelper(PackageManagerService pm, BroadcastHelper broadcastHelper) {
mPm = pm;
+ mBroadcastHelper = broadcastHelper;
}
private ResolveInfo findPreferredActivityNotLocked(@NonNull Computer snapshot, Intent intent,
@@ -120,7 +122,7 @@
}
if (changedUsers.size() > 0) {
updateDefaultHomeNotLocked(mPm.snapshotComputer(), changedUsers);
- mPm.postPreferredActivityChangedBroadcast(userId);
+ mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
mPm.scheduleWritePackageRestrictions(userId);
}
}
@@ -167,7 +169,7 @@
return mPm.setActiveLauncherPackage(packageName, userId,
successful -> {
if (successful) {
- mPm.postPreferredActivityChangedBroadcast(userId);
+ mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
}
});
}
@@ -215,7 +217,7 @@
}
// Re-snapshot after mLock
if (!(isHomeFilter(filter) && updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId))) {
- mPm.postPreferredActivityChangedBroadcast(userId);
+ mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
}
}
@@ -411,7 +413,7 @@
if (isHomeFilter(filter)) {
updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId);
}
- mPm.postPreferredActivityChangedBroadcast(userId);
+ mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
}
public void clearPackagePersistentPreferredActivities(String packageName, int userId) {
@@ -426,7 +428,7 @@
}
if (changed) {
updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId);
- mPm.postPreferredActivityChangedBroadcast(userId);
+ mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
mPm.scheduleWritePackageRestrictions(userId);
}
}
@@ -443,7 +445,7 @@
}
if (changed) {
updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId);
- mPm.postPreferredActivityChangedBroadcast(userId);
+ mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
mPm.scheduleWritePackageRestrictions(userId);
}
}
@@ -616,7 +618,7 @@
mPm.clearPackagePreferredActivitiesLPw(null, changedUsers, userId);
}
if (changedUsers.size() > 0) {
- mPm.postPreferredActivityChangedBroadcast(userId);
+ mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
}
synchronized (mPm.mLock) {
mPm.mSettings.applyDefaultPreferredAppsLPw(userId);
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index d989c90..b055a3f 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -22,6 +22,7 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
+
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
@@ -69,9 +70,11 @@
private final PermissionManagerServiceInternal mPermissionManager;
private final SharedLibrariesImpl mSharedLibraries;
private final AppDataHelper mAppDataHelper;
+ private final BroadcastHelper mBroadcastHelper;
// TODO(b/198166813): remove PMS dependency
- RemovePackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) {
+ RemovePackageHelper(PackageManagerService pm, AppDataHelper appDataHelper,
+ BroadcastHelper broadcastHelper) {
mPm = pm;
mIncrementalManager = mPm.mInjector.getIncrementalManager();
mInstaller = mPm.mInjector.getInstaller();
@@ -79,10 +82,7 @@
mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
mSharedLibraries = mPm.mInjector.getSharedLibrariesImpl();
mAppDataHelper = appDataHelper;
- }
-
- RemovePackageHelper(PackageManagerService pm) {
- this(pm, new AppDataHelper(pm));
+ mBroadcastHelper = broadcastHelper;
}
public void removeCodePath(File codePath) {
@@ -265,7 +265,8 @@
final List<AndroidPackage> sharedUserPkgs =
sus != null ? sus.getPackages() : Collections.emptyList();
- final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm);
+ final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm,
+ mBroadcastHelper);
final int[] userIds = (userId == UserHandle.USER_ALL) ? mUserManagerInternal.getUserIds()
: new int[] {userId};
for (int nextUserId : userIds) {
@@ -395,13 +396,13 @@
}
if (changedUsers.size() > 0) {
final PreferredActivityHelper preferredActivityHelper =
- new PreferredActivityHelper(mPm);
+ new PreferredActivityHelper(mPm, mBroadcastHelper);
preferredActivityHelper.updateDefaultHomeNotLocked(mPm.snapshotComputer(),
changedUsers);
- mPm.postPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
+ mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
}
} else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate
- && outInfo.mRemovedUsers != null) {
+ && outInfo.mRemovedUsers != null && !outInfo.mIsExternal) {
// For non-system uninstalls with DELETE_KEEP_DATA, set the installed state to false
// for affected users. This does not apply to app updates where the old apk is replaced
// but the old data remains.
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 0ea45c4..e993d9e 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -372,6 +372,7 @@
// Extract Icon and update the icon res ID and the bitmap path.
s.saveIconAndFixUpShortcutLocked(this, newShortcut);
s.fixUpShortcutResourceNamesAndValues(newShortcut);
+ ensureShortcutCountBeforePush();
saveShortcut(newShortcut);
}
@@ -426,7 +427,6 @@
@NonNull List<ShortcutInfo> changedShortcuts) {
Preconditions.checkArgument(newShortcut.isEnabled(),
"pushDynamicShortcuts() cannot publish disabled shortcuts");
- ensureShortcutCountBeforePush();
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index db5b9b1..c725cdc 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -147,7 +147,6 @@
final Settings.VersionInfo ver;
final List<? extends PackageStateInternal> packages;
- final InstallPackageHelper installPackageHelper = new InstallPackageHelper(mPm);
synchronized (mPm.mLock) {
ver = mPm.mSettings.findOrCreateVersion(volumeUuid);
packages = mPm.mSettings.getVolumePackagesLPr(volumeUuid);
@@ -160,7 +159,7 @@
synchronized (mPm.mInstallLock) {
final AndroidPackage pkg;
try {
- pkg = installPackageHelper.initPackageTracedLI(
+ pkg = mPm.initPackageTracedLI(
ps.getPath(), parseFlags, SCAN_INITIAL);
loaded.add(pkg);
@@ -228,7 +227,8 @@
}
if (DEBUG_INSTALL) Slog.d(TAG, "Loaded packages " + loaded);
- sendResourcesChangedBroadcast(true /* mediaStatus */, false /* replacing */, loaded);
+ mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm.snapshotComputer(),
+ true /* mediaStatus */, false /* replacing */, loaded);
synchronized (mLoadedVolumes) {
mLoadedVolumes.add(vol.getId());
}
@@ -256,7 +256,7 @@
final AndroidPackage pkg = ps.getPkg();
final int deleteFlags = PackageManager.DELETE_KEEP_DATA;
- final PackageRemovedInfo outInfo = new PackageRemovedInfo(mPm);
+ final PackageRemovedInfo outInfo = new PackageRemovedInfo();
try (PackageFreezer freezer = mPm.freezePackageForDelete(ps.getPackageName(),
UserHandle.USER_ALL, deleteFlags,
@@ -280,7 +280,8 @@
}
if (DEBUG_INSTALL) Slog.d(TAG, "Unloaded packages " + unloaded);
- sendResourcesChangedBroadcast(false /* mediaStatus */, false /* replacing */, unloaded);
+ mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm.snapshotComputer(),
+ false /* mediaStatus */, false /* replacing */, unloaded);
synchronized (mLoadedVolumes) {
mLoadedVolumes.remove(vol.getId());
}
@@ -295,21 +296,6 @@
}
}
- private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
- ArrayList<AndroidPackage> packages) {
- final int size = packages.size();
- final String[] packageNames = new String[size];
- final int[] packageUids = new int[size];
- for (int i = 0; i < size; i++) {
- final AndroidPackage pkg = packages.get(i);
- packageNames[i] = pkg.getPackageName();
- packageUids[i] = pkg.getUid();
- }
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, mediaStatus,
- replacing, packageNames, packageUids);
- mPm.notifyResourcesChanged(mediaStatus, replacing, packageNames, packageUids);
- }
-
/**
* Examine all apps present on given mounted volume, and destroy apps that
* aren't expected, either due to uninstallation or reinstallation on
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index ddb045d..29d99a73 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -26,17 +26,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
import android.app.AppOpsManager;
-import android.app.BroadcastOptions;
-import android.app.IActivityManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.SuspendDialogInfo;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Handler;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
@@ -47,7 +43,6 @@
import android.util.IntArray;
import android.util.Slog;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.LocalServices;
@@ -207,19 +202,22 @@
}
});
+ final Computer newSnapshot = mPm.snapshotComputer();
if (!notifyPackagesList.isEmpty()) {
final String[] changedPackages =
notifyPackagesList.toArray(new String[0]);
- sendPackagesSuspendedForUser(
+ mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
suspended ? Intent.ACTION_PACKAGES_SUSPENDED
: Intent.ACTION_PACKAGES_UNSUSPENDED,
changedPackages, notifyUids.toArray(), quarantined, userId);
- sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
+ mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, changedPackages,
+ suspended, userId);
mPm.scheduleWritePackageRestrictions(userId);
}
// Send the suspension changed broadcast to ensure suspension state is not stale.
if (!changedPackagesList.isEmpty()) {
- sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+ mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
+ Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
changedPackagesList.toArray(new String[0]), changedUids.toArray(), quarantined,
userId);
}
@@ -269,8 +267,10 @@
* @return The app extras of the suspended package.
*/
@Nullable
- Bundle getSuspendedPackageAppExtras(@NonNull Computer snapshot, @NonNull String packageName,
- int userId, int callingUid) {
+ static Bundle getSuspendedPackageAppExtras(@NonNull Computer snapshot,
+ @NonNull String packageName,
+ int userId,
+ int callingUid) {
final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName, callingUid);
if (ps == null) {
return null;
@@ -299,7 +299,7 @@
* suspensions will be removed.
* @param userId The user for which the changes are taking place.
*/
- void removeSuspensionsBySuspendingPackage(@NonNull Computer computer,
+ void removeSuspensionsBySuspendingPackage(@NonNull Computer snapshot,
@NonNull String[] packagesToChange,
@NonNull Predicate<String> suspendingPackagePredicate, int userId) {
final List<String> unsuspendedPackages = new ArrayList<>();
@@ -307,7 +307,7 @@
final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>();
for (String packageName : packagesToChange) {
final PackageStateInternal packageState =
- computer.getPackageStateInternal(packageName);
+ snapshot.getPackageStateInternal(packageName);
final PackageUserStateInternal packageUserState = packageState == null
? null : packageState.getUserStateOrDefault(userId);
if (packageUserState == null || !packageUserState.isSuspended()) {
@@ -350,11 +350,14 @@
});
mPm.scheduleWritePackageRestrictions(userId);
+ final Computer newSnapshot = mPm.snapshotComputer();
if (!unsuspendedPackages.isEmpty()) {
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
- sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
- sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
+ mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, packageArray,
+ false, userId);
+ mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
+ Intent.ACTION_PACKAGES_UNSUSPENDED,
packageArray, unsuspendedUids.toArray(), false, userId);
}
}
@@ -610,38 +613,6 @@
}
/**
- * Send broadcast intents for packages suspension changes.
- *
- * @param intent The action name of the suspension intent.
- * @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.
- */
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- void sendPackagesSuspendedForUser(@NonNull String intent, @NonNull String[] pkgList,
- @NonNull int[] uidList, boolean quarantined, int userId) {
- final Handler handler = mInjector.getHandler();
- final Bundle extras = new Bundle(3);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
- if (quarantined) {
- extras.putBoolean(Intent.EXTRA_QUARANTINED, true);
- }
- final int flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND;
- final Bundle options = new BroadcastOptions()
- .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
- .toBundle();
- handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
- extras, flags, null /* targetPkg */, null /* finishedReceiver */,
- new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
- (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
- mPm.snapshotComputer(), callingUid, intentExtras),
- options));
- mPm.notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
- null /* instantUserIds */, null /* broadcastAllowList */);
- }
-
- /**
* Suspends packages on behalf of an admin.
*
* @return array of packages that are unsuspendable, either because admin is not allowed to
@@ -756,37 +727,4 @@
}
return false;
}
-
- private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended,
- int userId) {
- final Handler handler = mInjector.getHandler();
- final String action = suspended
- ? Intent.ACTION_MY_PACKAGE_SUSPENDED
- : Intent.ACTION_MY_PACKAGE_UNSUSPENDED;
- handler.post(() -> {
- final IActivityManager am = ActivityManager.getService();
- if (am == null) {
- Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ "
- + (suspended ? "" : "UN") + "SUSPENDED broadcasts");
- return;
- }
- final int[] targetUserIds = new int[] {userId};
- final Computer snapshot = mPm.snapshotComputer();
- for (String packageName : affectedPackages) {
- final Bundle appExtras = suspended
- ? getSuspendedPackageAppExtras(snapshot, packageName, userId, SYSTEM_UID)
- : null;
- final Bundle intentExtras;
- if (appExtras != null) {
- intentExtras = new Bundle(1);
- intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras);
- } else {
- intentExtras = null;
- }
- mBroadcastHelper.doSendBroadcast(action, null, intentExtras,
- Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
- targetUserIds, false, null, null, null);
- }
- });
- }
}
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 8c73ce8..c6435ae 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -139,7 +139,6 @@
private final UserHandle mUser;
@NonNull
private final PackageManagerService mPm;
- private final InstallPackageHelper mInstallPackageHelper;
VerifyingSession(UserHandle user, File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, InstallSource installSource,
@@ -147,7 +146,6 @@
boolean userActionRequired, PackageManagerService pm) {
mPm = pm;
mUser = user;
- mInstallPackageHelper = new InstallPackageHelper(mPm);
mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
mObserver = observer;
mInstallFlags = sessionParams.installFlags;
@@ -181,7 +179,7 @@
PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext,
mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags, mPackageAbiOverride);
- Pair<Integer, String> ret = mInstallPackageHelper.verifyReplacingVersionCode(
+ Pair<Integer, String> ret = mPm.verifyReplacingVersionCode(
pkgLite, mRequiredInstalledVersionCode, mInstallFlags);
setReturnCode(ret.first, ret.second);
if (mRet != INSTALL_SUCCEEDED) {
@@ -729,7 +727,7 @@
continue;
}
- final int verifierUid = mInstallPackageHelper.getUidForVerifier(verifierInfo);
+ final int verifierUid = mPm.getUidForVerifier(verifierInfo);
if (verifierUid == -1) {
continue;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 097656c..dfc9b8b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -198,6 +198,7 @@
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
+import com.android.internal.display.BrightnessUtils;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
@@ -217,7 +218,6 @@
import com.android.server.LocalServices;
import com.android.server.SystemServiceManager;
import com.android.server.UiThread;
-import com.android.server.display.BrightnessUtils;
import com.android.server.input.InputManagerInternal;
import com.android.server.input.KeyboardMetricsCollector;
import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 40e9c13..88eaafa 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -955,6 +955,17 @@
}
}
+ public void setTiles(String tiles) {
+ enforceStatusBarOrShell();
+
+ if (mBar != null) {
+ try {
+ mBar.setQsTiles(tiles.split(","));
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
public void clickTile(ComponentName component) {
enforceStatusBarOrShell();
diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
index 11a4976d..d6bf02f 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
@@ -61,6 +61,8 @@
return runAddTile();
case "remove-tile":
return runRemoveTile();
+ case "set-tiles":
+ return runSetTiles();
case "click-tile":
return runClickTile();
case "check-support":
@@ -105,6 +107,11 @@
return 0;
}
+ private int runSetTiles() throws RemoteException {
+ mInterface.setTiles(getNextArgRequired());
+ return 0;
+ }
+
private int runClickTile() throws RemoteException {
mInterface.clickTile(ComponentName.unflattenFromString(getNextArgRequired()));
return 0;
@@ -242,6 +249,9 @@
pw.println(" remove-tile COMPONENT");
pw.println(" Remove a TileService of the specified component");
pw.println("");
+ pw.println(" set-tiles LIST-OF-TILES");
+ pw.println(" Sets the list of tiles as the current Quick Settings tiles");
+ pw.println("");
pw.println(" click-tile COMPONENT");
pw.println(" Click on a TileService of the specified component");
pw.println("");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a0c7870..1684724 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -580,7 +580,7 @@
IBinder mRequestedLaunchingTaskFragmentToken;
// Tracking splash screen status from previous activity
- boolean mAllowIconSplashScreen = true;
+ boolean mSplashScreenStyleSolidColor = false;
boolean mPauseSchedulePendingForPip = false;
@@ -2408,7 +2408,8 @@
@VisibleForTesting
boolean addStartingWindow(String pkg, int resolvedTheme, ActivityRecord from, boolean newTask,
boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot,
- boolean activityCreated, boolean allowIcon, boolean activityAllDrawn) {
+ boolean activityCreated, boolean isSimple,
+ boolean activityAllDrawn) {
// If the display is frozen, we won't do anything until the actual window is
// displayed so there is no reason to put in the starting window.
if (!okToDisplay()) {
@@ -2443,8 +2444,8 @@
final int typeParameter = StartingSurfaceController
.makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning,
- allowTaskSnapshot, activityCreated, allowIcon, useLegacy,
- activityAllDrawn, type, packageName, mUserId);
+ allowTaskSnapshot, activityCreated, isSimple, useLegacy, activityAllDrawn,
+ type, packageName, mUserId);
if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
if (isActivityTypeHome()) {
@@ -6746,7 +6747,7 @@
void onFirstWindowDrawn(WindowState win) {
firstWindowDrawn = true;
// stop tracking
- mAllowIconSplashScreen = false;
+ mSplashScreenStyleSolidColor = true;
if (mStartingWindow != null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Finish starting %s"
@@ -6795,7 +6796,7 @@
void onStartingWindowDrawn() {
boolean wasTaskVisible = false;
if (task != null) {
- mAllowIconSplashScreen = false;
+ mSplashScreenStyleSolidColor = true;
wasTaskVisible = !setTaskHasBeenVisible();
}
@@ -7320,32 +7321,19 @@
}
/**
- * Checks whether an icon splash screen can be used in the starting window based on the
- * preference in the {@code options} and this activity's theme, giving higher priority to the
- * {@code options}'s preference.
- *
- * When no preference is specified, a default behaviour is defined:
- * - if the activity is started from the home or shell app, an icon can be used
- * - if the activity is started from SystemUI, an icon should not be used
- * - if there is a launching activity, use its preference
- * - if none of the above is met, only use an icon when the activity is started for the first
- * time from a System app
- *
- * The returned value is sent to WmShell, which will make the final decision on what splash
- * screen type will be used.
- *
- * @return true if an icon can be used in the splash screen
- * false when an icon should not be used in the splash screen
+ * @return true if a solid color splash screen must be used
+ * false when an icon splash screen can be used, but the final decision for whether to
+ * use an icon or solid color splash screen will be made by WmShell.
*/
- private boolean canUseIconSplashScreen(ActivityRecord sourceRecord,
+ private boolean shouldUseSolidColorSplashScreen(ActivityRecord sourceRecord,
boolean startActivity, ActivityOptions options, int resolvedTheme) {
if (sourceRecord == null && !startActivity) {
- // Shouldn't use an icon if this activity is not top activity. This could happen when
- // adding a splash screen window to the warm start activity which is re-create because
- // top is finishing.
+ // Use simple style if this activity is not top activity. This could happen when adding
+ // a splash screen window to the warm start activity which is re-create because top is
+ // finishing.
final ActivityRecord above = task.getActivityAbove(this);
if (above != null) {
- return false;
+ return true;
}
}
@@ -7353,33 +7341,32 @@
final int optionsStyle = options != null ? options.getSplashScreenStyle() :
SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR) {
- return false;
+ return true;
} else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON
|| isIconStylePreferred(resolvedTheme)) {
- return true;
+ return false;
}
// Choose the default behavior when neither the ActivityRecord nor the activity theme have
// specified a splash screen style.
if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME || launchedFromUid == Process.SHELL_UID) {
- return true;
- } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) {
return false;
+ } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) {
+ return true;
} else {
- // Need to check sourceRecord in case this activity is launched from a service or a
- // trampoline activity.
+ // Need to check sourceRecord in case this activity is launched from a service.
if (sourceRecord == null) {
sourceRecord = searchCandidateLaunchingActivity();
}
if (sourceRecord != null) {
- return sourceRecord.mAllowIconSplashScreen;
+ return sourceRecord.mSplashScreenStyleSolidColor;
}
// Use an icon if the activity was launched from System for the first start.
- // Otherwise, can't use an icon splash screen.
- return mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEM && startActivity;
+ // Otherwise, must use solid color splash screen.
+ return mLaunchSourceType != LAUNCH_SOURCE_TYPE_SYSTEM || !startActivity;
}
}
@@ -7443,7 +7430,7 @@
final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme,
splashScreenTheme);
- mAllowIconSplashScreen = canUseIconSplashScreen(sourceRecord, startActivity,
+ mSplashScreenStyleSolidColor = shouldUseSolidColorSplashScreen(sourceRecord, startActivity,
startOptions, resolvedTheme);
final boolean activityCreated =
@@ -7455,7 +7442,7 @@
final boolean scheduled = addStartingWindow(packageName, resolvedTheme,
prev, newTask || newSingleActivity, taskSwitch, processRunning,
- allowTaskSnapshot(), activityCreated, mAllowIconSplashScreen, allDrawn);
+ allowTaskSnapshot(), activityCreated, mSplashScreenStyleSolidColor, allDrawn);
if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) {
Slog.d(TAG, "Scheduled starting window for " + this);
}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index f33ecaa..184de58 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -301,9 +301,12 @@
}
// Always update the reordering time when this is called to ensure that the timeout
- // is reset
+ // is reset. Extend this duration when running in tests.
+ final long timeout = ActivityManager.isRunningInUserTestHarness()
+ ? mFreezeTaskListTimeoutMs * 10
+ : mFreezeTaskListTimeoutMs;
mService.mH.removeCallbacks(mResetFreezeTaskListOnTimeoutRunnable);
- mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, mFreezeTaskListTimeoutMs);
+ mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, timeout);
}
/**
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index a0517be..a55c232 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -19,12 +19,12 @@
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN;
-import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_ICON;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SNAPSHOT;
import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
@@ -102,7 +102,7 @@
static int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch,
boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated,
- boolean allowIcon, boolean useLegacy, boolean activityDrawn, int startingWindowType,
+ boolean isSolidColor, boolean useLegacy, boolean activityDrawn, int startingWindowType,
String packageName, int userId) {
int parameter = 0;
if (newTask) {
@@ -120,8 +120,8 @@
if (activityCreated || startingWindowType == STARTING_WINDOW_TYPE_SNAPSHOT) {
parameter |= TYPE_PARAMETER_ACTIVITY_CREATED;
}
- if (allowIcon) {
- parameter |= TYPE_PARAMETER_ALLOW_ICON;
+ if (isSolidColor) {
+ parameter |= TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
}
if (useLegacy) {
parameter |= TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a6d285a..3faf8b9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5241,16 +5241,17 @@
applyForcedPropertiesForDefaultDisplay();
mAnimator.ready();
mDisplayReady = true;
- // Reconfigure all displays to make sure that forced properties and
- // DisplayWindowSettings are applied.
- mRoot.forAllDisplays(DisplayContent::reconfigureDisplayLocked);
+ mHasWideColorGamutSupport = queryWideColorGamutSupport();
+ mHasHdrSupport = queryHdrSupport();
mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TOUCHSCREEN);
mIsFakeTouchDevice = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_FAKETOUCH);
+ // Reconfigure all displays to make sure that the forced properties and
+ // DisplayWindowSettings are applied. In addition, wide-color/hdr/isTouchDevice also
+ // affect the Configuration.
+ mRoot.forAllDisplays(DisplayContent::reconfigureDisplayLocked);
}
-
- mAtmService.updateConfiguration(null /* request to compute config */);
}
public void systemReady() {
@@ -5258,8 +5259,6 @@
mPolicy.systemReady();
mRoot.forAllDisplayPolicies(DisplayPolicy::systemReady);
mSnapshotController.systemReady();
- mHasWideColorGamutSupport = queryWideColorGamutSupport();
- mHasHdrSupport = queryHdrSupport();
UiThread.getHandler().post(mSettingsObserver::loadSettings);
IVrManager vrManager = IVrManager.Stub.asInterface(
ServiceManager.getService(Context.VR_SERVICE));
@@ -9647,4 +9646,15 @@
Binder.restoreCallingIdentity(origId);
}
}
+
+ /**
+ * Resets the spatial ordering of recents for testing purposes.
+ */
+ void resetFreezeRecentTaskListReordering() {
+ if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS,
+ "resetFreezeRecentTaskListReordering()")) {
+ throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
+ }
+ mAtmService.getRecentTasks().resetFreezeTaskListReorderingOnTimeout();
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 8fad950..fa9a65f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -142,6 +142,8 @@
return runReset(pw);
case "disable-blur":
return runSetBlurDisabled(pw);
+ case "reset-freeze-recent-tasks":
+ return runResetFreezeRecentTaskListReordering(pw);
case "shell":
return runWmShellCommand(pw);
default:
@@ -252,6 +254,11 @@
return 0;
}
+ private int runResetFreezeRecentTaskListReordering(PrintWriter pw) throws RemoteException {
+ mInternal.resetFreezeRecentTaskListReordering();
+ return 0;
+ }
+
private void printInitialDisplayDensity(PrintWriter pw , int displayId) {
try {
final int initialDensity = mInterface.getInitialDisplayDensity(displayId);
@@ -1492,6 +1499,8 @@
printLetterboxHelp(pw);
printMultiWindowConfigHelp(pw);
+ pw.println(" reset-freeze-recent-tasks");
+ pw.println(" Resets the spatial ordering of the recent tasks list");
pw.println(" reset [-d DISPLAY_ID]");
pw.println(" Reset all override settings.");
if (!IS_USER) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index d0ead14..25e8475 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -21,6 +21,7 @@
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY;
import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_HARDWARE_LIMITATION;
+import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_STORAGE_LIMIT_REACHED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET;
import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
@@ -51,6 +52,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
+import android.os.Parcel;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -63,6 +65,7 @@
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.devicepolicy.flags.FlagUtils;
import com.android.server.utils.Slogf;
import libcore.io.IoUtils;
@@ -117,6 +120,10 @@
* Map containing the current set of admins in each user with active policies.
*/
private final SparseArray<Set<EnforcingAdmin>> mEnforcingAdmins;
+ private final SparseArray<HashMap<EnforcingAdmin, Integer>> mAdminPolicySize;
+
+ //TODO(b/295504706) : Speak to security team to decide what to set Policy_Size_Limit
+ private static final int POLICY_SIZE_LIMIT = 99999;
private final DeviceAdminServiceController mDeviceAdminServiceController;
@@ -131,6 +138,7 @@
mLocalPolicies = new SparseArray<>();
mGlobalPolicies = new HashMap<>();
mEnforcingAdmins = new SparseArray<>();
+ mAdminPolicySize = new SparseArray<>();
}
/**
@@ -139,7 +147,6 @@
*
* <p>If {@code skipEnforcePolicy} is true, it sets the policies in the internal data structure
* but doesn't call the enforcing logic.
- *
*/
<V> void setLocalPolicy(
@NonNull PolicyDefinition<V> policyDefinition,
@@ -152,6 +159,12 @@
synchronized (mLock) {
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
+ if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
+ policyDefinition, userId)) {
+ return;
+ }
+ }
if (policyDefinition.isNonCoexistablePolicy()) {
setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState,
@@ -236,6 +249,7 @@
}
// TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+
/**
* Set the policy for the provided {@code policyDefinition}
* (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
@@ -250,6 +264,7 @@
}
// TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+
/**
* Removes any previously set policy for the provided {@code policyDefinition}
* (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
@@ -267,6 +282,10 @@
}
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
+ if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
+ }
+
if (policyDefinition.isNonCoexistablePolicy()) {
setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState,
enforcingAdmin, /* value= */ null, userId, /* skipEnforcePolicy= */ false);
@@ -392,6 +411,7 @@
}
// TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+
/**
* Set the policy for the provided {@code policyDefinition}
* (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
@@ -407,6 +427,13 @@
Objects.requireNonNull(value);
synchronized (mLock) {
+ PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
+ if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
+ policyDefinition, UserHandle.USER_ALL)) {
+ return;
+ }
+ }
// TODO(b/270999567): Move error handling for DISALLOW_CELLULAR_2G into the code
// that honors the restriction once there's an API available
if (checkFor2gFailure(policyDefinition, enforcingAdmin)) {
@@ -416,8 +443,6 @@
return;
}
- PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
-
boolean policyChanged = globalPolicyState.addPolicy(enforcingAdmin, value);
boolean policyAppliedOnAllUsers = applyGlobalPolicyOnUsersWithLocalPoliciesLocked(
policyDefinition, enforcingAdmin, value, skipEnforcePolicy);
@@ -434,7 +459,7 @@
// TODO(b/285532044): remove hack and handle properly
if (!policyAppliedGlobally
&& policyDefinition.getPolicyKey().getIdentifier().equals(
- USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
+ USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value;
PolicyValue<Set<String>> parsedResolvedValue =
(PolicyValue<Set<String>>) globalPolicyState.getCurrentResolvedPolicy();
@@ -459,6 +484,7 @@
}
// TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+
/**
* Removes any previously set policy for the provided {@code policyDefinition}
* (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
@@ -472,6 +498,11 @@
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
+
+ if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ decreasePolicySizeForAdmin(policyState, enforcingAdmin);
+ }
+
boolean policyChanged = policyState.removePolicy(enforcingAdmin);
if (policyChanged) {
@@ -687,7 +718,6 @@
* <p>Note that this will always return at most one item for policies that do not require
* additional params (e.g. {@link PolicyDefinition#LOCK_TASK} vs
* {@link PolicyDefinition#PERMISSION_GRANT(String, String)}).
- *
*/
@NonNull
<V> Set<PolicyKey> getLocalPolicyKeysSetByAdmin(
@@ -723,7 +753,6 @@
* <p>Note that this will always return at most one item for policies that do not require
* additional params (e.g. {@link PolicyDefinition#LOCK_TASK} vs
* {@link PolicyDefinition#PERMISSION_GRANT(String, String)}).
- *
*/
@NonNull
<V> Set<PolicyKey> getLocalPolicyKeysSetByAllAdmins(
@@ -964,7 +993,7 @@
EnforcingAdmin callingAdmin,
PolicyDefinition<V> policyDefinition,
int userId) {
- for (EnforcingAdmin admin: policyState.getPoliciesSetByAdmins().keySet()) {
+ for (EnforcingAdmin admin : policyState.getPoliciesSetByAdmins().keySet()) {
// We're sending a separate broadcast for the calling admin with the result.
if (admin.equals(callingAdmin)) {
continue;
@@ -1152,7 +1181,7 @@
try {
if (packageManager.getPackageInfo(packageName, 0, userId) == null
|| packageManager.getActivityInfo(
- policies.get(admin).getValue(), 0, userId) == null) {
+ policies.get(admin).getValue(), 0, userId) == null) {
Slogf.e(TAG, String.format(
"Persistent preferred activity in package %s not found for "
+ "user %d, removing policy for admin",
@@ -1450,6 +1479,97 @@
return false;
}
+ /**
+ * Calculate the size of a policy in bytes
+ */
+
+ private static <V> int sizeOf(PolicyValue<V> value) {
+ try {
+ Parcel parcel = Parcel.obtain();
+ parcel.writeParcelable(value, /* flags= */ 0);
+
+ parcel.setDataPosition(0);
+
+ byte[] bytes;
+
+ bytes = parcel.marshall();
+ return bytes.length;
+ } catch (Exception e) {
+ Log.e(TAG, "Error calculating size of policy: " + e);
+ return 0;
+ }
+ }
+
+ /**
+ * Checks if the policy already exists and removes the current size to prevent recording the
+ * same policy twice.
+ *
+ * Checks if the new sum of the size of all policies is less than the maximum sum of policies
+ * size per admin and returns true.
+ *
+ * If the policy size limit is reached then send policy result to admin and return false.
+ */
+
+ private <V> boolean handleAdminPolicySizeLimit(PolicyState<V> policyState, EnforcingAdmin admin,
+ PolicyValue<V> value, PolicyDefinition policyDefinition, int userId) {
+ int currentSize = 0;
+ if (mAdminPolicySize.contains(admin.getUserId())
+ && mAdminPolicySize.get(
+ admin.getUserId()).containsKey(admin)) {
+ currentSize = mAdminPolicySize.get(admin.getUserId()).get(admin);
+ }
+ if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+ currentSize -= sizeOf(policyState.getPoliciesSetByAdmins().get(admin));
+ }
+ int policySize = sizeOf(value);
+ if (currentSize + policySize < POLICY_SIZE_LIMIT) {
+ increasePolicySizeForAdmin(admin, policySize);
+ return true;
+ } else {
+ sendPolicyResultToAdmin(
+ admin,
+ policyDefinition,
+ RESULT_FAILURE_STORAGE_LIMIT_REACHED,
+ userId);
+ return false;
+ }
+ }
+
+ /**
+ * Increase the int in mAdminPolicySize representing the size of the sum of all
+ * active policies for that admin.
+ */
+
+ private <V> void increasePolicySizeForAdmin(EnforcingAdmin admin, int policySize) {
+ if (!mAdminPolicySize.contains(admin.getUserId())) {
+ mAdminPolicySize.put(admin.getUserId(), new HashMap<>());
+ }
+ if (!mAdminPolicySize.get(admin.getUserId()).containsKey(admin)) {
+ mAdminPolicySize.get(admin.getUserId()).put(admin, /* size= */ 0);
+ }
+ mAdminPolicySize.get(admin.getUserId()).put(admin,
+ mAdminPolicySize.get(admin.getUserId()).get(admin) + policySize);
+ }
+
+ /**
+ * Decrease the int in mAdminPolicySize representing the size of the sum of all
+ * active policies for that admin.
+ */
+
+ private <V> void decreasePolicySizeForAdmin(PolicyState<V> policyState, EnforcingAdmin admin) {
+ if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+ mAdminPolicySize.get(admin.getUserId()).put(admin,
+ mAdminPolicySize.get(admin.getUserId()).get(admin) - sizeOf(
+ policyState.getPoliciesSetByAdmins().get(admin)));
+ }
+ if (mAdminPolicySize.get(admin.getUserId()).get(admin) <= 0) {
+ mAdminPolicySize.get(admin.getUserId()).remove(admin);
+ }
+ if (mAdminPolicySize.get(admin.getUserId()).isEmpty()) {
+ mAdminPolicySize.remove(admin.getUserId());
+ }
+ }
+
@NonNull
private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) {
synchronized (mLock) {
@@ -1508,11 +1628,13 @@
clear();
write();
}
+
private void clear() {
synchronized (mLock) {
mGlobalPolicies.clear();
mLocalPolicies.clear();
mEnforcingAdmins.clear();
+ mAdminPolicySize.clear();
}
}
@@ -1553,7 +1675,11 @@
private static final String TAG_POLICY_STATE_ENTRY = "policy-state-entry";
private static final String TAG_POLICY_KEY_ENTRY = "policy-key-entry";
private static final String TAG_ENFORCING_ADMINS_ENTRY = "enforcing-admins-entry";
+ private static final String TAG_ENFORCING_ADMIN_AND_SIZE = "enforcing-admin-and-size";
+ private static final String TAG_ENFORCING_ADMIN = "enforcing-admin";
+ private static final String TAG_POLICY_SUM_SIZE = "policy-sum-size";
private static final String ATTR_USER_ID = "user-id";
+ private static final String ATTR_POLICY_SUM_SIZE = "size";
private final File mFile;
@@ -1595,6 +1721,7 @@
writeLocalPoliciesInner(serializer);
writeGlobalPoliciesInner(serializer);
writeEnforcingAdminsInner(serializer);
+ writeEnforcingAdminSizeInner(serializer);
}
private void writeLocalPoliciesInner(TypedXmlSerializer serializer) throws IOException {
@@ -1652,6 +1779,30 @@
}
}
+ private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
+ throws IOException {
+ if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (mAdminPolicySize != null) {
+ for (int i = 0; i < mAdminPolicySize.size(); i++) {
+ int userId = mAdminPolicySize.keyAt(i);
+ for (EnforcingAdmin admin : mAdminPolicySize.get(
+ userId).keySet()) {
+ serializer.startTag(/* namespace= */ null,
+ TAG_ENFORCING_ADMIN_AND_SIZE);
+ serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN);
+ admin.saveToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN);
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE);
+ serializer.attributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE,
+ mAdminPolicySize.get(userId).get(admin));
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE);
+ serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_AND_SIZE);
+ }
+ }
+ }
+ }
+ }
+
void readFromFileLocked() {
if (!mFile.exists()) {
Log.d(TAG, "" + mFile + " doesn't exist");
@@ -1689,6 +1840,9 @@
case TAG_ENFORCING_ADMINS_ENTRY:
readEnforcingAdminsInner(parser);
break;
+ case TAG_ENFORCING_ADMIN_AND_SIZE:
+ readEnforcingAdminAndSizeInner(parser);
+ break;
default:
Slogf.wtf(TAG, "Unknown tag " + tag);
}
@@ -1767,5 +1921,37 @@
}
mEnforcingAdmins.get(admin.getUserId()).add(admin);
}
+
+ private void readEnforcingAdminAndSizeInner(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ EnforcingAdmin admin = null;
+ int size = 0;
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tag = parser.getName();
+ switch (tag) {
+ case TAG_ENFORCING_ADMIN:
+ admin = EnforcingAdmin.readFromXml(parser);
+ break;
+ case TAG_POLICY_SUM_SIZE:
+ size = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE);
+ break;
+ default:
+ Slogf.wtf(TAG, "Unknown tag " + tag);
+ }
+ }
+ if (admin == null) {
+ Slogf.wtf(TAG, "Error parsing enforcingAdmins, EnforcingAdmin is null.");
+ return;
+ }
+ if (size <= 0) {
+ Slogf.wtf(TAG, "Error parsing policy size, size is " + size);
+ return;
+ }
+ if (!mAdminPolicySize.contains(admin.getUserId())) {
+ mAdminPolicySize.put(admin.getUserId(), new HashMap<>());
+ }
+ mAdminPolicySize.get(admin.getUserId()).put(admin, size);
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f604932..6aa135a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -241,7 +241,6 @@
import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
import static android.provider.Telephony.Carriers.INVALID_APN_ID;
import static android.security.keystore.AttestationUtils.USE_INDIVIDUAL_ATTESTATION;
-
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
@@ -23455,7 +23454,6 @@
public DevicePolicyState getDevicePolicyState() {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
-
return mInjector.binderWithCleanCallingIdentity(mDevicePolicyEngine::getDevicePolicyState);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
index 9fe3749..7e17ef11 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy.flags;
+import static com.android.server.devicepolicy.flags.Flags.devicePolicySizeTrackingEnabled;
import static com.android.server.devicepolicy.flags.Flags.policyEngineMigrationV2Enabled;
import android.os.Binder;
@@ -28,4 +29,10 @@
return policyEngineMigrationV2Enabled();
});
}
+
+ public static boolean isDevicePolicySizeTrackingEnabled() {
+ return Binder.withCleanCallingIdentity(() -> {
+ return devicePolicySizeTrackingEnabled();
+ });
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
index 00702a9..0dde496 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
@@ -5,4 +5,10 @@
namespace: "enterprise"
description: "V2 of the policy engine migrations for Android V"
bug: "289520697"
+}
+flag {
+ name: "device_policy_size_tracking_enabled"
+ namespace: "enterprise"
+ description: "Add feature to track the total policy size and have a max threshold."
+ bug: "281543351"
}
\ No newline at end of file
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
index 4a2bf75..5d3eba8 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -17,24 +17,22 @@
package com.android.server.pm;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
import static com.google.common.truth.Truth.assertWithMessage;
+
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
+
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.AppGlobals;
-import android.content.IIntentReceiver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Postsubmit;
-import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -87,18 +85,6 @@
@Test
public void testPackageRemoval() {
class PackageSenderImpl implements PackageSender {
- public void sendPackageBroadcast(final String action, final String pkg,
- final Bundle extras, final int flags, final String targetPkg,
- final IIntentReceiver finishedReceiver, final int[] userIds,
- int[] instantUserIds, SparseArray<int[]> broadcastAllowList,
- @Nullable Bundle bOptions) {
- }
-
- public void sendPackageAddedForNewUsers(@NonNull Computer snapshot, String packageName,
- boolean sendBootComplete, boolean includeStopped, int appId,
- int[] userIds, int[] instantUserIds, boolean isArchived, int dataLoaderType) {
- }
-
@Override
public void notifyPackageAdded(String packageName, int uid) {
}
@@ -113,9 +99,8 @@
}
}
- PackageSenderImpl sender = new PackageSenderImpl();
PackageSetting setting = null;
- PackageRemovedInfo pri = new PackageRemovedInfo(sender);
+ PackageRemovedInfo pri = new PackageRemovedInfo();
// Initial conditions: nothing there
Assert.assertNull(pri.mRemovedUsers);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
index 2fd6e5f..7a4327c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isA;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -27,15 +28,18 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.database.ContentObserver;
+import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.net.Uri;
import android.os.Handler;
+import android.os.PowerManager;
import android.os.UserHandle;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.view.Display;
+import android.view.DisplayAdjustments;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
@@ -59,6 +63,7 @@
private static final float EPSILON = 0.00001f;
private static final Uri BRIGHTNESS_URI =
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
+ private static final float BRIGHTNESS_MAX = 0.6f;
private Context mContext;
private MockContentResolver mContentResolverSpy;
@@ -66,6 +71,7 @@
private DisplayListener mDisplayListener;
private ContentObserver mContentObserver;
private TestLooper mTestLooper;
+ private BrightnessSynchronizer mSynchronizer;
@Mock private DisplayManager mDisplayManagerMock;
@Captor private ArgumentCaptor<DisplayListener> mDisplayListenerCaptor;
@@ -74,7 +80,17 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+
+ Display display = mock(Display.class);
+ when(display.getDisplayAdjustments()).thenReturn(new DisplayAdjustments());
+ BrightnessInfo info = new BrightnessInfo(PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ PowerManager.BRIGHTNESS_MIN, BRIGHTNESS_MAX,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, BRIGHTNESS_MAX,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+ when(display.getBrightnessInfo()).thenReturn(info);
+
+ mContext = spy(new ContextWrapper(
+ ApplicationProvider.getApplicationContext().createDisplayContext(display)));
mContentResolverSpy = spy(new MockContentResolver(mContext));
mContentResolverSpy.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mContext.getContentResolver()).thenReturn(mContentResolverSpy);
@@ -128,13 +144,12 @@
@Test
public void testSetSameIntValue_nothingUpdated() {
putFloatSetting(0.5f);
- putIntSetting(128);
start();
- putIntSetting(128);
+ putIntSetting(fToI(0.5f));
advanceTime(10);
verify(mDisplayManagerMock, times(0)).setBrightness(
- eq(Display.DEFAULT_DISPLAY), eq(iToF(128)));
+ eq(Display.DEFAULT_DISPLAY), eq(0.5f));
}
@Test
@@ -154,14 +169,13 @@
// Verify that this update did not get sent to float, because synchronizer
// is still waiting for confirmation of its first value.
verify(mDisplayManagerMock, times(0)).setBrightness(
- eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
+ Display.DEFAULT_DISPLAY, iToF(20));
// Send the confirmation of the initial change. This should trigger the new value to
// finally be processed and we can verify that the new value (20) is sent.
putIntSetting(fToI(0.4f));
advanceTime(10);
- verify(mDisplayManagerMock).setBrightness(
- eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
+ verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY, iToF(20));
}
@@ -183,8 +197,7 @@
advanceTime(200);
// Verify that the new value gets sent because the timeout expired.
- verify(mDisplayManagerMock).setBrightness(
- eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
+ verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY, iToF(20));
// Send a confirmation of the initial event, BrightnessSynchronizer should treat this as a
// new event because the timeout had already expired
@@ -196,14 +209,14 @@
// Verify we sent what would have been the confirmation as a new event to displaymanager.
// We do both fToI and iToF because the conversions are not symmetric.
- verify(mDisplayManagerMock).setBrightness(
- eq(Display.DEFAULT_DISPLAY), eq(iToF(fToI(0.4f))));
+ verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY,
+ iToF(fToI(0.4f)));
}
- private BrightnessSynchronizer start() {
- BrightnessSynchronizer bs = new BrightnessSynchronizer(mContext, mTestLooper.getLooper(),
+ private void start() {
+ mSynchronizer = new BrightnessSynchronizer(mContext, mTestLooper.getLooper(),
mClock::now);
- bs.startSynchronizing();
+ mSynchronizer.startSynchronizing();
verify(mDisplayManagerMock).registerDisplayListener(mDisplayListenerCaptor.capture(),
isA(Handler.class), eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
mDisplayListener = mDisplayListenerCaptor.getValue();
@@ -211,7 +224,6 @@
verify(mContentResolverSpy).registerContentObserver(eq(BRIGHTNESS_URI), eq(false),
mContentObserverCaptor.capture(), eq(UserHandle.USER_ALL));
mContentObserver = mContentObserverCaptor.getValue();
- return bs;
}
private int getIntSetting() throws Exception {
@@ -241,11 +253,11 @@
}
private int fToI(float brightness) {
- return BrightnessSynchronizer.brightnessFloatToInt(brightness);
+ return mSynchronizer.brightnessFloatToIntSetting(brightness);
}
private float iToF(int brightness) {
- return BrightnessSynchronizer.brightnessIntToFloat(brightness);
+ return mSynchronizer.brightnessIntSettingToFloat(brightness);
}
private void advanceTime(long timeMs) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index a23539e..306de52 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -71,10 +71,12 @@
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.BrightnessInfo;
import android.hardware.display.Curve;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -87,11 +89,13 @@
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.MessageQueue;
import android.os.Process;
import android.os.RemoteException;
import android.view.ContentRecordingSession;
import android.view.Display;
+import android.view.DisplayAdjustments;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
@@ -99,7 +103,6 @@
import android.view.SurfaceControl;
import android.window.DisplayWindowPolicyController;
-import androidx.test.InstrumentationRegistry;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
@@ -118,11 +121,11 @@
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.wm.WindowManagerInternal;
-import com.google.common.truth.Expect;
-
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+import com.google.common.truth.Expect;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -198,9 +201,10 @@
@Override
LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
- Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+ Handler handler, DisplayAdapter.Listener displayAdapterListener,
+ DisplayManagerFlags flags) {
return new LocalDisplayAdapter(syncRoot, context, handler,
- displayAdapterListener, new LocalDisplayAdapter.Injector() {
+ displayAdapterListener, flags, new LocalDisplayAdapter.Injector() {
@Override
public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
return mSurfaceControlProxy;
@@ -244,8 +248,14 @@
@Override
LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
- Handler handler, DisplayAdapter.Listener displayAdapterListener) {
- return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
+ Handler handler, DisplayAdapter.Listener displayAdapterListener,
+ DisplayManagerFlags flags) {
+ return new LocalDisplayAdapter(
+ syncRoot,
+ context,
+ handler,
+ displayAdapterListener,
+ flags,
new LocalDisplayAdapter.Injector() {
@Override
public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
@@ -323,7 +333,11 @@
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
// TODO: b/287945043
- mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ Display display = mock(Display.class);
+ when(display.getDisplayAdjustments()).thenReturn(new DisplayAdjustments());
+ when(display.getBrightnessInfo()).thenReturn(mock(BrightnessInfo.class));
+ mContext = spy(new ContextWrapper(
+ ApplicationProvider.getApplicationContext().createDisplayContext(display)));
mResources = Mockito.spy(mContext.getResources());
manageDisplaysPermission(/* granted= */ false);
when(mContext.getResources()).thenReturn(mResources);
@@ -1898,7 +1912,6 @@
@Test
public void testSettingTwoBrightnessConfigurationsOnMultiDisplay() {
- Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
// get the first two internal displays
@@ -2445,6 +2458,86 @@
assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED,
EVENT_DISPLAY_DISCONNECTED);
}
+
+ @Test
+ public void testRegisterDisplayOffloader_whenEnabled_DisplayHasDisplayOffloadSession() {
+ when(mMockFlags.isDisplayOffloadEnabled()).thenReturn(true);
+ // set up DisplayManager
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ // set up display
+ FakeDisplayDevice displayDevice =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.DEFAULT_DISPLAY);
+ initDisplayPowerController(localService);
+ LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+ LogicalDisplay display =
+ logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+ int displayId = display.getDisplayIdLocked();
+
+ // Register DisplayOffloader.
+ DisplayOffloader mockDisplayOffloader = mock(DisplayOffloader.class);
+ localService.registerDisplayOffloader(displayId, mockDisplayOffloader);
+
+ assertThat(display.getDisplayOffloadSessionLocked().getDisplayOffloader()).isEqualTo(
+ mockDisplayOffloader);
+ }
+
+ @Test
+ public void testRegisterDisplayOffloader_whenDisabled_DisplayHasNoDisplayOffloadSession() {
+ when(mMockFlags.isDisplayOffloadEnabled()).thenReturn(false);
+ // set up DisplayManager
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ // set up display
+ FakeDisplayDevice displayDevice =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.DEFAULT_DISPLAY);
+ initDisplayPowerController(localService);
+ LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+ LogicalDisplay display =
+ logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+ int displayId = display.getDisplayIdLocked();
+
+ // Register DisplayOffloader.
+ DisplayOffloader mockDisplayOffloader = mock(DisplayOffloader.class);
+ localService.registerDisplayOffloader(displayId, mockDisplayOffloader);
+
+ assertThat(display.getDisplayOffloadSessionLocked()).isNull();
+ }
+
+ private void initDisplayPowerController(DisplayManagerInternal localService) {
+ localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
+ @Override
+ public void onStateChanged() {
+
+ }
+
+ @Override
+ public void onProximityPositive() {
+
+ }
+
+ @Override
+ public void onProximityNegative() {
+
+ }
+
+ @Override
+ public void onDisplayStateChange(boolean allInactive, boolean allOff) {
+
+ }
+
+ @Override
+ public void acquireSuspendBlocker(String id) {
+
+ }
+
+ @Override
+ public void releaseSuspendBlocker(String id) {
+
+ }
+ }, new Handler(Looper.getMainLooper()), mSensorManager);
+ }
+
private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled) {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mShortMockedInjector);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index a56b59a..dca69eb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -44,6 +44,7 @@
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
@@ -123,6 +124,9 @@
private Handler mHandler;
private DisplayPowerControllerHolder mHolder;
private Sensor mProxSensor;
+ private DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
+ private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+
@Mock
private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
@Mock
@@ -1409,6 +1413,111 @@
BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
}
+ @Test
+ public void testDozeScreenStateOverride_toSupportedOffloadStateFromDoze_DisplayStateChanges() {
+ // set up.
+ int initState = Display.STATE_DOZE;
+ int supportedTargetState = Display.STATE_DOZE_SUSPEND;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ doAnswer(invocation -> {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
+ return null;
+ }).when(mHolder.displayPowerState).setScreenState(anyInt());
+ // init displayoffload session and support offloading.
+ initDisplayOffloadSession();
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ // start with DOZE.
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ mHolder.dpc.overrideDozeScreenState(supportedTargetState);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.displayPowerState).setScreenState(supportedTargetState);
+ }
+
+ @Test
+ public void testDozeScreenStateOverride_toUnSupportedOffloadStateFromDoze_stateRemains() {
+ // set up.
+ int initState = Display.STATE_DOZE;
+ int unSupportedTargetState = Display.STATE_ON;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ doAnswer(invocation -> {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
+ return null;
+ }).when(mHolder.displayPowerState).setScreenState(anyInt());
+ // init displayoffload session and support offloading.
+ initDisplayOffloadSession();
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ // start with DOZE.
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ mHolder.dpc.overrideDozeScreenState(unSupportedTargetState);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+ }
+
+ @Test
+ public void testDozeScreenStateOverride_toSupportedOffloadStateFromOFF_stateRemains() {
+ // set up.
+ int initState = Display.STATE_OFF;
+ int supportedTargetState = Display.STATE_DOZE_SUSPEND;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ doAnswer(invocation -> {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
+ return null;
+ }).when(mHolder.displayPowerState).setScreenState(anyInt());
+ // init displayoffload session and support offloading.
+ initDisplayOffloadSession();
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ // start with OFF.
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ mHolder.dpc.overrideDozeScreenState(supportedTargetState);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+ }
+
+ private void initDisplayOffloadSession() {
+ mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() {
+ @Override
+ public boolean startOffload() {
+ return true;
+ }
+
+ @Override
+ public void stopOffload() {}
+ });
+
+ mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() {
+ @Override
+ public void setDozeStateOverride(int displayState) {}
+
+ @Override
+ public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() {
+ return mDisplayOffloader;
+ }
+ };
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -1742,9 +1851,9 @@
BrightnessRangeController getBrightnessRangeController(
HighBrightnessModeController hbmController, Runnable modeChangeCallback,
DisplayDeviceConfig displayDeviceConfig, Handler handler,
- DisplayManagerFlags flags) {
+ DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
return new BrightnessRangeController(hbmController, modeChangeCallback,
- displayDeviceConfig, mHdrClamper, mFlags);
+ displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
}
@Override
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 0572117..edaa1d5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -44,6 +44,7 @@
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
@@ -122,6 +123,8 @@
private Handler mHandler;
private DisplayPowerControllerHolder mHolder;
private Sensor mProxSensor;
+ private DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
+ private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
@Mock
private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
@@ -1364,6 +1367,110 @@
verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
}
+ @Test
+ public void testDozeScreenStateOverride_toSupportedOffloadStateFromDoze_DisplayStateChanges() {
+ // set up.
+ int initState = Display.STATE_DOZE;
+ int supportedTargetState = Display.STATE_DOZE_SUSPEND;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ doAnswer(invocation -> {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
+ return null;
+ }).when(mHolder.displayPowerState).setScreenState(anyInt());
+ // init displayoffload session and support offloading.
+ initDisplayOffloadSession();
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ // start with DOZE.
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ mHolder.dpc.overrideDozeScreenState(supportedTargetState);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.displayPowerState).setScreenState(supportedTargetState);
+ }
+
+ @Test
+ public void testDozeScreenStateOverride_toUnSupportedOffloadStateFromDoze_stateRemains() {
+ // set up.
+ int initState = Display.STATE_DOZE;
+ int unSupportedTargetState = Display.STATE_ON;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ doAnswer(invocation -> {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
+ return null;
+ }).when(mHolder.displayPowerState).setScreenState(anyInt());
+ // init displayoffload session and support offloading.
+ initDisplayOffloadSession();
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ // start with DOZE.
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ mHolder.dpc.overrideDozeScreenState(unSupportedTargetState);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+ }
+
+ @Test
+ public void testDozeScreenStateOverride_toSupportedOffloadStateFromOFF_stateRemains() {
+ // set up.
+ int initState = Display.STATE_OFF;
+ int supportedTargetState = Display.STATE_DOZE_SUSPEND;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ doAnswer(invocation -> {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
+ return null;
+ }).when(mHolder.displayPowerState).setScreenState(anyInt());
+ // init displayoffload session and support offloading.
+ initDisplayOffloadSession();
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ // start with OFF.
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ mHolder.dpc.overrideDozeScreenState(supportedTargetState);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+ }
+
+ private void initDisplayOffloadSession() {
+ mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() {
+ @Override
+ public boolean startOffload() {
+ return true;
+ }
+
+ @Override
+ public void stopOffload() {}
+ });
+
+ mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() {
+ @Override
+ public void setDozeStateOverride(int displayState) {}
+
+ @Override
+ public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() {
+ return mDisplayOffloader;
+ }
+ };
+ }
private void advanceTime(long timeMs) {
mClock.fastForward(timeMs);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 6cde5e3..147e8f2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -32,12 +32,15 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -55,6 +58,7 @@
import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
@@ -72,6 +76,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -107,8 +112,15 @@
private LightsManager mMockedLightsManager;
@Mock
private LogicalLight mMockedBacklight;
+ @Mock
+ private DisplayManagerFlags mFlags;
+
private Handler mHandler;
+ private DisplayOffloadSession mDisplayOffloadSession;
+
+ private DisplayOffloader mDisplayOffloader;
+
private TestListener mListener = new TestListener();
private LinkedList<DisplayAddress.Physical> mAddresses = new LinkedList<>();
@@ -120,6 +132,8 @@
private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
private static final int[] BACKLIGHT_RANGE = { 1, 255 };
private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
+ private static final List<Integer> mDisplayOffloadSupportedStates
+ = new ArrayList<>(List.of(Display.STATE_DOZE_SUSPEND));
@Before
public void setUp() throws Exception {
@@ -134,7 +148,7 @@
mInjector = new Injector();
when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
- mListener, mInjector);
+ mListener, mFlags, mInjector);
spyOn(mAdapter);
doReturn(mMockedContext).when(mAdapter).getOverlayContext();
@@ -185,6 +199,8 @@
when(mMockedResources.getIntArray(
com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
.thenReturn(new int[]{});
+ doReturn(true).when(mFlags).isDisplayOffloadEnabled();
+ initDisplayOffloadSession();
}
@After
@@ -1109,6 +1125,72 @@
assertThat(info.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP).isEqualTo(0);
}
+
+ @Test
+ public void test_displayStateToSupportedState_DisplayOffloadStart()
+ throws InterruptedException {
+ // prepare a display.
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ for (Integer supportedState : mDisplayOffloadSupportedStates) {
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(
+ supportedState, 0, 0, mDisplayOffloadSession);
+ changeStateRunnable.run();
+
+ verify(mDisplayOffloader).startOffload();
+ }
+ }
+
+ @Test
+ public void test_displayStateToDozeFromDozeSuspend_DisplayOffloadStop()
+ throws InterruptedException {
+ // prepare a display.
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ Runnable changeStateToDozeSuspendRunnable = displayDevice.requestDisplayStateLocked(
+ Display.STATE_DOZE_SUSPEND, 0, 0, mDisplayOffloadSession);
+ Runnable changeStateToDozeRunnable = displayDevice.requestDisplayStateLocked(
+ Display.STATE_DOZE, 0, 0, mDisplayOffloadSession);
+ changeStateToDozeSuspendRunnable.run();
+ changeStateToDozeRunnable.run();
+
+ verify(mDisplayOffloader).stopOffload();
+ }
+
+ private void initDisplayOffloadSession() {
+ mDisplayOffloader = spy(new DisplayOffloader() {
+ @Override
+ public boolean startOffload() {
+ return true;
+ }
+
+ @Override
+ public void stopOffload() {}
+ });
+
+ mDisplayOffloadSession = new DisplayOffloadSession() {
+ @Override
+ public void setDozeStateOverride(int displayState) {}
+
+ @Override
+ public DisplayOffloader getDisplayOffloader() {
+ return mDisplayOffloader;
+ }
+ };
+ }
+
private void setupCutoutAndRoundedCorners() {
String sampleCutout = "M 507,66\n"
+ "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index 37d966d..c3322ec 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -20,6 +20,12 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.IBinder;
import android.os.PowerManager;
import androidx.test.filters.SmallTest;
@@ -31,6 +37,7 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -40,7 +47,20 @@
@SmallTest
public class HdrClamperTest {
- public static final float FLOAT_TOLERANCE = 0.0001f;
+ private static final float FLOAT_TOLERANCE = 0.0001f;
+ private static final long SEND_TIME_TOLERANCE = 100;
+
+ private static final HdrBrightnessData TEST_HDR_DATA = new HdrBrightnessData(
+ Map.of(500f, 0.6f),
+ /* brightnessIncreaseDebounceMillis= */ 1000,
+ /* brightnessIncreaseDurationMillis= */ 2000,
+ /* brightnessDecreaseDebounceMillis= */ 3000,
+ /* brightnessDecreaseDurationMillis= */4000
+ );
+
+ private static final int WIDTH = 600;
+ private static final int HEIGHT = 800;
+ private static final float MIN_HDR_PERCENT = 0.5f;
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
@@ -48,17 +68,31 @@
@Mock
private BrightnessClamperController.ClamperChangeListener mMockListener;
+ @Mock
+ private IBinder mMockBinder;
+
+ @Mock
+ private HdrClamper.Injector mMockInjector;
+
+ @Mock
+ private HdrClamper.HdrLayerInfoListener mMockHdrInfoListener;
+
OffsettableClock mClock = new OffsettableClock.Stopped();
private final TestHandler mTestHandler = new TestHandler(null, mClock);
private HdrClamper mHdrClamper;
-
+ private HdrClamper.HdrListener mHdrChangeListener;
@Before
public void setUp() {
- mHdrClamper = new HdrClamper(mMockListener, mTestHandler);
+ when(mMockInjector.getHdrListener(any(), any())).thenReturn(mMockHdrInfoListener);
+ mHdrClamper = new HdrClamper(mMockListener, mTestHandler, mMockInjector);
+ ArgumentCaptor<HdrClamper.HdrListener> listenerCaptor = ArgumentCaptor.forClass(
+ HdrClamper.HdrListener.class);
+ verify(mMockInjector).getHdrListener(listenerCaptor.capture(), eq(mTestHandler));
+ mHdrChangeListener = listenerCaptor.getValue();
configureClamper();
}
@@ -68,20 +102,23 @@
assertFalse(mTestHandler.hasMessagesOrCallbacks());
assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+ assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
@Test
- public void testClamper_AmbientLuxChangesBelowLimit() {
+ public void testClamper_AmbientLuxChangesBelowLimit_MaxDecrease() {
mHdrClamper.onAmbientLuxChange(499);
assertTrue(mTestHandler.hasMessagesOrCallbacks());
TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek();
- assertEquals(2000, msgInfo.sendTime);
+ assertSendTime(3000, msgInfo.sendTime);
assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+ assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
- mClock.fastForward(2000);
+ mClock.fastForward(3000);
mTestHandler.timeAdvance();
assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+ assertEquals(0.1f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); // (1-0.6) / 4
}
@Test
@@ -91,33 +128,65 @@
assertFalse(mTestHandler.hasMessagesOrCallbacks());
assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+ assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
@Test
public void testClamper_AmbientLuxChangesBelowLimit_ThenSlowlyAboveLimit() {
mHdrClamper.onAmbientLuxChange(499);
- mClock.fastForward(2000);
+ mClock.fastForward(3000);
mTestHandler.timeAdvance();
mHdrClamper.onAmbientLuxChange(500);
assertTrue(mTestHandler.hasMessagesOrCallbacks());
TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek();
- assertEquals(3000, msgInfo.sendTime); // 2000 + 1000
+ assertSendTime(4000, msgInfo.sendTime); // 3000 + 1000
mClock.fastForward(1000);
mTestHandler.timeAdvance();
assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+ assertEquals(0.2f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); // (1-0.6) / 2
+ }
+
+ @Test
+ public void testClamper_HdrOff_ThenAmbientLuxChangesBelowLimit() {
+ mHdrChangeListener.onHdrVisible(false);
+ mHdrClamper.onAmbientLuxChange(499);
+
+ assertFalse(mTestHandler.hasMessagesOrCallbacks());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+ assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testClamper_HdrOff_ThenAmbientLuxChangesBelowLimit_ThenHdrOn() {
+ mHdrChangeListener.onHdrVisible(false);
+ mHdrClamper.onAmbientLuxChange(499);
+ mHdrChangeListener.onHdrVisible(true);
+
+ assertTrue(mTestHandler.hasMessagesOrCallbacks());
+ TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek();
+ assertSendTime(3000, msgInfo.sendTime);
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+
+ mClock.fastForward(3000);
+ mTestHandler.timeAdvance();
+ assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+ assertEquals(0.1f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ }
+
+ // MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
+ // (in Handler.sendMessageDelayed) and then by subtracting SystemClock.uptimeMillis()
+ // (in TestHandler.sendMessageAtTime, there might be several milliseconds difference between
+ // SystemClock.uptimeMillis() calls, and subtracted value might be greater than added.
+ private static void assertSendTime(long expectedTime, long sendTime) {
+ assertTrue(expectedTime >= sendTime);
+ assertTrue(expectedTime - SEND_TIME_TOLERANCE < sendTime);
}
private void configureClamper() {
- HdrBrightnessData data = new HdrBrightnessData(
- Map.of(500f, 0.6f),
- /* brightnessIncreaseDebounceMillis= */ 1000,
- /* brightnessIncreaseDurationMillis= */ 1500,
- /* brightnessDecreaseDebounceMillis= */ 2000,
- /* brightnessDecreaseDurationMillis= */2500
- );
- mHdrClamper.resetHdrConfig(data);
+ mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, MIN_HDR_PERCENT, mMockBinder);
+ mHdrChangeListener.onHdrVisible(true);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
index 880501f..f5c6bb2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
@@ -141,6 +141,32 @@
assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
}
+ @Test
+ public void dozeScreenStateOverrideToDozeSuspend_DozePolicy_updateDisplayStateToDozeSuspend() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ mDisplayStateController.overrideDozeScreenState(Display.STATE_DOZE_SUSPEND);
+
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
+ !DISPLAY_IN_TRANSITION);
+
+ assertEquals(state, Display.STATE_DOZE_SUSPEND);
+ }
+
+ @Test
+ public void dozeScreenStateOverrideToDozeSuspend_OffPolicy_displayRemainOff() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+ mDisplayStateController.overrideDozeScreenState(Display.STATE_DOZE_SUSPEND);
+
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
+ !DISPLAY_IN_TRANSITION);
+
+ assertEquals(state, Display.STATE_OFF);
+ }
+
private void validDisplayState(int policy, int displayState, boolean isEnabled,
boolean isInTransition) {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index 52044bf..de8b308 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -117,7 +117,9 @@
Build.VERSION_CODES.CUR_DEVELOPMENT,
Build.VERSION.INCREMENTAL);
mMockSystem.system().validateFinalState();
- mInstallPackageHelper = new InstallPackageHelper(mPmService, mock(AppDataHelper.class));
+ mInstallPackageHelper = new InstallPackageHelper(mPmService, mock(AppDataHelper.class),
+ mock(RemovePackageHelper.class), mock(DeletePackageHelper.class),
+ mock(BroadcastHelper.class));
}
@NonNull
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
index d6a4d40..931b38d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -34,6 +34,7 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.mock
@RunWith(JUnit4::class)
class DeletePackageHelperTest {
@@ -79,7 +80,8 @@
whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0))
whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1)
- val dph = DeletePackageHelper(mPms)
+ val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+ mock(BroadcastHelper::class.java))
val result = dph.deletePackageX("a.data.package", 1L, 1, 0, false)
assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED)
@@ -97,7 +99,8 @@
whenever(mUserManagerInternal.getUserInfo(parentId)).thenReturn(
UserInfo(userId, "testparent", 0))
- val dph = DeletePackageHelper(mPms)
+ val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+ mock(BroadcastHelper::class.java))
val result = dph.deletePackageX("a.data.package", 1L, userId, 0, false)
assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED)
@@ -112,7 +115,8 @@
whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
.thenReturn(PERMISSION_DENIED)
- val dph = DeletePackageHelper(mPms)
+ val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+ mock(BroadcastHelper::class.java))
val result = dph.deletePackageX("a.data.package", 1L, 1,
PackageManager.DELETE_SYSTEM_APP, false)
@@ -133,7 +137,8 @@
whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
.thenReturn(PERMISSION_DENIED)
- val dph = DeletePackageHelper(mPms)
+ val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+ mock(BroadcastHelper::class.java))
val result = dph.deletePackageX("a.data.package", 1L, userId,
PackageManager.DELETE_SYSTEM_APP, false)
@@ -150,7 +155,8 @@
whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
.thenReturn(PERMISSION_DENIED)
- val dph = DeletePackageHelper(mPms)
+ val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+ mock(BroadcastHelper::class.java))
val result = dph.deletePackageX("a.data.package", 1L, 1,
PackageManager.DELETE_SYSTEM_APP, false)
@@ -164,7 +170,8 @@
whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
.thenReturn(PERMISSION_GRANTED)
- val dph = DeletePackageHelper(mPms)
+ val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+ mock(BroadcastHelper::class.java))
val result = dph.deletePackageX("a.data.package", 1L, 1,
PackageManager.DELETE_SYSTEM_APP, false)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
index 9f1cec3..cf81f0a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -39,7 +39,7 @@
override fun setup() {
super.setup()
distractingPackageHelper = DistractingPackageHelper(
- pms, rule.mocks().injector, broadcastHelper, suspendPackageHelper)
+ pms, broadcastHelper, suspendPackageHelper)
}
@Test
@@ -50,12 +50,11 @@
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(), nullable())
+ verify(broadcastHelper).sendDistractingPackagesChanged(any(Computer::class.java),
+ pkgListCaptor.capture(), any(), any(), flagsCaptor.capture())
- val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
+ val modifiedPackages = pkgListCaptor.value
+ val distractionFlags = flagsCaptor.value
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(distractionFlags).isEqualTo(PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
assertThat(unactionedPackages).isEmpty()
@@ -75,10 +74,8 @@
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(),
- nullable())
+ verify(broadcastHelper, never()).sendDistractingPackagesChanged(
+ any(), any(), any(), any(), any())
assertThat(unactionedPackages).isEmpty()
}
@@ -154,11 +151,11 @@
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(), nullable())
- val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
+ verify(broadcastHelper).sendDistractingPackagesChanged(
+ any(Computer::class.java), pkgListCaptor.capture(), any(), eq(TEST_USER_ID),
+ flagsCaptor.capture())
+ val modifiedPackages = pkgListCaptor.value
+ val distractionFlags = flagsCaptor.value
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(distractionFlags).isEqualTo(PackageManager.RESTRICTION_NONE)
}
@@ -170,9 +167,8 @@
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(), nullable())
+ verify(broadcastHelper, never()).sendDistractingPackagesChanged(
+ any(), any(), any(), any(), any())
}
@Test
@@ -189,22 +185,21 @@
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(), nullable())
+ verify(broadcastHelper, never()).sendDistractingPackagesChanged(
+ any(), any(), any(), any(), any())
}
@Test
fun sendDistractingPackagesChanged() {
- distractingPackageHelper.sendDistractingPackagesChanged(packagesToChange, uidsToChange,
- TEST_USER_ID, PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
+ broadcastHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
+ packagesToChange, uidsToChange, TEST_USER_ID,
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
testHandler.flush()
- verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
- nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable(), nullable())
+ verify(broadcastHelper).sendDistractingPackagesChanged(any(Computer::class.java),
+ pkgListCaptor.capture(), uidsCaptor.capture(), eq(TEST_USER_ID), any())
- var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+ var changedPackages = pkgListCaptor.value
+ var changedUids = uidsCaptor.value
assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(changedUids).asList().containsExactly(
packageSetting1.appId, packageSetting2.appId)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
index 5fd270e..eb00164 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
@@ -17,7 +17,6 @@
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
@@ -68,7 +67,11 @@
lateinit var protectedPackages: ProtectedPackages
@Captor
- lateinit var bundleCaptor: ArgumentCaptor<Bundle>
+ lateinit var pkgListCaptor: ArgumentCaptor<Array<String>>
+ @Captor
+ lateinit var flagsCaptor: ArgumentCaptor<Int>
+ @Captor
+ lateinit var uidsCaptor: ArgumentCaptor<IntArray>
@Rule
@JvmField
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index c5db5db..6c44fd0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -17,12 +17,12 @@
package com.android.server.pm;
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.content.Intent;
import android.content.pm.PackageInstaller;
@@ -63,9 +63,7 @@
@Before
public void setup() {
- when(mMockSystem.mocks().getInjector().getHandler()).thenReturn(mHandler);
- mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper(
- mMockSystem.mocks().getInjector());
+ mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper();
}
@@ -80,7 +78,7 @@
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -93,7 +91,7 @@
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
- null /* broadcastAllowList */);
+ null /* broadcastAllowList */, mHandler);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
@@ -101,7 +99,7 @@
mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -114,7 +112,7 @@
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -138,7 +136,7 @@
// Notify for user 10
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -155,7 +153,7 @@
mPackageMonitorCallbackHelper.notifyPackageChanged(FAKE_PACKAGE_NAME,
false /* dontKillApp */, components, FAKE_PACKAGE_UID, null /* reason */,
new int[]{0} /* userIds */, null /* instantUserIds */,
- null /* broadcastAllowList */);
+ null /* broadcastAllowList */, mHandler);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -183,7 +181,8 @@
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(FAKE_PACKAGE_NAME,
FAKE_PACKAGE_UID, new int[]{0} /* userIds */, new int[0], false /* isArchived */,
- PackageInstaller.DATA_LOADER_TYPE_STREAMING, null /* broadcastAllowList */);
+ PackageInstaller.DATA_LOADER_TYPE_STREAMING, null /* broadcastAllowList */,
+ mHandler);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -207,7 +206,7 @@
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyResourcesChanged(true /* mediaStatus */,
true /* replacing */, new String[]{FAKE_PACKAGE_NAME},
- new int[]{FAKE_PACKAGE_UID} /* uids */);
+ new int[]{FAKE_PACKAGE_UID} /* uids */, mHandler);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -240,7 +239,7 @@
mPackageMonitorCallbackHelper.onUserRemoved(10);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -256,7 +255,7 @@
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, broadcastAllowList);
+ null /* instantUserIds */, broadcastAllowList, mHandler);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
}
@@ -272,7 +271,7 @@
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, broadcastAllowList);
+ null /* instantUserIds */, broadcastAllowList, mHandler);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -288,7 +287,7 @@
Process.SYSTEM_UID);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, broadcastAllowList);
+ null /* instantUserIds */, broadcastAllowList, mHandler);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
}
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 6797576..4240373 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -22,12 +22,11 @@
import android.os.PersistableBundle
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
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -44,17 +43,10 @@
testHandler.flush()
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
- verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED),
- nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable(), nullable())
- verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
- nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(),
- nullable(), nullable())
- verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
- nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(),
- nullable(), nullable())
+ verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java),
+ eq(Intent.ACTION_PACKAGES_SUSPENDED), pkgListCaptor.capture(), any(), any(), any())
- var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ var modifiedPackages = pkgListCaptor.value
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(failedNames).isEmpty()
}
@@ -146,6 +138,7 @@
null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
TEST_USER_ID, deviceOwnerUid, false /* forQuietMode */, false /* quarantined */)
testHandler.flush()
+ Mockito.clearInvocations(broadcastHelper)
assertThat(failedNames).isEmpty()
failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
targetPackages, false /* suspended */, null /* appExtras */,
@@ -154,17 +147,13 @@
testHandler.flush()
verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
- verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
- nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable(), nullable())
- verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
- nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
- nullable(), nullable(), nullable())
- verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
- nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
- nullable(), nullable(), nullable())
+ verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java),
+ eq(Intent.ACTION_PACKAGES_UNSUSPENDED), pkgListCaptor.capture(), any(), any(),
+ any())
+ verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(any(Computer::class.java),
+ any(), any(), any())
- var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ var modifiedPackages = pkgListCaptor.value
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(failedNames).isEmpty()
}
@@ -206,7 +195,7 @@
testHandler.flush()
assertThat(failedNames).isEmpty()
- val result = suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
+ val result = SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)!!
assertThat(result.getString(TEST_PACKAGE_1)).isEqualTo(TEST_PACKAGE_1)
@@ -222,14 +211,15 @@
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
false /* forQuietMode */, false /* quarantined */)
testHandler.flush()
+ Mockito.clearInvocations(broadcastHelper)
assertThat(failedNames).isEmpty()
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
- assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
+ assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNotNull()
- assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
+ assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull()
suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
@@ -238,23 +228,18 @@
testHandler.flush()
verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
- verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
- nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable(), nullable())
- verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
- nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
- nullable(), nullable(), nullable())
- verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
- nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
- nullable(), nullable(), nullable())
+ verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java),
+ eq(Intent.ACTION_PACKAGES_UNSUSPENDED), any(), any(), any(), any())
+ verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(any(Computer::class.java),
+ any(), any(), any())
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull()
- assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
+ assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
- assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
+ assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull()
}
@@ -319,39 +304,4 @@
assertThat(result.title).isEqualTo(TEST_PACKAGE_1)
}
-
- @Test
- @Throws(Exception::class)
- fun sendPackagesSuspendedForUser() {
- suspendPackageHelper.sendPackagesSuspendedForUser(
- Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, false, TEST_USER_ID)
- testHandler.flush()
- verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
- nullable())
-
- var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
- assertThat(changedUids).asList().containsExactly(
- packageSetting1.appId, packageSetting2.appId)
- }
-
- @Test
- @Throws(Exception::class)
- fun sendPackagesSuspendModifiedForUser() {
- suspendPackageHelper.sendPackagesSuspendedForUser(
- Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange, false, TEST_USER_ID)
- testHandler.flush()
- verify(broadcastHelper).sendPackageBroadcast(
- eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
- nullable())
-
- var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
- assertThat(modifiedUids).asList().containsExactly(
- packageSetting1.appId, packageSetting2.appId)
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index a0bca3b..24ad976 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -50,7 +50,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.platform.test.annotations.FlakyTest;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.view.InputDevice;
@@ -60,6 +59,7 @@
import android.view.accessibility.MagnificationAnimationCallback;
import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.FlakyTest;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 41af9e3..1e6306c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -57,6 +57,7 @@
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorCallback;
@@ -100,6 +101,7 @@
import android.os.RemoteException;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArraySet;
@@ -211,6 +213,9 @@
private static final String TEST_SITE = "http://test";
@Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule
public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
InstrumentationRegistry.getInstrumentation().getUiAutomation(),
Manifest.permission.CREATE_VIRTUAL_DEVICE);
@@ -328,6 +333,11 @@
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+ mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY);
+ mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME);
+
doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
@@ -1450,6 +1460,50 @@
}
@Test
+ public void openPermissionControllerOnVirtualDisplay_displayOnRemoteDevices_startsWhenFlagIsEnabled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
+ DISPLAY_ID_1);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ActivityInfo activityInfo = getActivityInfo(
+ PERMISSION_CONTROLLER_PACKAGE_NAME,
+ PERMISSION_CONTROLLER_PACKAGE_NAME,
+ /* displayOnRemoveDevices */ true,
+ /* targetDisplayCategory */ null);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfo, mAssociationInfo.getDisplayName());
+ gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+
+ verify(mContext, never()).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openPermissionControllerOnVirtualDisplay_dontDisplayOnRemoteDevices_startsWhenFlagIsEnabled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
+ DISPLAY_ID_1);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ActivityInfo activityInfo = getActivityInfo(
+ PERMISSION_CONTROLLER_PACKAGE_NAME,
+ PERMISSION_CONTROLLER_PACKAGE_NAME,
+ /* displayOnRemoveDevices */ false,
+ /* targetDisplayCategory */ null);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfo, mAssociationInfo.getDisplayName());
+ gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+
+ verify(mContext).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index c65452a..90d9452 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -49,6 +49,7 @@
private static final int VIRTUAL_DEVICE_ID = 42;
private static final String PERSISTENT_ID = "persistentId";
private static final String DEVICE_NAME = "VirtualDeviceName";
+ private static final String DISPLAY_NAME = "DisplayName";
@Mock
private IVirtualDevice mVirtualDevice;
@@ -87,7 +88,8 @@
@Test
public void parcelable_shouldRecreateSuccessfully() {
VirtualDevice originalDevice =
- new VirtualDevice(mVirtualDevice, VIRTUAL_DEVICE_ID, PERSISTENT_ID, DEVICE_NAME);
+ new VirtualDevice(mVirtualDevice, VIRTUAL_DEVICE_ID, PERSISTENT_ID, DEVICE_NAME,
+ DISPLAY_NAME);
Parcel parcel = Parcel.obtain();
originalDevice.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@@ -96,6 +98,7 @@
assertThat(device.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID);
assertThat(device.getPersistentDeviceId()).isEqualTo(PERSISTENT_ID);
assertThat(device.getName()).isEqualTo(DEVICE_NAME);
+ assertThat(device.getDisplayName().toString()).isEqualTo(DISPLAY_NAME);
}
@RequiresFlagsEnabled(Flags.FLAG_VDM_PUBLIC_APIS)
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 1c48b8a..ef5270e 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -82,6 +82,7 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
+ /* permissionDialogComponent */ null,
/* activityListener= */ null,
/* pipBlockedCallback= */ null,
/* activityBlockedCallback= */ null,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index dee7780..37a6d22 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -781,7 +781,7 @@
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- mService.onCleanupUser(PRIMARY_USER_ID);
+ mService.onUserStopped(PRIMARY_USER_ID);
assertNull(mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID));
assertTrue(mLocalService.unlockUserWithToken(handle, token, PRIMARY_USER_ID));
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
index 2c9ba34..e8b7ad7 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
@@ -169,7 +169,7 @@
assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
- mService.onCleanupUser(PRIMARY_USER_ID);
+ mService.onUserStopped(PRIMARY_USER_ID);
assertNull(mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID));
assertTrue(mLocalService.unlockUserWithToken(handle, token, PRIMARY_USER_ID));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index f65cb93..40ac7b1c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2855,14 +2855,14 @@
.setTask(sourceRecord.getTask()).build();
secondRecord.showStartingWindow(null /* prev */, true /* newTask */, false,
true /* startActivity */, sourceRecord);
- assertTrue(secondRecord.mAllowIconSplashScreen);
+ assertFalse(secondRecord.mSplashScreenStyleSolidColor);
secondRecord.onStartingWindowDrawn();
final ActivityRecord finalRecord = new ActivityBuilder(mAtm)
.setTask(sourceRecord.getTask()).build();
finalRecord.showStartingWindow(null /* prev */, true /* newTask */, false,
true /* startActivity */, secondRecord);
- assertFalse(finalRecord.mAllowIconSplashScreen);
+ assertTrue(finalRecord.mSplashScreenStyleSolidColor);
}
@Test
diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py
index 4a2e37e..2e82beb 100644
--- a/tools/lint/fix/soong_lint_fix.py
+++ b/tools/lint/fix/soong_lint_fix.py
@@ -14,6 +14,7 @@
import argparse
import json
+import functools
import os
import shutil
import subprocess
@@ -28,6 +29,7 @@
PATH_PREFIX = "out/soong/.intermediates"
PATH_SUFFIX = "android_common/lint"
FIX_ZIP = "suggested-fixes.zip"
+MODULE_JAVA_DEPS = "out/soong/module_bp_java_deps.json"
class SoongModule:
@@ -49,11 +51,26 @@
print(f"Found module {partial_path}/{self._name}.")
self._path = f"{PATH_PREFIX}/{partial_path}/{self._name}/{PATH_SUFFIX}"
+ def find_java_deps(self, module_java_deps):
+ """Finds the dependencies of a Java module in the loaded module_bp_java_deps.json.
+
+ Returns:
+ A list of module names.
+ """
+ if self._name not in module_java_deps:
+ raise Exception(f"Module {self._name} not found!")
+
+ return module_java_deps[self._name]["dependencies"]
+
@property
def name(self):
return self._name
@property
+ def path(self):
+ return self._path
+
+ @property
def lint_report(self):
return f"{self._path}/lint-report.txt"
@@ -62,52 +79,25 @@
return f"{self._path}/{FIX_ZIP}"
-class SoongLintFix:
+class SoongLintWrapper:
"""
- This class creates a command line tool that will apply lint fixes to the
- platform via the necessary combination of soong and shell commands.
+ This class wraps the necessary calls to Soong and/or shell commands to lint
+ platform modules and apply suggested fixes if desired.
- It breaks up these operations into a few "private" methods that are
- intentionally exposed so experimental code can tweak behavior.
-
- The entry point, `run`, will apply lint fixes using the intermediate
- `suggested-fixes` directory that soong creates during its invocation of
- lint.
-
- Basic usage:
- ```
- from soong_lint_fix import SoongLintFix
-
- opts = SoongLintFixOptions()
- opts.parse_args(sys.argv)
- SoongLintFix(opts).run()
- ```
+ It breaks up these operations into a few methods that are available to
+ sub-classes (see SoongLintFix for an example).
"""
- def __init__(self, opts):
- self._opts = opts
+ def __init__(self, check=None, lint_module=None):
+ self._check = check
+ self._lint_module = lint_module
self._kwargs = None
- self._modules = []
-
- def run(self):
- """
- Run the script
- """
- self._setup()
- self._find_modules()
- self._lint()
-
- if not self._opts.no_fix:
- self._fix()
-
- if self._opts.print:
- self._print()
def _setup(self):
env = os.environ.copy()
- if self._opts.check:
- env["ANDROID_LINT_CHECK"] = self._opts.check
- if self._opts.lint_module:
- env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._opts.lint_module
+ if self._check:
+ env["ANDROID_LINT_CHECK"] = self._check
+ if self._lint_module:
+ env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._lint_module
self._kwargs = {
"env": env,
@@ -117,7 +107,10 @@
os.chdir(ANDROID_BUILD_TOP)
- print("Refreshing soong modules...")
+ @functools.cached_property
+ def _module_info(self):
+ """Returns the JSON content of module-info.json."""
+ print("Refreshing Soong modules...")
try:
os.mkdir(ANDROID_PRODUCT_OUT)
except OSError:
@@ -125,19 +118,54 @@
subprocess.call(f"{SOONG_UI} --make-mode {PRODUCT_OUT}/module-info.json", **self._kwargs)
print("done.")
-
- def _find_modules(self):
with open(f"{ANDROID_PRODUCT_OUT}/module-info.json") as f:
- module_info = json.load(f)
+ return json.load(f)
- for module_name in self._opts.modules:
- module = SoongModule(module_name)
- module.find(module_info)
- self._modules.append(module)
+ def _find_module(self, module_name):
+ """Returns a SoongModule from a module name.
- def _lint(self):
+ Ensures that the module is known to Soong.
+ """
+ module = SoongModule(module_name)
+ module.find(self._module_info)
+ return module
+
+ def _find_modules(self, module_names):
+ modules = []
+ for module_name in module_names:
+ modules.append(self._find_module(module_name))
+ return modules
+
+ @functools.cached_property
+ def _module_java_deps(self):
+ """Returns the JSON content of module_bp_java_deps.json."""
+ print("Refreshing Soong Java deps...")
+ subprocess.call(f"{SOONG_UI} --make-mode {MODULE_JAVA_DEPS}", **self._kwargs)
+ print("done.")
+
+ with open(f"{MODULE_JAVA_DEPS}") as f:
+ return json.load(f)
+
+ def _find_module_java_deps(self, module):
+ """Returns a list a dependencies for a module.
+
+ Args:
+ module: A SoongModule.
+
+ Returns:
+ A list of SoongModule.
+ """
+ deps = []
+ dep_names = module.find_java_deps(self._module_java_deps)
+ for dep_name in dep_names:
+ dep = SoongModule(dep_name)
+ dep.find(self._module_info)
+ deps.append(dep)
+ return deps
+
+ def _lint(self, modules):
print("Cleaning up any old lint results...")
- for module in self._modules:
+ for module in modules:
try:
os.remove(f"{module.lint_report}")
os.remove(f"{module.suggested_fixes}")
@@ -145,13 +173,13 @@
pass
print("done.")
- target = " ".join([ module.lint_report for module in self._modules ])
+ target = " ".join([ module.lint_report for module in modules ])
print(f"Generating {target}")
subprocess.call(f"{SOONG_UI} --make-mode {target}", **self._kwargs)
print("done.")
- def _fix(self):
- for module in self._modules:
+ def _fix(self, modules):
+ for module in modules:
print(f"Copying suggested fixes for {module.name} to the tree...")
with zipfile.ZipFile(f"{module.suggested_fixes}") as zip:
for name in zip.namelist():
@@ -161,13 +189,40 @@
shutil.copyfileobj(src, dst)
print("done.")
- def _print(self):
- for module in self._modules:
+ def _print(self, modules):
+ for module in modules:
print(f"### lint-report.txt {module.name} ###", end="\n\n")
with open(module.lint_report, "r") as f:
print(f.read())
+class SoongLintFix(SoongLintWrapper):
+ """
+ Basic usage:
+ ```
+ from soong_lint_fix import SoongLintFix
+
+ opts = SoongLintFixOptions()
+ opts.parse_args()
+ SoongLintFix(opts).run()
+ ```
+ """
+ def __init__(self, opts):
+ super().__init__(check=opts.check, lint_module=opts.lint_module)
+ self._opts = opts
+
+ def run(self):
+ self._setup()
+ modules = self._find_modules(self._opts.modules)
+ self._lint(modules)
+
+ if not self._opts.no_fix:
+ self._fix(modules)
+
+ if self._opts.print:
+ self._print(modules)
+
+
class SoongLintFixOptions:
"""Options for SoongLintFix"""
diff --git a/tools/lint/utils/enforce_permission_counter.py b/tools/lint/utils/enforce_permission_counter.py
index b5c2ffe..a4c00f7 100644
--- a/tools/lint/utils/enforce_permission_counter.py
+++ b/tools/lint/utils/enforce_permission_counter.py
@@ -16,57 +16,38 @@
import soong_lint_fix
-# Libraries that constitute system_server.
-# It is non-trivial to keep in sync with services/Android.bp as some
-# module are post-processed (e.g, services.core).
-TARGETS = [
- "services.core.unboosted",
- "services.accessibility",
- "services.appprediction",
- "services.appwidget",
- "services.autofill",
- "services.backup",
- "services.companion",
- "services.contentcapture",
- "services.contentsuggestions",
- "services.coverage",
- "services.devicepolicy",
- "services.midi",
- "services.musicsearch",
- "services.net",
- "services.people",
- "services.print",
- "services.profcollect",
- "services.restrictions",
- "services.searchui",
- "services.smartspace",
- "services.systemcaptions",
- "services.translation",
- "services.texttospeech",
- "services.usage",
- "services.usb",
- "services.voiceinteraction",
- "services.wallpapereffectsgeneration",
- "services.wifi",
-]
+CHECK = "AnnotatedAidlCounter"
+LINT_MODULE = "AndroidUtilsLintChecker"
-
-class EnforcePermissionMigratedCounter:
+class EnforcePermissionMigratedCounter(soong_lint_fix.SoongLintWrapper):
"""Wrapper around lint_fix to count the number of AIDL methods annotated."""
+
+ def __init__(self):
+ super().__init__(check=CHECK, lint_module=LINT_MODULE)
+
def run(self):
- opts = soong_lint_fix.SoongLintFixOptions()
- opts.check = "AnnotatedAidlCounter"
- opts.lint_module = "AndroidUtilsLintChecker"
- opts.no_fix = True
- opts.modules = TARGETS
+ self._setup()
- self.linter = soong_lint_fix.SoongLintFix(opts)
- self.linter.run()
- self.parse_lint_reports()
+ # Analyze the dependencies of the "services" module and the module
+ # "services.core.unboosted".
+ service_module = self._find_module("services")
+ dep_modules = self._find_module_java_deps(service_module) + \
+ [self._find_module("services.core.unboosted")]
- def parse_lint_reports(self):
+ # Skip dependencies that are not services. Skip the "services.core"
+ # module which is analyzed via "services.core.unboosted".
+ modules = []
+ for module in dep_modules:
+ if "frameworks/base/services" not in module.path:
+ continue
+ if module.name == "services.core":
+ continue
+ modules.append(module)
+
+ self._lint(modules)
+
counts = { "unannotated": 0, "enforced": 0, "notRequired": 0 }
- for module in self.linter._modules:
+ for module in modules:
with open(module.lint_report, "r") as f:
content = f.read()
keys = dict(re.findall(r'(\w+)=(\d+)', content))