Merge "Introduce a new APK attribute: emergencyInstaller" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index cd991c7..9ee74e3 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -36,6 +36,7 @@
":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
":android.location.flags-aconfig-java{.generated_srcjars}",
+ ":android.media.codec-aconfig-java{.generated_srcjars}",
":android.media.tv.flags-aconfig-java{.generated_srcjars}",
":android.multiuser.flags-aconfig-java{.generated_srcjars}",
":android.net.platform.flags-aconfig-java{.generated_srcjars}",
diff --git a/core/api/current.txt b/core/api/current.txt
index 283e429..cb937d2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9357,9 +9357,12 @@
method public long getDataBytes();
method public long getExternalCacheBytes();
method public void writeToParcel(android.os.Parcel, int);
- field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0; // 0x0
- field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1; // 0x1
- field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 2; // 0x2
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 3; // 0x3
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE = 2; // 0x2
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT = 0; // 0x0
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 4; // 0x4
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE = 1; // 0x1
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 5; // 0x5
field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
}
@@ -12407,7 +12410,7 @@
method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback);
method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
- method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibilityOptions(boolean, boolean);
+ method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibility(@NonNull android.content.pm.LauncherApps.ArchiveCompatibilityParams);
method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
@@ -12421,6 +12424,12 @@
field public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}
+ @FlaggedApi("android.content.pm.archiving") public static class LauncherApps.ArchiveCompatibilityParams {
+ ctor public LauncherApps.ArchiveCompatibilityParams();
+ method public void setEnableIconOverlay(boolean);
+ method public void setEnableUnarchivalConfirmation(boolean);
+ }
+
public abstract static class LauncherApps.Callback {
ctor public LauncherApps.Callback();
method public abstract void onPackageAdded(String, android.os.UserHandle);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 783bebd..1273da7 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -350,7 +350,9 @@
package android.os {
public class ArtModuleServiceManager {
+ method @FlaggedApi("android.content.pm.use_art_service_v2") @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdPreRebootServiceRegisterer();
method @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdServiceRegisterer();
+ method @FlaggedApi("android.content.pm.use_art_service_v2") @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getDexoptChrootSetupServiceRegisterer();
}
public static final class ArtModuleServiceManager.ServiceRegisterer {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 66e03db..a6db0c0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3944,7 +3944,9 @@
method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender);
method @Nullable @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public android.content.pm.DataLoaderParams getDataLoaderParams();
+ method @FlaggedApi("android.content.pm.set_pre_verified_domains") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public java.util.Set<java.lang.String> getPreVerifiedDomains();
method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void removeFile(int, @NonNull String);
+ method @FlaggedApi("android.content.pm.set_pre_verified_domains") @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public void setPreVerifiedDomains(@NonNull java.util.Set<java.lang.String>);
}
public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
@@ -4504,6 +4506,7 @@
method @NonNull public android.credentials.selection.RequestToken getRequestToken();
method @NonNull public String getType();
method public boolean hasPermissionToOverrideDefault();
+ method public boolean isShowAllOptionsRequested();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.RequestInfo> CREATOR;
field @NonNull public static final String TYPE_CREATE = "android.credentials.selection.TYPE_CREATE";
@@ -10389,6 +10392,7 @@
public final class ConfigUpdate {
field public static final String ACTION_UPDATE_CARRIER_ID_DB = "android.os.action.UPDATE_CARRIER_ID_DB";
field public static final String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS";
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String ACTION_UPDATE_CONFIG = "android.os.action.UPDATE_CONFIG";
field public static final String ACTION_UPDATE_CONVERSATION_ACTIONS = "android.intent.action.UPDATE_CONVERSATION_ACTIONS";
field public static final String ACTION_UPDATE_CT_LOGS = "android.intent.action.UPDATE_CT_LOGS";
field public static final String ACTION_UPDATE_EMERGENCY_NUMBER_DB = "android.os.action.UPDATE_EMERGENCY_NUMBER_DB";
@@ -10398,6 +10402,7 @@
field public static final String ACTION_UPDATE_PINS = "android.intent.action.UPDATE_PINS";
field public static final String ACTION_UPDATE_SMART_SELECTION = "android.intent.action.UPDATE_SMART_SELECTION";
field public static final String ACTION_UPDATE_SMS_SHORT_CODES = "android.intent.action.UPDATE_SMS_SHORT_CODES";
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String EXTRA_DOMAIN = "android.os.extra.DOMAIN";
field public static final String EXTRA_REQUIRED_HASH = "android.os.extra.REQUIRED_HASH";
field public static final String EXTRA_VERSION = "android.os.extra.VERSION";
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a21d7c4..4a048bd 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1347,8 +1347,8 @@
}
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestInfo implements android.os.Parcelable {
- method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String, boolean, @NonNull java.util.List<java.lang.String>);
- method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String, boolean);
+ method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String, boolean, @NonNull java.util.List<java.lang.String>, boolean);
+ method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String, boolean, boolean);
}
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestToken {
diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java
index 87d97d5..7bfaef4 100644
--- a/core/java/android/app/usage/StorageStats.java
+++ b/core/java/android/app/usage/StorageStats.java
@@ -40,28 +40,79 @@
/** @hide */ public long apkBytes;
/** @hide */ public long libBytes;
/** @hide */ public long dmBytes;
+ /** @hide */ public long dexoptBytes;
+ /** @hide */ public long curProfBytes;
+ /** @hide */ public long refProfBytes;
/** @hide */ public long externalCacheBytes;
- /** Represents all .apk files in application code path.
+ /**
+ * Represents all nonstale dexopt and runtime artifacts of application.
+ * This includes AOT-compiled code and other data that can speed up app execution.
+ * For more detailed information, read the
+ * <a href="https://source.android.com/docs/core/runtime/jit-compiler#flow">JIT compiler</a>
+ * guide.
+ *
+ * Dexopt artifacts become stale when one of their dependencies
+ * has changed. They may be cleaned up or replaced by ART Services at any time.
+ *
+ * For a preload app, this type includes dexopt artifacts on readonly partitions
+ * if they are up-to-date.
+ *
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the sum of sizes for files of this type. The sum might include the size of data
+ * that is part of appBytes, dataBytes or cacheBytes.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT = 0;
+
+ /**
+ * Represents reference profile of application.
+ *
+ * Reference profiles are the ones used during the last profile-guided dexopt.
+ * If the last dexopt wasn't profile-guided, then these profiles were not used.
+ *
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the size of files of this type.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE = 1;
+
+ /**
+ * Represents current profile of application.
+ *
+ * Current profiles may or may not be used during the next profile-guided dexopt.
+ *
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the size of files of this type. This size fluctuates regularly,
+ * it goes up when the user uses more and more classes/methods and comes down when
+ * a deamon merges this into the ref profile and does profile-guided dexopt.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE = 2;
+
+ /**
+ * Represents all .apk files in application code path.
* Can be used as an input to {@link #getAppBytesByDataType(int)}
* to get the sum of sizes for files of this type.
*/
@FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
- public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0;
+ public static final int APP_DATA_TYPE_FILE_TYPE_APK = 3;
- /** Represents all .dm files in application code path.
+ /**
+ * Represents all .dm files in application code path.
* Can be used as an input to {@link #getAppBytesByDataType(int)}
* to get the sum of sizes for files of this type.
*/
@FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
- public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1;
+ public static final int APP_DATA_TYPE_FILE_TYPE_DM = 4;
- /** Represents lib/ in application code path.
+ /**
+ * Represents lib/ in application code path.
* Can be used as an input to {@link #getAppBytesByDataType(int)}
* to get the size of lib/ directory.
*/
@FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
- public static final int APP_DATA_TYPE_LIB = 2;
+ public static final int APP_DATA_TYPE_LIB = 5;
/**
* Keep in sync with the file types defined above.
@@ -69,6 +120,9 @@
*/
@FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
@IntDef(flag = false, value = {
+ APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT,
+ APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE,
+ APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE,
APP_DATA_TYPE_FILE_TYPE_APK,
APP_DATA_TYPE_FILE_TYPE_DM,
APP_DATA_TYPE_LIB,
@@ -103,6 +157,9 @@
@FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
public long getAppBytesByDataType(@AppDataType int dataType) {
switch (dataType) {
+ case APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT: return dexoptBytes;
+ case APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE: return refProfBytes;
+ case APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE: return curProfBytes;
case APP_DATA_TYPE_FILE_TYPE_APK: return apkBytes;
case APP_DATA_TYPE_LIB: return libBytes;
case APP_DATA_TYPE_FILE_TYPE_DM: return dmBytes;
@@ -161,6 +218,9 @@
this.codeBytes = in.readLong();
this.dataBytes = in.readLong();
this.cacheBytes = in.readLong();
+ this.dexoptBytes = in.readLong();
+ this.refProfBytes = in.readLong();
+ this.curProfBytes = in.readLong();
this.apkBytes = in.readLong();
this.libBytes = in.readLong();
this.dmBytes = in.readLong();
@@ -177,6 +237,9 @@
dest.writeLong(codeBytes);
dest.writeLong(dataBytes);
dest.writeLong(cacheBytes);
+ dest.writeLong(dexoptBytes);
+ dest.writeLong(refProfBytes);
+ dest.writeLong(curProfBytes);
dest.writeLong(apkBytes);
dest.writeLong(libBytes);
dest.writeLong(dmBytes);
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index ea69a2b..9f31aec 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -21,6 +21,7 @@
import android.content.pm.IOnChecksumsReadyListener;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.PackageInstaller;
+import android.content.pm.verify.domain.DomainSet;
import android.content.IntentSender;
import android.os.ParcelFileDescriptor;
@@ -73,4 +74,7 @@
ParcelFileDescriptor getAppMetadataFd();
ParcelFileDescriptor openWriteAppMetadata();
void removeAppMetadata();
+
+ void setPreVerifiedDomains(in DomainSet preVerifiedDomains);
+ DomainSet getPreVerifiedDomains();
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 50be983..7c264f6 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1802,25 +1802,16 @@
}
/**
- * Enable or disable different archive compatibility options of the launcher.
+ * Disable different archive compatibility options of the launcher for the caller of this
+ * method.
*
- * @param enableIconOverlay Provides a cloud overlay for archived apps to ensure users are aware
- * that a certain app is archived. True by default.
- * Launchers might want to disable this operation if they want to provide custom user experience
- * to differentiate archived apps.
- * @param enableUnarchivalConfirmation If true, the user is shown a confirmation dialog when
- * they click an archived app, which explains that the app will be downloaded and restored in
- * the background. True by default.
- * Launchers might want to disable this operation if they provide sufficient, alternative user
- * guidance to highlight that an unarchival is starting and ongoing once an archived app is
- * tapped. E.g., this could be achieved by showing the unarchival progress around the icon.
+ * @see ArchiveCompatibilityParams for individual options.
*/
@FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
- public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
- boolean enableUnarchivalConfirmation) {
+ public void setArchiveCompatibility(@NonNull ArchiveCompatibilityParams params) {
try {
- mService.setArchiveCompatibilityOptions(enableIconOverlay,
- enableUnarchivalConfirmation);
+ mService.setArchiveCompatibilityOptions(params.isEnableIconOverlay(),
+ params.isEnableUnarchivalConfirmation());
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -1982,6 +1973,50 @@
}
};
+ /**
+ * Used to enable Archiving compatibility options with {@link #setArchiveCompatibility}.
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+ public static class ArchiveCompatibilityParams {
+ private boolean mEnableIconOverlay = true;
+
+ private boolean mEnableUnarchivalConfirmation = true;
+
+ /** @hide */
+ public boolean isEnableIconOverlay() {
+ return mEnableIconOverlay;
+ }
+
+ /** @hide */
+ public boolean isEnableUnarchivalConfirmation() {
+ return mEnableUnarchivalConfirmation;
+ }
+
+ /**
+ * If true, provides a cloud overlay for archived apps to ensure users are aware that a
+ * certain app is archived. True by default.
+ *
+ * <p> Launchers might want to disable this operation if they want to provide custom user
+ * experience to differentiate archived apps.
+ */
+ public void setEnableIconOverlay(boolean enableIconOverlay) {
+ this.mEnableIconOverlay = enableIconOverlay;
+ }
+
+ /**
+ * If true, the user is shown a confirmation dialog when they click an archived app, which
+ * explains that the app will be downloaded and restored in the background. True by default.
+ *
+ * <p> Launchers might want to disable this operation if they provide sufficient,
+ * alternative user guidance to highlight that an unarchival is starting and ongoing once an
+ * archived app is tapped. E.g., this could be achieved by showing the unarchival progress
+ * around the icon.
+ */
+ public void setEnableUnarchivalConfirmation(boolean enableUnarchivalConfirmation) {
+ this.mEnableUnarchivalConfirmation = enableUnarchivalConfirmation;
+ }
+ }
+
private static class CallbackMessageHandler extends Handler {
private static final int MSG_ADDED = 1;
private static final int MSG_REMOVED = 2;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 5df23c0..c2ff9f6 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -61,6 +61,7 @@
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.pm.verify.domain.DomainSet;
import android.graphics.Bitmap;
import android.icu.util.ULocale;
import android.net.Uri;
@@ -2296,6 +2297,66 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Sets the pre-verified domains for the app to be installed. By setting pre-verified
+ * domains, the installer allows the app to be opened by the app links of these domains
+ * immediately after it is installed.
+ *
+ * <p>The specified pre-verified domains should be a subset of the hostnames declared with
+ * {@code android:host} and {@code android:autoVerify=true} in the intent filters of the
+ * AndroidManifest.xml of the app. If some of the specified domains are not declared in
+ * the manifest, they will be ignored.</p>
+ * <p>If this API is called multiple times on the same {@link #Session}, the last call
+ * overrides the previous ones.</p>
+ * <p>The instant app installer is the only entity that may call this API.
+ * </p>
+ *
+ * @param preVerifiedDomains domains that are already pre-verified by the installer.
+ *
+ * @throws IllegalArgumentException if the number or the total size of the pre-verified
+ * domains exceeds the maximum allowed, or if the domain
+ * names contain invalid characters.
+ * @throws SecurityException if called from an installer that is not the instant app
+ * installer of the device, or if called after the session has
+ * been committed or abandoned.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
+ @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS)
+ public void setPreVerifiedDomains(@NonNull Set<String> preVerifiedDomains) {
+ Preconditions.checkArgument(preVerifiedDomains != null && !preVerifiedDomains.isEmpty(),
+ "Provided pre-verified domains cannot be null or empty.");
+ try {
+ mSession.setPreVerifiedDomains(new DomainSet(preVerifiedDomains));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve the pre-verified domains set in a session.
+ * See {@link #setPreVerifiedDomains(Set)} for the definition of pre-verified domains.
+ *
+ * @throws SecurityException if called from an installer that is not the owner of the
+ * session, or if called after the session has been committed or
+ * abandoned.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
+ @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS)
+ @NonNull
+ public Set<String> getPreVerifiedDomains() {
+ try {
+ DomainSet domainSet = mSession.getPreVerifiedDomains();
+ return domainSet != null ? domainSet.getDomains() : Collections.emptySet();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
index b919c4b..db3050f 100644
--- a/core/java/android/content/pm/PackageStats.java
+++ b/core/java/android/content/pm/PackageStats.java
@@ -67,6 +67,18 @@
/** @hide */
public long dmSize;
+ /** Size of dexopt artifacts of the application. */
+ /** @hide */
+ public long dexoptSize;
+
+ /** Size of the current profile of the application. */
+ /** @hide */
+ public long curProfSize;
+
+ /** Size of the reference profile of the application. */
+ /** @hide */
+ public long refProfSize;
+
/**
* Size of the secure container on external storage holding the
* application's code.
@@ -132,6 +144,18 @@
sb.append(" dm=");
sb.append(dmSize);
}
+ if (dexoptSize != 0) {
+ sb.append(" dexopt=");
+ sb.append(dexoptSize);
+ }
+ if (curProfSize != 0) {
+ sb.append(" curProf=");
+ sb.append(curProfSize);
+ }
+ if (refProfSize != 0) {
+ sb.append(" refProf=");
+ sb.append(refProfSize);
+ }
if (externalCodeSize != 0) {
sb.append(" extCode=");
sb.append(externalCodeSize);
@@ -176,6 +200,9 @@
apkSize = source.readLong();
libSize = source.readLong();
dmSize = source.readLong();
+ dexoptSize = source.readLong();
+ curProfSize = source.readLong();
+ refProfSize = source.readLong();
externalCodeSize = source.readLong();
externalDataSize = source.readLong();
externalCacheSize = source.readLong();
@@ -192,6 +219,9 @@
apkSize = pStats.apkSize;
libSize = pStats.libSize;
dmSize = pStats.dmSize;
+ dexoptSize = pStats.dexoptSize;
+ curProfSize = pStats.curProfSize;
+ refProfSize = pStats.refProfSize;
externalCodeSize = pStats.externalCodeSize;
externalDataSize = pStats.externalDataSize;
externalCacheSize = pStats.externalCacheSize;
@@ -212,6 +242,9 @@
dest.writeLong(apkSize);
dest.writeLong(libSize);
dest.writeLong(dmSize);
+ dest.writeLong(dexoptSize);
+ dest.writeLong(curProfSize);
+ dest.writeLong(refProfSize);
dest.writeLong(externalCodeSize);
dest.writeLong(externalDataSize);
dest.writeLong(externalCacheSize);
@@ -234,6 +267,9 @@
&& apkSize == otherStats.apkSize
&& libSize == otherStats.libSize
&& dmSize == otherStats.dmSize
+ && dexoptSize == otherStats.dexoptSize
+ && curProfSize == otherStats.curProfSize
+ && refProfSize == otherStats.refProfSize
&& externalCodeSize == otherStats.externalCodeSize
&& externalDataSize == otherStats.externalDataSize
&& externalCacheSize == otherStats.externalCacheSize
@@ -244,7 +280,8 @@
@Override
public int hashCode() {
return Objects.hash(packageName, userHandle, codeSize, dataSize,
- apkSize, libSize, dmSize, cacheSize, externalCodeSize,
+ apkSize, libSize, dmSize, dexoptSize, curProfSize,
+ refProfSize, cacheSize, externalCodeSize,
externalDataSize, externalCacheSize, externalMediaSize,
externalObbSize);
}
diff --git a/core/java/android/credentials/selection/RequestInfo.java b/core/java/android/credentials/selection/RequestInfo.java
index 2fd322a..60bbae6 100644
--- a/core/java/android/credentials/selection/RequestInfo.java
+++ b/core/java/android/credentials/selection/RequestInfo.java
@@ -110,6 +110,8 @@
private final boolean mHasPermissionToOverrideDefault;
+ private final boolean mIsShowAllOptionsRequested;
+
/**
* Creates new {@code RequestInfo} for a create-credential flow.
*
@@ -121,10 +123,10 @@
public static RequestInfo newCreateRequestInfo(
@NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
@NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
- @NonNull List<String> defaultProviderIds) {
+ @NonNull List<String> defaultProviderIds, boolean isShowAllOptionsRequested) {
return new RequestInfo(
token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
- hasPermissionToOverrideDefault, defaultProviderIds);
+ hasPermissionToOverrideDefault, defaultProviderIds, isShowAllOptionsRequested);
}
/**
@@ -137,11 +139,12 @@
@NonNull
public static RequestInfo newGetRequestInfo(
@NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
- @NonNull String appPackageName, boolean hasPermissionToOverrideDefault) {
+ @NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
+ boolean isShowAllOptionsRequested) {
return new RequestInfo(
token, TYPE_GET, appPackageName, null, getCredentialRequest,
hasPermissionToOverrideDefault,
- /*defaultProviderIds=*/ new ArrayList<>());
+ /*defaultProviderIds=*/ new ArrayList<>(), isShowAllOptionsRequested);
}
@@ -218,12 +221,31 @@
return mGetCredentialRequest;
}
+ /**
+ * Returns true if all options should be immediately displayed in the UI, and false otherwise.
+ *
+ * Normally this bit is set to false, upon which the selection UI should first display a
+ * condensed view of popular, deduplicated options that is determined based on signals like
+ * last-used timestamps, credential type priorities, and preferred providers configured from the
+ * user settings {@link #getDefaultProviderIds()}; at the same time, the UI should offer an
+ * option (button) that navigates the user to viewing all options from this condensed view.
+ *
+ * In some special occasions, e.g. when a request is initiated from the autofill drop-down
+ * suggestion, this bit will be set to true to indicate that the selection UI should immediately
+ * render the all option UI. This means that the request initiator has collected a user signal
+ * to confirm that the user wants to view all the available options at once.
+ */
+ public boolean isShowAllOptionsRequested() {
+ return mIsShowAllOptionsRequested;
+ }
+
private RequestInfo(@NonNull IBinder token, @NonNull @RequestType String type,
@NonNull String appPackageName,
@Nullable CreateCredentialRequest createCredentialRequest,
@Nullable GetCredentialRequest getCredentialRequest,
boolean hasPermissionToOverrideDefault,
- @NonNull List<String> defaultProviderIds) {
+ @NonNull List<String> defaultProviderIds,
+ boolean isShowAllOptionsRequested) {
mToken = token;
mType = type;
mAppPackageName = appPackageName;
@@ -232,6 +254,7 @@
mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault;
mDefaultProviderIds = defaultProviderIds == null ? new ArrayList<>() : defaultProviderIds;
mRegistryProviderIds = new ArrayList<>();
+ mIsShowAllOptionsRequested = isShowAllOptionsRequested;
}
private RequestInfo(@NonNull Parcel in) {
@@ -254,6 +277,7 @@
mHasPermissionToOverrideDefault = in.readBoolean();
mDefaultProviderIds = in.createStringArrayList();
mRegistryProviderIds = in.createStringArrayList();
+ mIsShowAllOptionsRequested = in.readBoolean();
}
@Override
@@ -266,6 +290,7 @@
dest.writeBoolean(mHasPermissionToOverrideDefault);
dest.writeStringList(mDefaultProviderIds);
dest.writeStringList(mRegistryProviderIds);
+ dest.writeBoolean(mIsShowAllOptionsRequested);
}
@Override
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 35ae3c9..5dfeac7 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -693,7 +693,7 @@
* Begins a transaction in DEFERRED mode, with the android-specific constraint that the
* transaction is read-only. The database may not be modified inside a read-only transaction.
* <p>
- * Read-only transactions may run concurrently with other read-only transactions, and if they
+ * Read-only transactions may run concurrently with other read-only transactions, and if the
* database is in WAL mode, they may also run concurrently with IMMEDIATE or EXCLUSIVE
* transactions.
* <p>
diff --git a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
index d943c37..1cc910c 100644
--- a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
+++ b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
@@ -219,26 +219,22 @@
if (!glyphData.hasBaseText()) {
return;
}
- if (glyphData.hasValidShiftText() && glyphData.hasValidAltGrText()) {
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
- GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
+ boolean isCenter = !glyphData.hasValidAltGrText() && !glyphData.hasValidAltShiftText();
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+ GRAVITY_BOTTOM | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
+ mBaseTextPaint));
+ if (glyphData.hasValidShiftText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
- GRAVITY_TOP | GRAVITY_LEFT, mModifierTextPaint));
+ GRAVITY_TOP | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
+ mModifierTextPaint));
+ }
+ if (glyphData.hasValidAltGrText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
- } else if (glyphData.hasValidShiftText()) {
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
- GRAVITY_BOTTOM | GRAVITY_CENTER_HORIZONTAL, mBaseTextPaint));
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
- GRAVITY_TOP | GRAVITY_CENTER_HORIZONTAL, mModifierTextPaint));
- } else if (glyphData.hasValidAltGrText()) {
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
- GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
- GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
- } else {
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
- GRAVITY_CENTER, mBaseTextPaint));
+ }
+ if (glyphData.hasValidAltShiftText()) {
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrShiftText(), new RectF(),
+ GRAVITY_TOP | GRAVITY_RIGHT, mModifierTextPaint));
}
}
diff --git a/core/java/android/hardware/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java
index 3454c39..844e02f 100644
--- a/core/java/android/hardware/input/PhysicalKeyLayout.java
+++ b/core/java/android/hardware/input/PhysicalKeyLayout.java
@@ -396,6 +396,7 @@
private final String mBaseText;
private final String mShiftText;
private final String mAltGrText;
+ private final String mAltGrShiftText;
public KeyGlyph(KeyCharacterMap kcm, int keyCode) {
mBaseText = getKeyText(kcm, keyCode, KeyEvent.META_CAPS_LOCK_ON);
@@ -403,6 +404,9 @@
KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON);
mAltGrText = getKeyText(kcm, keyCode,
KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_CAPS_LOCK_ON);
+ mAltGrShiftText = getKeyText(kcm, keyCode,
+ KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_SHIFT_LEFT_ON
+ | KeyEvent.META_SHIFT_ON);
}
public String getBaseText() {
@@ -417,6 +421,10 @@
return mAltGrText;
}
+ public String getAltGrShiftText() {
+ return mAltGrShiftText;
+ }
+
public boolean hasBaseText() {
return !TextUtils.isEmpty(mBaseText);
}
@@ -428,5 +436,12 @@
public boolean hasValidAltGrText() {
return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText);
}
+
+ public boolean hasValidAltShiftText() {
+ return !TextUtils.isEmpty(mAltGrShiftText)
+ && !TextUtils.equals(mBaseText, mAltGrShiftText)
+ && !TextUtils.equals(mAltGrText, mAltGrShiftText)
+ && !TextUtils.equals(mShiftText, mAltGrShiftText);
+ }
}
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 71698e4..281ee50 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1256,7 +1256,9 @@
private void updateEditorToolTypeInternal(int toolType) {
if (Flags.useHandwritingListenerForTooltype()) {
mLastUsedToolType = toolType;
- mInputEditorInfo.setInitialToolType(toolType);
+ if (mInputEditorInfo != null) {
+ mInputEditorInfo.setInitialToolType(toolType);
+ }
}
onUpdateEditorToolType(toolType);
}
diff --git a/core/java/android/os/ArtModuleServiceManager.java b/core/java/android/os/ArtModuleServiceManager.java
index 0009e61..e0b631d 100644
--- a/core/java/android/os/ArtModuleServiceManager.java
+++ b/core/java/android/os/ArtModuleServiceManager.java
@@ -15,9 +15,11 @@
*/
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.content.pm.Flags;
/**
* Provides a way to register and obtain the system service binder objects managed by the ART
@@ -60,4 +62,18 @@
public ServiceRegisterer getArtdServiceRegisterer() {
return new ServiceRegisterer("artd");
}
+
+ /** Returns {@link ServiceRegisterer} for the "artd_pre_reboot" service. */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
+ public ServiceRegisterer getArtdPreRebootServiceRegisterer() {
+ return new ServiceRegisterer("artd_pre_reboot");
+ }
+
+ /** Returns {@link ServiceRegisterer} for the "dexopt_chroot_setup" service. */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
+ public ServiceRegisterer getDexoptChrootSetupServiceRegisterer() {
+ return new ServiceRegisterer("dexopt_chroot_setup");
+ }
}
diff --git a/core/java/android/os/ConfigUpdate.java b/core/java/android/os/ConfigUpdate.java
index 4908919..87cd4ee 100644
--- a/core/java/android/os/ConfigUpdate.java
+++ b/core/java/android/os/ConfigUpdate.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -131,6 +132,23 @@
"android.os.action.UPDATE_EMERGENCY_NUMBER_DB";
/**
+ * Broadcast intent action indicating that the updated config data is available.
+ * This broadcast intent action is to be sent by the config updater app, and will be received
+ * and handled by the platform.
+ * <p>Extra: {@link #EXTRA_VERSION} the numeric version of the database.
+ * <p>Extra: {@link #EXTRA_REQUIRED_HASH} hash of the database, which is encoded by base-16
+ * SHA512
+ * <p>Extra: {@link #EXTRA_DOMAIN} the string identifying the affected module
+ * <p>Input: {@link android.content.Intent#getData} the URI to download config data file
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_UPDATE_CONFIG = "android.os.action.UPDATE_CONFIG";
+
+ /**
* An integer to indicate the numeric version of the new data. Devices should only install
* if the update version is newer than the current one.
*
@@ -147,6 +165,16 @@
@SystemApi
public static final String EXTRA_REQUIRED_HASH = "android.os.extra.REQUIRED_HASH";
+ /**
+ * String identifying the affected module.
+ * Devices apply the updated config data to the module specified in the string.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final String EXTRA_DOMAIN = "android.os.extra.DOMAIN";
+
private ConfigUpdate() {
}
}
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index e6bfcd7..224b10d 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -313,25 +313,33 @@
* close to the target duration.
*
* @param workDuration the work duration of each component.
- * @throws IllegalArgumentException if work period start timestamp is not positive, or
- * actual total duration is not positive, or actual CPU duration is not positive,
- * or actual GPU duration is negative.
+ * @throws IllegalArgumentException if
+ * the work period start timestamp or the total duration are less than or equal to zero,
+ * if either the actual CPU duration or actual GPU duration is less than zero,
+ * or if both the CPU and GPU durations are zero.
*/
@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
public void reportActualWorkDuration(@NonNull WorkDuration workDuration) {
if (workDuration.mWorkPeriodStartTimestampNanos <= 0) {
throw new IllegalArgumentException(
- "the work period start timestamp should be positive.");
+ "the work period start timestamp should be greater than zero.");
}
if (workDuration.mActualTotalDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual total duration should be positive.");
+ throw new IllegalArgumentException(
+ "the actual total duration should be greater than zero.");
}
- if (workDuration.mActualCpuDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual CPU duration should be positive.");
+ if (workDuration.mActualCpuDurationNanos < 0) {
+ throw new IllegalArgumentException(
+ "the actual CPU duration should be greater than or equal to zero.");
}
if (workDuration.mActualGpuDurationNanos < 0) {
throw new IllegalArgumentException(
- "the actual GPU duration should be non negative.");
+ "the actual GPU duration should be greater than or equal to zero.");
+ }
+ if (workDuration.mActualCpuDurationNanos + workDuration.mActualGpuDurationNanos <= 0) {
+ throw new IllegalArgumentException(
+ "either the actual CPU duration or the actual GPU duration should be greater"
+ + "than zero.");
}
nativeReportActualWorkDuration(mNativeSessionPtr,
workDuration.mWorkPeriodStartTimestampNanos,
diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java
index 2ebcd83..5a54e90 100644
--- a/core/java/android/os/WorkDuration.java
+++ b/core/java/android/os/WorkDuration.java
@@ -83,7 +83,7 @@
public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) {
if (workPeriodStartTimestampNanos <= 0) {
throw new IllegalArgumentException(
- "the work period start timestamp should be positive.");
+ "the work period start timestamp should be greater than zero.");
}
mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
}
@@ -95,7 +95,8 @@
*/
public void setActualTotalDurationNanos(long actualTotalDurationNanos) {
if (actualTotalDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual total duration should be positive.");
+ throw new IllegalArgumentException(
+ "the actual total duration should be greater than zero.");
}
mActualTotalDurationNanos = actualTotalDurationNanos;
}
@@ -106,8 +107,9 @@
* All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public void setActualCpuDurationNanos(long actualCpuDurationNanos) {
- if (actualCpuDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual CPU duration should be positive.");
+ if (actualCpuDurationNanos < 0) {
+ throw new IllegalArgumentException(
+ "the actual CPU duration should be greater than or equal to zero.");
}
mActualCpuDurationNanos = actualCpuDurationNanos;
}
@@ -119,7 +121,8 @@
*/
public void setActualGpuDurationNanos(long actualGpuDurationNanos) {
if (actualGpuDurationNanos < 0) {
- throw new IllegalArgumentException("the actual GPU duration should be non negative.");
+ throw new IllegalArgumentException(
+ "the actual GPU duration should be greater than or equal to zero.");
}
mActualGpuDurationNanos = actualGpuDurationNanos;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 76fda06..ef2d5eb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -18161,6 +18161,16 @@
"show_notification_channel_warnings";
/**
+ * Whether to disable app and notification screen share protections.
+ *
+ * The value 1 - enable, 0 - disable
+ * @hide
+ */
+ @Readable
+ public static final String DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS =
+ "disable_screen_share_protections_for_apps_and_notifications";
+
+ /**
* Whether cell is enabled/disabled
* @hide
*/
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 298bdb8..e6a84df 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -600,6 +600,14 @@
*/
public static final String EXTRA_ERROR = "error";
+ /**
+ * Name of the key used to mark whether the fill response is for a webview.
+ *
+ * @hide
+ */
+ public static final String WEBVIEW_REQUESTED_CREDENTIAL_KEY = "webview_requested_credential";
+
+
private final IAutoFillService mInterface = new IAutoFillService.Stub() {
@Override
public void onConnectedStateChanged(boolean connected) {
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 0421d5a..32f05be 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -54,4 +54,11 @@
public static boolean fixLineHeightForLocale() {
return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE);
}
+
+ /**
+ * @see Flags#icuBidiMigration()
+ */
+ public static boolean icuBidiMigration() {
+ return TextFlags.isFeatureEnabled(Flags.FLAG_ICU_BIDI_MIGRATION);
+ }
}
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index b268c2e..09f15c3 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -30,6 +30,7 @@
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.graphics.text.MeasuredText;
+import android.icu.text.Bidi;
import android.text.AutoGrowArray.ByteArray;
import android.text.AutoGrowArray.FloatArray;
import android.text.AutoGrowArray.IntArray;
@@ -115,6 +116,11 @@
// This is empty if mLtrWithoutBidi is true.
private @NonNull ByteArray mLevels = new ByteArray();
+ // The bidi level for runs.
+ private @NonNull ByteArray mRunLevels = new ByteArray();
+
+ private Bidi mBidi;
+
// The whole width of the text.
// See getWholeWidth comments.
private @FloatRange(from = 0.0f) float mWholeWidth;
@@ -148,6 +154,7 @@
reset();
mLevels.clearWithReleasingLargeArray();
mWidths.clearWithReleasingLargeArray();
+ mRunLevels.clearWithReleasingLargeArray();
mFontMetrics.clearWithReleasingLargeArray();
mSpanEndCache.clearWithReleasingLargeArray();
}
@@ -160,10 +167,12 @@
mCopiedBuffer = null;
mWholeWidth = 0;
mLevels.clear();
+ mRunLevels.clear();
mWidths.clear();
mFontMetrics.clear();
mSpanEndCache.clear();
mMeasuredText = null;
+ mBidi = null;
}
/**
@@ -193,6 +202,13 @@
* @hide
*/
public @Layout.Direction int getParagraphDir() {
+ if (ClientFlags.icuBidiMigration()) {
+ if (mBidi == null) {
+ return Layout.DIR_LEFT_TO_RIGHT;
+ }
+ return (mBidi.getParaLevel() & 0x01) == 0
+ ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
+ }
return mParaDir;
}
@@ -204,6 +220,62 @@
*/
public Directions getDirections(@IntRange(from = 0) int start, // inclusive
@IntRange(from = 0) int end) { // exclusive
+ if (ClientFlags.icuBidiMigration()) {
+ // Easy case: mBidi == null means the text is all LTR and no bidi suppot is needed.
+ if (mBidi == null) {
+ return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ }
+
+ // Easy case: If the original text only contains single directionality run, the
+ // substring is only single run.
+ if (start == end) {
+ if ((mBidi.getParaLevel() & 0x01) == 0) {
+ return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ } else {
+ return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+ }
+ }
+
+ // Okay, now we need to generate the line instance.
+ Bidi bidi = mBidi.createLineBidi(start, end);
+
+ // Easy case: If the line instance only contains single directionality run, no need
+ // to reorder visually.
+ if (bidi.getRunCount() == 1) {
+ if ((bidi.getParaLevel() & 0x01) == 1) {
+ return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+ } else {
+ return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ }
+ }
+
+ // Reorder directionality run visually.
+ mRunLevels.resize(bidi.getRunCount());
+ byte[] levels = mRunLevels.getRawArray();
+ for (int i = 0; i < bidi.getRunCount(); ++i) {
+ levels[i] = (byte) bidi.getRunLevel(i);
+ }
+ int[] visualOrders = Bidi.reorderVisual(levels);
+
+ int[] dirs = new int[bidi.getRunCount() * 2];
+ for (int i = 0; i < bidi.getRunCount(); ++i) {
+ int vIndex;
+ if ((mBidi.getBaseLevel() & 0x01) == 1) {
+ // For the historical reasons, if the base directionality is RTL, the Android
+ // draws from the right, i.e. the visually reordered run needs to be reversed.
+ vIndex = visualOrders[bidi.getRunCount() - i - 1];
+ } else {
+ vIndex = visualOrders[i];
+ }
+
+ // Special packing of dire
+ dirs[i * 2] = bidi.getRunStart(vIndex);
+ dirs[i * 2 + 1] = bidi.getRunLevel(vIndex) << Layout.RUN_LEVEL_SHIFT
+ | (bidi.getRunLimit(vIndex) - dirs[i * 2]);
+ }
+
+ return new Directions(dirs);
+ }
if (mLtrWithoutBidi) {
return Layout.DIRS_ALL_LEFT_TO_RIGHT;
}
@@ -608,6 +680,37 @@
}
}
+ if (ClientFlags.icuBidiMigration()) {
+ if ((textDir == TextDirectionHeuristics.LTR
+ || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
+ || textDir == TextDirectionHeuristics.ANYRTL_LTR)
+ && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
+ mLevels.clear();
+ mLtrWithoutBidi = true;
+ return;
+ }
+ final int bidiRequest;
+ if (textDir == TextDirectionHeuristics.LTR) {
+ bidiRequest = Bidi.LTR;
+ } else if (textDir == TextDirectionHeuristics.RTL) {
+ bidiRequest = Bidi.RTL;
+ } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
+ bidiRequest = Bidi.LEVEL_DEFAULT_LTR;
+ } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
+ bidiRequest = Bidi.LEVEL_DEFAULT_RTL;
+ } else {
+ final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
+ bidiRequest = isRtl ? Bidi.RTL : Bidi.LTR;
+ }
+ mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest);
+ mLevels.resize(mTextLength);
+ byte[] rawArray = mLevels.getRawArray();
+ for (int i = 0; i < mTextLength; ++i) {
+ rawArray[i] = mBidi.getLevelAt(i);
+ }
+ mLtrWithoutBidi = false;
+ return;
+ }
if ((textDir == TextDirectionHeuristics.LTR
|| textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
|| textDir == TextDirectionHeuristics.ANYRTL_LTR)
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 2466386..770e5c9 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -59,6 +59,7 @@
Flags.FLAG_PHRASE_STRICT_FALLBACK,
Flags.FLAG_USE_BOUNDS_FOR_WIDTH,
Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
+ Flags.FLAG_ICU_BIDI_MIGRATION,
};
/**
@@ -71,6 +72,7 @@
Flags.phraseStrictFallback(),
Flags.useBoundsForWidth(),
Flags.fixLineHeightForLocale(),
+ Flags.icuBidiMigration(),
};
/**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f3e0ea7..a49aee1 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -103,3 +103,10 @@
description: "Fix that InputService#onUpdateSelection is not called when insert mode gesture is performed."
bug: "300850862"
}
+
+flag {
+ name: "icu_bidi_migration"
+ namespace: "text"
+ description: "A flag for replacing AndroidBidi with android.icu.text.Bidi."
+ bug: "317144801"
+}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 891e2a2..d22d2a5 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -1544,7 +1544,7 @@
* source, this method returns {@code false}.
*
* @param axis the {@link MotionEvent} axis whose value is used to get the scroll extent.
- * @param source the {link InputDevice} source from which the {@link MotionEvent} that
+ * @param source the {@link InputDevice} source from which the {@link MotionEvent} that
* triggers the scroll came.
* @return {@code true} if smooth scrolling should be used for the scroll, or {@code false}
* if smooth scrolling is not necessary, or if the provided axis and source combination
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2366ff7..5c5817f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15868,20 +15868,7 @@
}
if (onFilterTouchEventForSecurity(event)) {
- if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
- result = true;
- }
- //noinspection SimplifiableIfStatement
- ListenerInfo li = mListenerInfo;
- if (li != null && li.mOnTouchListener != null
- && (mViewFlags & ENABLED_MASK) == ENABLED
- && li.mOnTouchListener.onTouch(this, event)) {
- result = true;
- }
-
- if (!result && onTouchEvent(event)) {
- result = true;
- }
+ result = performOnTouchCallback(event);
}
if (!result && mInputEventConsistencyVerifier != null) {
@@ -15900,6 +15887,36 @@
return result;
}
+ /**
+ * Returns {@code true} if the {@link MotionEvent} from {@link #dispatchTouchEvent} was
+ * handled by this view.
+ */
+ private boolean performOnTouchCallback(MotionEvent event) {
+ boolean handled = false;
+ if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
+ handled = true;
+ }
+ //noinspection SimplifiableIfStatement
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED) {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "View.onTouchListener#onTouch");
+ handled = li.mOnTouchListener.onTouch(this, event);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+ if (handled) {
+ return true;
+ }
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "View#onTouchEvent");
+ return onTouchEvent(event);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
boolean isAccessibilityFocusedViewOrHost() {
return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
.getAccessibilityFocusedHost() == this);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6534354..e03f857 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11550,15 +11550,6 @@
event.setContentChangeTypes(mChangeTypes);
if (mAction.isPresent()) event.setAction(mAction.getAsInt());
if (AccessibilityEvent.DEBUG_ORIGIN) event.originStackTrace = mOrigin;
-
- if (fixMergedContentChangeEvent()) {
- if ((mChangeTypes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
- final View importantParent = source.getSelfOrParentImportantForA11y();
- if (importantParent != null) {
- source = importantParent;
- }
- }
- }
source.sendAccessibilityEventUnchecked(event);
} else {
mLastEventTimeMillis = 0;
@@ -11595,6 +11586,9 @@
if (mSource != null) {
if (fixMergedContentChangeEvent()) {
View newSource = getCommonPredecessor(mSource, source);
+ if (newSource != null) {
+ newSource = newSource.getSelfOrParentImportantForA11y();
+ }
if (newSource == null) {
// If there is no common predecessor, then mSource points to
// a removed view, hence in this case always prefer the source.
diff --git a/core/java/android/view/accessibility/MagnificationAnimationCallback.java b/core/java/android/view/accessibility/MagnificationAnimationCallback.java
index 72518db..1755497 100644
--- a/core/java/android/view/accessibility/MagnificationAnimationCallback.java
+++ b/core/java/android/view/accessibility/MagnificationAnimationCallback.java
@@ -16,6 +16,8 @@
package android.view.accessibility;
+import android.view.MagnificationSpec;
+
/**
* A callback for magnification animation result.
* @hide
@@ -31,4 +33,16 @@
* change. Otherwise {@code false}
*/
void onResult(boolean success);
+
+ /**
+ * Called when the animation is finished or interrupted during animating.
+ *
+ * @param success {@code true} if animating successfully with given spec or the spec did not
+ * change. Otherwise {@code false}
+ * @param lastSpecSent the last spec that was sent to WindowManager for animation, in case you
+ * need to update the local copy
+ */
+ default void onResult(boolean success, MagnificationSpec lastSpecSent) {
+ onResult(success);
+ }
}
\ No newline at end of file
diff --git a/core/java/android/widget/RemoteCanvas.java b/core/java/android/widget/RemoteCanvas.java
deleted file mode 100644
index 9a0898c..0000000
--- a/core/java/android/widget/RemoteCanvas.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2024 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.widget;
-
-import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL;
-
-import android.annotation.AttrRes;
-import android.annotation.FlaggedApi;
-import android.annotation.StyleRes;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import java.util.function.IntConsumer;
-
-/**
- * {@link RemoteCanvas} is designed to support arbitrary protocols between two processes using
- * {@link RemoteViews.DrawInstructions}. Upon instantiation in the host process,
- * {@link RemoteCanvas#setDrawInstructions(RemoteViews.DrawInstructions)} is called so that the
- * host process can render the {@link RemoteViews.DrawInstructions} from the provider process
- * accordingly.
- *
- * @hide
- */
-@FlaggedApi(FLAG_DRAW_DATA_PARCEL)
-public class RemoteCanvas extends View {
-
- private static final String TAG = "RemoteCanvas";
-
- @Nullable
- private SparseArray<Runnable> mCallbacks;
-
- private final IntConsumer mOnClickHandler = (viewId) -> {
- if (mCallbacks == null) {
- Log.w(TAG, "Cannot find callback for " + viewId
- + ", in fact there were no callbacks from this RemoteViews at all.");
- return;
- }
- final Runnable cb = getCallbacks().get(viewId);
- if (cb != null) {
- cb.run();
- } else {
- Log.w(TAG, "Cannot find callback for " + viewId);
- }
- };
-
- RemoteCanvas(@NonNull Context context) {
- super(context);
- }
-
- RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
- @AttrRes int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
- @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- /**
- * Setter method for the {@link RemoteViews.DrawInstructions} from the provider process for
- * the host process to render accordingly.
- *
- * @param instructions {@link RemoteViews.DrawInstructions} from the provider process.
- */
- void setDrawInstructions(@NonNull final RemoteViews.DrawInstructions instructions) {
- setTag(instructions);
- // TODO: handle draw instructions
- // TODO: attach mOnClickHandler
- }
-
- /**
- * Adds a callback function to a clickable area in the RemoteCanvas.
- *
- * @param viewId the viewId of the clickable area
- * @param cb the callback function to be triggered when clicked
- */
- void addOnClickHandler(final int viewId, @NonNull final Runnable cb) {
- getCallbacks().set(viewId, cb);
- }
-
- /**
- * Returns all callbacks added to the RemoteCanvas through
- * {@link #addOnClickHandler(int, Runnable)}.
- */
- @VisibleForTesting
- public SparseArray<Runnable> getCallbacks() {
- if (mCallbacks == null) {
- mCallbacks = new SparseArray<>();
- }
- return mCallbacks;
- }
-}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 738bb1f..5bf1b5b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -112,7 +112,10 @@
import com.android.internal.R;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.IRemoteViewsFactory;
+import com.android.internal.widget.remotecompose.player.RemoteComposeDocument;
+import com.android.internal.widget.remotecompose.player.RemoteComposePlayer;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
@@ -1479,9 +1482,7 @@
@Override
public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
- if (hasDrawInstructions() && root instanceof RemoteCanvas target) {
- target.addOnClickHandler(mViewId, () ->
- mResponse.handleViewInteraction(root, params.handler));
+ if (hasDrawInstructions() && root instanceof RemoteComposePlayer) {
return;
}
final View target = root.findViewById(mViewId);
@@ -3900,8 +3901,17 @@
public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
throws ActionException {
if (drawDataParcel() && mInstructions != null
- && root instanceof RemoteCanvas remoteCanvas) {
- remoteCanvas.setDrawInstructions(mInstructions);
+ && root instanceof RemoteComposePlayer player) {
+ player.setTag(mInstructions);
+ final List<byte[]> bytes = mInstructions.mInstructions;
+ if (bytes.isEmpty()) {
+ return;
+ }
+ try (ByteArrayInputStream is = new ByteArrayInputStream(bytes.get(0))) {
+ player.setDocument(new RemoteComposeDocument(is));
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to render draw instructions", e);
+ }
}
}
@@ -6041,6 +6051,16 @@
RemoteViews rvToApply = getRemoteViewsToApply(context, size);
View result = inflateView(context, rvToApply, directParent,
params.applyThemeResId, params.colorResources);
+ if (result instanceof RemoteComposePlayer player) {
+ player.addClickListener((viewId, metadata) -> {
+ mActions.forEach(action -> {
+ if (viewId == action.mViewId
+ && action instanceof SetOnClickResponse setOnClickResponse) {
+ setOnClickResponse.mResponse.handleViewInteraction(player, params.handler);
+ }
+ });
+ });
+ }
rvToApply.performApply(result, rootParent, params);
return result;
}
@@ -6064,7 +6084,7 @@
}
// If the RemoteViews contains draw instructions, just use it instead.
if (rv.hasDrawInstructions()) {
- return new RemoteCanvas(inflationContext);
+ return new RemoteComposePlayer(inflationContext);
}
LayoutInflater inflater = LayoutInflater.from(context);
@@ -7546,7 +7566,7 @@
public static final class DrawInstructions {
@NonNull
- private final List<byte[]> mInstructions;
+ final List<byte[]> mInstructions;
private DrawInstructions() {
throw new UnsupportedOperationException(
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 9ef6880..0cc9a0d 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -50,6 +50,8 @@
static final int RESULT_CODE_UNREGISTER = 1;
@NonNull
private final ResultReceiver mResultReceiver;
+ @NonNull
+ private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
public ImeOnBackInvokedDispatcher(Handler handler) {
mResultReceiver = new ResultReceiver(handler) {
@@ -88,7 +90,7 @@
// cause a memory leak because the app side already clears the reference correctly.
final IOnBackInvokedCallback iCallback =
new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(
- callback, false /* useWeakRef */);
+ callback, mProgressAnimator, false /* useWeakRef */);
bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
bundle.putInt(RESULT_KEY_PRIORITY, priority);
bundle.putInt(RESULT_KEY_ID, callback.hashCode());
@@ -179,6 +181,9 @@
}
}
mImeCallbacks.clear();
+ // We should also stop running animations since all callbacks have been removed.
+ // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+ Handler.getMain().post(mProgressAnimator::reset);
}
static class ImeOnBackInvokedCallback implements OnBackInvokedCallback {
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index baefe7b..5c911f4 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -246,7 +246,10 @@
.ImeOnBackInvokedCallback
? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
callback).getIOnBackInvokedCallback()
- : new OnBackInvokedCallbackWrapper(callback, this);
+ : new OnBackInvokedCallbackWrapper(
+ callback,
+ mProgressAnimator,
+ this);
callbackInfo = new OnBackInvokedCallbackInfo(
iCallback,
priority,
@@ -272,7 +275,7 @@
}
@NonNull
- private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
private boolean mIsDispatching = false;
/**
@@ -339,18 +342,24 @@
* forwarded and registered on the app's {@link WindowOnBackInvokedDispatcher}. */
@Nullable
private final WindowOnBackInvokedDispatcher mDispatcher;
+ @NonNull
+ private final BackProgressAnimator mProgressAnimator;
OnBackInvokedCallbackWrapper(
@NonNull OnBackInvokedCallback callback,
+ @NonNull BackProgressAnimator progressAnimator,
WindowOnBackInvokedDispatcher dispatcher) {
mCallbackRef = new CallbackRef(callback, true /* useWeakRef */);
+ mProgressAnimator = progressAnimator;
mDispatcher = dispatcher;
}
OnBackInvokedCallbackWrapper(
@NonNull OnBackInvokedCallback callback,
+ @NonNull BackProgressAnimator progressAnimator,
boolean useWeakRef) {
mCallbackRef = new CallbackRef(callback, useWeakRef);
+ mProgressAnimator = progressAnimator;
mDispatcher = null;
}
@@ -362,11 +371,11 @@
}
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
- mProgressAnimator.onBackStarted(backEvent, event ->
- callback.onBackProgressed(event));
callback.onBackStarted(new BackEvent(
backEvent.getTouchX(), backEvent.getTouchY(),
backEvent.getProgress(), backEvent.getSwipeEdge()));
+ mProgressAnimator.onBackStarted(backEvent, event ->
+ callback.onBackProgressed(event));
}
});
}
diff --git a/core/java/com/android/internal/widget/remotecompose/OWNERS b/core/java/com/android/internal/widget/remotecompose/OWNERS
index 61724ec..54facab 100644
--- a/core/java/com/android/internal/widget/remotecompose/OWNERS
+++ b/core/java/com/android/internal/widget/remotecompose/OWNERS
@@ -4,3 +4,5 @@
sihua@google.com
sunnygoyal@google.com
oscarad@google.com
+pinyaoting@google.com
+zakcohen@google.com
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 323f7b6..b5fbb22 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -73,7 +73,7 @@
uint32_t mNextPublishedSeq;
const std::string getInputChannelName() {
- return mInputPublisher.getChannel()->getName();
+ return mInputPublisher.getChannel().getName();
}
int handleEvent(int receiveFd, int events, void* data) override;
@@ -102,7 +102,7 @@
}
status_t NativeInputEventSender::initialize() {
- const int receiveFd = mInputPublisher.getChannel()->getFd();
+ const int receiveFd = mInputPublisher.getChannel().getFd();
mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
return OK;
}
@@ -112,7 +112,7 @@
LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender.";
}
- mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
+ mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel().getFd());
}
status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index d3f3af7..2861858 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -723,6 +723,7 @@
// encoded as a key=value list separated by commas.
optional SettingProto smart_suggestions_in_notifications_flags = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto bubbles = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto disable_screen_share_protections_for_apps_and_notifications = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Notification notification = 82;
diff --git a/core/res/res/drawable/ic_notification_summary_auto.xml b/core/res/res/drawable/ic_notification_summary_auto.xml
new file mode 100644
index 0000000..908bba8
--- /dev/null
+++ b/core/res/res/drawable/ic_notification_summary_auto.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M260,760Q236,760 218,742Q200,724 200,700L200,140Q200,116 218,98Q236,80 260,80L820,80Q844,80 862,98Q880,116 880,140L880,700Q880,724 862,742Q844,760 820,760L260,760ZM260,700L820,700Q820,700 820,700Q820,700 820,700L820,140Q820,140 820,140Q820,140 820,140L260,140Q260,140 260,140Q260,140 260,140L260,700Q260,700 260,700Q260,700 260,700ZM140,880Q116,880 98,862Q80,844 80,820L80,200L140,200L140,820Q140,820 140,820Q140,820 140,820L760,820L760,880L140,880ZM260,140L260,140Q260,140 260,140Q260,140 260,140L260,700Q260,700 260,700Q260,700 260,700L260,700Q260,700 260,700Q260,700 260,700L260,140Q260,140 260,140Q260,140 260,140Z"/>
+</vector>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 951625e..d4e727e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5385,19 +5385,20 @@
and a second time clipped to the fill level to indicate charge -->
<bool name="config_batterymeterDualTone">false</bool>
- <!-- The default refresh rate for a given device. This value is used to set the
- global refresh rate vote, and when set to zero it has no effect on the vote.
- If this value is non-zero but the hardware composer on the device supports
- display modes with higher refresh rates, the framework may use those higher
- refresh rate modes if an app chooses one by setting preferredDisplayModeId
- or calling setFrameRate().-->
- <integer name="config_defaultRefreshRate">0</integer>
+ <!-- The default refresh rate for a given device. Change this value to set a higher default
+ refresh rate. If the hardware composer on the device supports display modes with a higher
+ refresh rate than the default value specified here, the framework may use those higher
+ refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
+ setFrameRate().
+ If a non-zero value is set for config_defaultPeakRefreshRate, then
+ config_defaultRefreshRate may be set to 0, in which case the value set for
+ config_defaultPeakRefreshRate will act as the default frame rate. -->
+ <integer name="config_defaultRefreshRate">60</integer>
- <!-- The default peak refresh rate for a given device. This value is used to set the
- global peak refresh rate vote, and when set to zero it has no effect on the vote.
- Change this value to non-zero if you want to prevent the framework from using higher
- refresh rates, even if display modes with higher refresh rates are available from
- hardware composer. -->
+ <!-- The default peak refresh rate for a given device. Change this value if you want to prevent
+ the framework from using higher refresh rates, even if display modes with higher refresh
+ rates are available from hardware composer. Only has an effect if the value is
+ non-zero. -->
<integer name="config_defaultPeakRefreshRate">0</integer>
<!-- External display peak refresh rate for the given device. Change this value if you want to
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b8a399b..4cf37df 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3146,6 +3146,7 @@
<java-symbol type="drawable" name="ic_collapse_notification" />
<java-symbol type="drawable" name="ic_expand_bundle" />
<java-symbol type="drawable" name="ic_collapse_bundle" />
+ <java-symbol type="drawable" name="ic_notification_summary_auto" />
<java-symbol type="dimen" name="notification_header_shrink_min_width" />
<java-symbol type="dimen" name="notification_header_shrink_hide_width" />
<java-symbol type="dimen" name="notification_content_margin_start" />
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 296c451..262f167 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -31,6 +31,7 @@
import static org.junit.Assert.assertThrows;
+import android.Manifest;
import android.app.compat.CompatChanges;
import android.graphics.Bitmap;
import android.hardware.broadcastradio.ConfigFlag;
@@ -57,6 +58,8 @@
import android.util.ArrayMap;
import android.util.ArraySet;
+import androidx.test.platform.app.InstrumentationRegistry;
+
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
import com.android.server.broadcastradio.RadioServiceUserController;
@@ -171,6 +174,10 @@
@Before
public void setup() throws Exception {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+ Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+
doReturn(true).when(() -> CompatChanges.isChangeEnabled(
eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt()));
doReturn(USER_ID_1).when(mUserHandleMock).getIdentifier();
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 1b25d7f..ee1a4ac 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -66,6 +66,7 @@
"android.view.accessibility.flags-aconfig-java",
"androidx.core_core",
"androidx.core_core-ktx",
+ "androidx.test.core",
"androidx.test.espresso.core",
"androidx.test.ext.junit",
"androidx.test.runner",
@@ -90,6 +91,7 @@
"flickerlib-parsers",
"flickerlib-trace_processor_shell",
"mockito-target-extended-minus-junit4",
+ "TestParameterInjector",
],
libs: [
diff --git a/core/tests/coretests/res/drawable/adaptiveicon_drawable.xml b/core/tests/coretests/res/drawable/adaptiveicon_drawable.xml
new file mode 100644
index 0000000..dcffe75
--- /dev/null
+++ b/core/tests/coretests/res/drawable/adaptiveicon_drawable.xml
@@ -0,0 +1,22 @@
+<?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.
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@android:color/white"/>
+ <foreground android:drawable="@android:color/black"/>
+ <monochrome android:drawable="@android:color/system_accent2_800"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 49ed3a8..950925f 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -18,8 +18,13 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.app.IUriGrantsManager;
import android.content.ContentProvider;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.Bitmap;
@@ -32,13 +37,15 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
-import android.test.AndroidTestCase;
import android.util.Log;
-import androidx.test.filters.SmallTest;
+import androidx.test.core.app.ApplicationProvider;
import com.android.frameworks.coretests.R;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
@@ -46,13 +53,29 @@
import java.util.ArrayList;
import java.util.Arrays;
-public class IconTest extends AndroidTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public class IconTest {
public static final String TAG = IconTest.class.getSimpleName();
+ private Context mContext;
+
public static void L(String s, Object... parts) {
Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
}
- @SmallTest
+ private Context getContext() {
+ return mContext;
+ }
+
+ @Before
+ public void setup() {
+ mContext = ApplicationProvider.getApplicationContext();
+ }
+
+ @Test
public void testWithBitmap() throws Exception {
final Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
final Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
@@ -119,7 +142,7 @@
}
}
- @SmallTest
+ @Test
public void testScaleDownIfNecessary() throws Exception {
final Bitmap bm = Bitmap.createBitmap(4321, 78, Bitmap.Config.ARGB_8888);
final Icon ic = Icon.createWithBitmap(bm);
@@ -132,7 +155,7 @@
assertThat(ic.getBitmap().getHeight()).isLessThan(21);
}
- @SmallTest
+ @Test
public void testWithAdaptiveBitmap() throws Exception {
final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
@@ -166,7 +189,7 @@
}
}
- @SmallTest
+ @Test
public void testWithBitmapResource() throws Exception {
final Bitmap res1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
.getBitmap();
@@ -193,7 +216,7 @@
* Icon resource test that ensures we can load and draw non-bitmaps. (In this case,
* stat_sys_adb is assumed, and asserted, to be a vector drawable.)
*/
- @SmallTest
+ @Test
public void testWithStatSysAdbResource() throws Exception {
// establish reference bitmap
final float dp = getContext().getResources().getDisplayMetrics().density;
@@ -244,7 +267,7 @@
}
}
- @SmallTest
+ @Test
public void testWithFile() throws Exception {
final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
.getBitmap();
@@ -268,7 +291,55 @@
}
}
- @SmallTest
+ @Test
+ public void testWithAdaptiveIconResource_useMonochrome() throws Exception {
+ final int colorMono = ((ColorDrawable) getContext().getDrawable(
+ android.R.color.system_accent2_800)).getColor();
+ final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+ R.drawable.adaptiveicon_drawable, true, 0.0f);
+ final Drawable draw1 = im1.loadDrawable(mContext);
+ assertThat(draw1 instanceof InsetDrawable).isTrue();
+ ColorDrawable colorDrawable = (ColorDrawable) ((DrawableWrapper) draw1).getDrawable();
+ assertThat(colorDrawable.getColor()).isEqualTo(colorMono);
+ }
+
+ @Test
+ public void testWithAdaptiveIconResource_dontUseMonochrome() throws Exception {
+ final int colorMono = ((ColorDrawable) getContext().getDrawable(
+ android.R.color.system_accent2_800)).getColor();
+ final int colorFg = ((ColorDrawable) getContext().getDrawable(
+ android.R.color.black)).getColor();
+ final int colorBg = ((ColorDrawable) getContext().getDrawable(
+ android.R.color.white)).getColor();
+
+ final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+ R.drawable.adaptiveicon_drawable, false , 0.0f);
+ final Drawable draw1 = im1.loadDrawable(mContext);
+ assertThat(draw1 instanceof AdaptiveIconDrawable).isTrue();
+ ColorDrawable colorDrawableMono = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+ .getMonochrome();
+ assertThat(colorDrawableMono.getColor()).isEqualTo(colorMono);
+ ColorDrawable colorDrawableFg = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+ .getForeground();
+ assertThat(colorDrawableFg.getColor()).isEqualTo(colorFg);
+ ColorDrawable colorDrawableBg = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+ .getBackground();
+ assertThat(colorDrawableBg.getColor()).isEqualTo(colorBg);
+ }
+
+ @Test
+ public void testAdaptiveIconResource_sameAs(@TestParameter boolean useMonochrome)
+ throws Exception {
+ final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+ R.drawable.adaptiveicon_drawable, useMonochrome, 1.0f);
+ final Parcel parcel = Parcel.obtain();
+ im1.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ final Icon im2 = Icon.CREATOR.createFromParcel(parcel);
+ assertThat(im1.sameAs(im2)).isTrue();
+ }
+
+ @Test
public void testAsync() throws Exception {
final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
.getBitmap();
@@ -311,7 +382,7 @@
L(TAG, "asyncTest: done");
}
- @SmallTest
+ @Test
public void testParcel() throws Exception {
final Bitmap originalbits = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
.getBitmap();
@@ -391,7 +462,7 @@
return (int) Math.sqrt(maxNumPixels / aspRatio);
}
- @SmallTest
+ @Test
public void testScaleDownMaxSizeWithBitmap() throws Exception {
final int bmpWidth = 13_000;
final int bmpHeight = 10_000;
@@ -408,7 +479,7 @@
assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
}
- @SmallTest
+ @Test
public void testScaleDownMaxSizeWithAdaptiveBitmap() throws Exception {
final int bmpWidth = 20_000;
final int bmpHeight = 10_000;
@@ -427,7 +498,7 @@
assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
}
- @SmallTest
+ @Test
public void testScaleDownMaxSizeWithResource() throws Exception {
final Icon ic = Icon.createWithResource(getContext(), R.drawable.test_too_big);
final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
@@ -435,7 +506,7 @@
assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
}
- @SmallTest
+ @Test
public void testScaleDownMaxSizeWithFile() throws Exception {
final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.test_too_big))
.getBitmap();
@@ -450,7 +521,7 @@
assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
}
- @SmallTest
+ @Test
public void testScaleDownMaxSizeWithData() throws Exception {
final int bmpBpp = 4;
final Bitmap originalBits = ((BitmapDrawable) getContext().getDrawable(
@@ -465,7 +536,7 @@
assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
}
- @SmallTest
+ @Test
public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException {
int uid = 12345;
String packageName = "test_pkg";
@@ -509,7 +580,7 @@
}
}
- @SmallTest
+ @Test
public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException {
int uid = 12345;
String packageName = "test_pkg";
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index a28bb69..2bd5631 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -219,6 +219,22 @@
workDuration.setActualGpuDurationNanos(6);
s.reportActualWorkDuration(workDuration);
}
+ {
+ WorkDuration workDuration = new WorkDuration();
+ workDuration.setWorkPeriodStartTimestampNanos(1);
+ workDuration.setActualTotalDurationNanos(14);
+ workDuration.setActualCpuDurationNanos(0);
+ workDuration.setActualGpuDurationNanos(6);
+ s.reportActualWorkDuration(workDuration);
+ }
+ {
+ WorkDuration workDuration = new WorkDuration();
+ workDuration.setWorkPeriodStartTimestampNanos(1);
+ workDuration.setActualTotalDurationNanos(14);
+ workDuration.setActualCpuDurationNanos(7);
+ workDuration.setActualGpuDurationNanos(0);
+ s.reportActualWorkDuration(workDuration);
+ }
}
@Test
@@ -242,7 +258,7 @@
s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6, 1));
});
assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6, 1));
+ s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 0, 1));
});
assertThrows(IllegalArgumentException.class, () -> {
s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1, 1));
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 5862711..3d5494d 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -422,19 +422,9 @@
if (!drawDataParcel()) {
return;
}
- final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
- final RemoteViews rv = new RemoteViews(drawInstructions);
- final View view = rv.apply(mContext, mContainer);
- assertTrue(view instanceof RemoteCanvas);
- assertEquals(drawInstructions, view.getTag());
- }
-
- @Test
- public void remoteCanvasWiresClickHandlers() {
- if (!drawDataParcel()) {
- return;
- }
- final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
+ final byte[] bytes = new byte[] {'h', 'e', 'l', 'l', 'o'};
+ final RemoteViews.DrawInstructions drawInstructions =
+ new RemoteViews.DrawInstructions.Builder(Collections.singletonList(bytes)).build();
final RemoteViews rv = new RemoteViews(drawInstructions);
final PendingIntent pi = PendingIntent.getActivity(mContext, 0,
new Intent(Intent.ACTION_VIEW), PendingIntent.FLAG_IMMUTABLE);
@@ -443,15 +433,7 @@
rv.setPendingIntentTemplate(viewId, pi);
rv.setOnClickFillInIntent(viewId, i);
final View view = rv.apply(mContext, mContainer);
- assertTrue(view instanceof RemoteCanvas);
- RemoteCanvas target = (RemoteCanvas) view;
- assertEquals(1, target.getCallbacks().size());
- assertNotNull(target.getCallbacks().get(viewId));
- }
-
- private RemoteViews.DrawInstructions getDrawInstructions() {
- final byte[] bytes = new byte[] {'h', 'e', 'l', 'l', 'o'};
- return new RemoteViews.DrawInstructions.Builder(Collections.singletonList(bytes)).build();
+ assertEquals(drawInstructions, view.getTag());
}
private RemoteViews createViewChained(int depth, String... texts) {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 91e620c..0baaff0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -185,6 +185,7 @@
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<permission name="android.permission.UWB_PRIVILEGED"/>
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+ <permission name="android.permission.UPDATE_CONFIG"/>
<permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 45e29a8..f359025 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -154,6 +154,12 @@
// TYPE_DATA: data offset
private int mInt2;
+ // TYPE_RESOURCE: use the monochrome drawable from an AdaptiveIconDrawable
+ private boolean mUseMonochrome = false;
+
+ // TYPE_RESOURCE: wrap the monochrome drawable in an InsetDrawable with the specified inset
+ private float mInsetScale = 0.0f;
+
/**
* Gets the type of the icon provided.
* <p>
@@ -368,10 +374,34 @@
result.setTintList(mTintList);
result.setTintBlendMode(mBlendMode);
}
+
+ if (mUseMonochrome) {
+ return crateMonochromeDrawable(result, mInsetScale);
+ }
+
return result;
}
/**
+ * Gets the monochrome drawable from an {@link AdaptiveIconDrawable}.
+ *
+ * @param drawable An {@link AdaptiveIconDrawable}
+ * @return Adjusted (wrapped in {@link InsetDrawable}) monochrome drawable
+ * from an {@link AdaptiveIconDrawable}.
+ * Or the original drawable if no monochrome layer exists.
+ */
+ private static Drawable crateMonochromeDrawable(Drawable drawable, float inset) {
+ if (drawable instanceof AdaptiveIconDrawable) {
+ Drawable monochromeDrawable = ((AdaptiveIconDrawable) drawable).getMonochrome();
+ // wrap with negative inset => scale icon (inspired from BaseIconFactory)
+ if (monochromeDrawable != null) {
+ return new InsetDrawable(monochromeDrawable, inset);
+ }
+ }
+ return drawable;
+ }
+
+ /**
* Resizes image if size too large for Canvas to draw
* @param bitmap Bitmap to be resized if size > {@link RecordingCanvas.MAX_BITMAP_SIZE}
* @return resized bitmap
@@ -693,7 +723,9 @@
&& Arrays.equals(getDataBytes(), otherIcon.getDataBytes());
case TYPE_RESOURCE:
return getResId() == otherIcon.getResId()
- && Objects.equals(getResPackage(), otherIcon.getResPackage());
+ && Objects.equals(getResPackage(), otherIcon.getResPackage())
+ && mUseMonochrome == otherIcon.mUseMonochrome
+ && mInsetScale == otherIcon.mInsetScale;
case TYPE_URI:
case TYPE_URI_ADAPTIVE_BITMAP:
return Objects.equals(getUriString(), otherIcon.getUriString());
@@ -748,6 +780,26 @@
}
/**
+ * Create an Icon pointing to a drawable resource.
+ * @param resPackage Name of the package containing the resource in question
+ * @param resId ID of the drawable resource
+ * @param useMonochrome if this icon should use the monochrome res from the adaptive drawable
+ * @hide
+ */
+ public static @NonNull Icon createWithResourceAdaptiveDrawable(@NonNull String resPackage,
+ @DrawableRes int resId, boolean useMonochrome, float inset) {
+ if (resPackage == null) {
+ throw new IllegalArgumentException("Resource package name must not be null.");
+ }
+ final Icon rep = new Icon(TYPE_RESOURCE);
+ rep.mInt1 = resId;
+ rep.mUseMonochrome = useMonochrome;
+ rep.mInsetScale = inset;
+ rep.mString1 = resPackage;
+ return rep;
+ }
+
+ /**
* Create an Icon pointing to a bitmap in memory.
* @param bits A valid {@link android.graphics.Bitmap} object
*/
@@ -986,6 +1038,8 @@
final int resId = in.readInt();
mString1 = pkg;
mInt1 = resId;
+ mUseMonochrome = in.readBoolean();
+ mInsetScale = in.readFloat();
break;
case TYPE_DATA:
final int len = in.readInt();
@@ -1027,6 +1081,8 @@
case TYPE_RESOURCE:
dest.writeString(getResPackage());
dest.writeInt(getResId());
+ dest.writeBoolean(mUseMonochrome);
+ dest.writeFloat(mInsetScale);
break;
case TYPE_DATA:
dest.writeInt(getDataLength());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 50a58da..a2a2914 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -19,6 +19,7 @@
import static android.os.AsyncTask.Status.FINISHED;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.DimenRes;
import android.annotation.Hide;
@@ -47,6 +48,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
@@ -466,6 +468,7 @@
* Call when all the views should be removed/cleaned up.
*/
public void cleanupViews() {
+ ProtoLog.d(WM_SHELL_BUBBLES, "Bubble#cleanupViews=%s", getKey());
cleanupViews(true);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index a43a951..621c453 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -23,7 +23,6 @@
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
@@ -435,6 +434,9 @@
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
for (Bubble b : mBubbleData.getBubbles()) {
if (task.taskId == b.getTaskId()) {
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s",
+ task.taskId, b.getKey());
mBubbleData.setSelectedBubble(b);
mBubbleData.setExpanded(true);
return;
@@ -442,6 +444,9 @@
}
for (Bubble b : mBubbleData.getOverflowBubbles()) {
if (task.taskId == b.getTaskId()) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d "
+ + "selecting matching overflow bubble=%s",
+ task.taskId, b.getKey());
promoteBubbleFromOverflow(b);
mBubbleData.setExpanded(true);
return;
@@ -581,10 +586,15 @@
// Hide the stack temporarily if the status bar has been made invisible, and the stack
// is collapsed. An expanded stack should remain visible until collapsed.
mStackView.setTemporarilyInvisible(!visible && !isStackExpanded());
+ ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarVisibilityChanged=%b stackExpanded=%b",
+ visible, isStackExpanded());
}
}
private void onZenStateChanged() {
+ if (hasBubbles()) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onZenStateChanged");
+ }
for (Bubble b : mBubbleData.getBubbles()) {
b.setShowDot(b.showInShade());
}
@@ -593,9 +603,10 @@
@VisibleForTesting
public void onStatusBarStateChanged(boolean isShade) {
boolean didChange = mIsStatusBarShade != isShade;
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG, "onStatusBarStateChanged isShade=" + isShade + " didChange=" + didChange);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarStateChanged "
+ + "isShade=%b didChange=%b mNotifEntryToExpandOnShadeUnlock=%s",
+ isShade, didChange, (mNotifEntryToExpandOnShadeUnlock != null
+ ? mNotifEntryToExpandOnShadeUnlock.getKey() : "null"));
mIsStatusBarShade = isShade;
if (!mIsStatusBarShade && didChange) {
// Only collapse stack on change
@@ -611,6 +622,8 @@
@VisibleForTesting
public void onBubbleMetadataFlagChanged(Bubble bubble) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onBubbleMetadataFlagChanged=%s flags=%d",
+ bubble.getKey(), bubble.getFlags());
// Make sure NoMan knows suppression state so that anyone querying it can tell.
try {
mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags());
@@ -623,6 +636,8 @@
/** Called when the current user changes. */
@VisibleForTesting
public void onUserChanged(int newUserId) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onUserChanged currentUser=%d newUser=%d",
+ mCurrentUserId, newUserId);
saveBubbles(mCurrentUserId);
mCurrentUserId = newUserId;
@@ -825,6 +840,7 @@
*/
void updateWindowFlagsForBackpress(boolean interceptBack) {
if (mAddedToWindowManager) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "updateFlagsForBackPress interceptBack=%b", interceptBack);
mWmLayoutParams.flags = interceptBack
? 0
: WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -1014,8 +1030,9 @@
}
private void onNotificationPanelExpandedChanged(boolean expanded) {
- ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded);
if (mStackView != null && mStackView.isExpanded()) {
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "onNotificationPanelExpandedChanged expanded=%b", expanded);
if (expanded) {
mStackView.stopMonitoringSwipeUpGesture();
} else {
@@ -1096,6 +1113,7 @@
/** Promote the provided bubble from the overflow view. */
public void promoteBubbleFromOverflow(Bubble bubble) {
mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
+ ProtoLog.d(WM_SHELL_BUBBLES, "promoteBubbleFromOverflow=%s", bubble.getKey());
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.setShouldAutoExpand(true);
bubble.markAsAccessedAt(System.currentTimeMillis());
@@ -1211,11 +1229,8 @@
// Skip update, but store it in user bubbles so it gets restored after user switch
mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(),
true /* shownInShade */);
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG,
- "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId
- + " current userId=" + mCurrentUserId);
- }
+ Log.w(TAG, "updateBubble, ignore update for non-active user=" + bubbleUserId
+ + " currentUser=" + mCurrentUserId);
}
}
@@ -1252,7 +1267,9 @@
}
String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
- Log.i(TAG, "showOrHideAppBubble, with key: " + appBubbleKey);
+ Log.i(TAG, "showOrHideAppBubble, key= " + appBubbleKey + " stackVisibility= "
+ + (mStackView != null ? mStackView.getVisibility() : " null ")
+ + " statusBarShade=" + mIsStatusBarShade);
PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
if (!isResizableActivity(intent, packageManager, appBubbleKey)) return;
@@ -1769,18 +1786,19 @@
@Override
public void applyUpdate(BubbleData.Update update) {
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG, "applyUpdate:" + " bubbleAdded=" + (update.addedBubble != null)
- + " bubbleRemoved="
- + (update.removedBubbles != null && update.removedBubbles.size() > 0)
- + " bubbleUpdated=" + (update.updatedBubble != null)
- + " orderChanged=" + update.orderChanged
- + " expandedChanged=" + update.expandedChanged
- + " selectionChanged=" + update.selectionChanged
- + " suppressed=" + (update.suppressedBubble != null)
- + " unsuppressed=" + (update.unsuppressedBubble != null)
- + " shouldShowEducation=" + update.shouldShowEducation);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:"
+ + " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b"
+ + " expanded=%b selectionChanged=%b selected=%s"
+ + " suppressed=%s unsupressed=%s shouldShowEducation=%b",
+ update.addedBubble != null ? update.addedBubble.getKey() : "null",
+ update.removedBubbles.isEmpty(),
+ update.updatedBubble != null ? update.updatedBubble.getKey() : "null",
+ update.orderChanged, update.expandedChanged, update.expanded,
+ update.selectionChanged,
+ update.selectedBubble != null ? update.selectedBubble.getKey() : "null",
+ update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null",
+ update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null",
+ update.shouldShowEducation);
ensureBubbleViewsAndWindowCreated();
@@ -1974,7 +1992,8 @@
if (mStackView == null && mLayerView == null) {
return;
}
-
+ ProtoLog.v(WM_SHELL_BUBBLES, "updateBubbleViews mIsStatusBarShade=%s hasBubbles=%s",
+ mIsStatusBarShade, hasBubbles());
if (!mIsStatusBarShade) {
// Bubbles don't appear when the device is locked.
if (mStackView != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index dbfa260..6d3f0c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -16,10 +16,9 @@
package com.android.wm.shell.bubbles;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
-import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.app.PendingIntent;
@@ -36,6 +35,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
@@ -333,9 +333,6 @@
}
public void setExpanded(boolean expanded) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setExpanded: " + expanded);
- }
setExpandedInternal(expanded);
dispatchPendingChanges();
}
@@ -347,9 +344,8 @@
* updated to have the correct state.
*/
public void setSelectedBubbleFromLauncher(BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubbleFromLauncher: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleFromLauncher=%s",
+ (bubble != null ? bubble.getKey() : "null"));
mExpanded = true;
if (Objects.equals(bubble, mSelectedBubble)) {
return;
@@ -370,9 +366,6 @@
}
public void setSelectedBubble(BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubble: " + bubble);
- }
setSelectedBubbleInternal(bubble);
dispatchPendingChanges();
}
@@ -430,12 +423,13 @@
* BubbleBarLayerView, BubbleIconFactory, boolean)
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryUpdated: " + bubble);
- }
mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
suppressFlyout |= !bubble.isTextChanged();
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "notifEntryUpdated=%s prevBubble=%b suppressFlyout=%b showInShade=%b autoExpand=%b",
+ bubble.getKey(), (prevBubble != null), suppressFlyout, showInShade,
+ bubble.shouldAutoExpand());
if (prevBubble == null) {
// Create a new bubble
@@ -483,9 +477,6 @@
* Dismisses the bubble with the matching key, if it exists.
*/
public void dismissBubbleWithKey(String key, @DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason);
- }
doRemove(key, reason);
dispatchPendingChanges();
}
@@ -603,9 +594,7 @@
}
private void doAdd(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doAdd: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doAdd=%s", bubble.getKey());
mBubbles.add(0, bubble);
mStateChange.addedBubble = bubble;
// Adding the first bubble doesn't change the order
@@ -634,9 +623,7 @@
}
private void doUpdate(Bubble bubble, boolean reorder) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doUpdate: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "BubbleData - doUpdate=%s", bubble.getKey());
mStateChange.updatedBubble = bubble;
if (!isExpanded() && reorder) {
int prevPos = mBubbles.indexOf(bubble);
@@ -663,9 +650,6 @@
}
private void doRemove(String key, @DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA || (key != null && key.contains(KEY_APP_BUBBLE))) {
- Log.d(TAG, "doRemove: " + key + " reason: " + reason);
- }
// If it was pending remove it
if (mPendingBubbles.containsKey(key)) {
mPendingBubbles.remove(key);
@@ -686,9 +670,7 @@
&& shouldRemoveHiddenBubble) {
Bubble b = getOverflowBubbleWithKey(key);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Cancel overflow bubble: " + b);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel overflow bubble=%s", key);
if (b != null) {
b.stopInflation();
}
@@ -699,9 +681,7 @@
}
if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) {
Bubble b = getSuppressedBubbleWithKey(key);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Cancel suppressed bubble: " + b);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel suppressed bubble=%s", key);
if (b != null) {
mSuppressedBubbles.remove(b.getLocusId());
b.stopInflation();
@@ -711,6 +691,7 @@
return;
}
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove=%s", bubbleToRemove.getKey());
bubbleToRemove.stopInflation();
overflowBubble(reason, bubbleToRemove);
@@ -744,17 +725,12 @@
}
// Move selection to the new bubble at the same position.
int newIndex = Math.min(indexOfSelected, mBubbles.size() - 1);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setNewSelectedIndex: " + indexOfSelected);
- }
BubbleViewProvider newSelected = mBubbles.get(newIndex);
setSelectedBubbleInternal(newSelected);
}
private void doSuppress(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doSuppressed: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doSuppress=%s", bubble.getKey());
mStateChange.suppressedBubble = bubble;
bubble.setSuppressBubble(true);
@@ -777,9 +753,7 @@
}
private void doUnsuppress(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doUnsuppressed: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doUnsuppress=%s", bubble.getKey());
bubble.setSuppressBubble(false);
mStateChange.unsuppressedBubble = bubble;
mBubbles.add(bubble);
@@ -801,9 +775,7 @@
|| reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
return;
}
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Overflowing: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "overflowBubble=%s", bubble.getKey());
mLogger.logOverflowAdd(bubble, reason);
mOverflowBubbles.remove(bubble);
mOverflowBubbles.add(0, bubble);
@@ -812,9 +784,7 @@
if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
// Remove oldest bubble.
Bubble oldest = mOverflowBubbles.get(mOverflowBubbles.size() - 1);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Overflow full. Remove: " + oldest);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "overflow full, remove=%s", oldest.getKey());
mStateChange.bubbleRemoved(oldest, Bubbles.DISMISS_OVERFLOW_MAX_REACHED);
mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_MAX_REACHED);
mOverflowBubbles.remove(oldest);
@@ -823,9 +793,7 @@
}
public void dismissAll(@DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "dismissAll: reason=" + reason);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "dismissAll reason=%d", reason);
if (mBubbles.isEmpty() && mSuppressedBubbles.isEmpty()) {
return;
}
@@ -851,9 +819,10 @@
* @param visible whether the task with the locusId is visible or not.
*/
public void onLocusVisibilityChanged(int taskId, LocusId locusId, boolean visible) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "onLocusVisibilityChanged: " + locusId + " visible=" + visible);
- }
+ if (locusId == null) return;
+
+ ProtoLog.d(WM_SHELL_BUBBLES, "onLocusVisibilityChanged=%s visible=%b taskId=%d",
+ locusId.getId(), visible, taskId);
Bubble matchingBubble = getBubbleInStackWithLocusId(locusId);
// Don't add the locus if it's from a bubble'd activity, we only suppress for non-bubbled.
@@ -910,9 +879,8 @@
* @param bubble the new selected bubble
*/
private void setSelectedBubbleInternal(@Nullable BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleInternal=%s",
+ (bubble != null ? bubble.getKey() : "null"));
if (Objects.equals(bubble, mSelectedBubble)) {
return;
}
@@ -969,12 +937,10 @@
* @param shouldExpand the new requested state
*/
private void setExpandedInternal(boolean shouldExpand) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
- }
if (mExpanded == shouldExpand) {
return;
}
+ ProtoLog.d(WM_SHELL_BUBBLES, "setExpandedInternal=%b", shouldExpand);
if (shouldExpand) {
if (mBubbles.isEmpty() && !mShowingOverflow) {
Log.e(TAG, "Attempt to expand stack when empty!");
@@ -1026,9 +992,6 @@
* @return true if the position of any bubbles changed as a result
*/
private boolean repackAll() {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "repackAll()");
- }
if (mBubbles.isEmpty()) {
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index f56b171..f1a68e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -37,17 +37,7 @@
// Default log tag for the Bubbles package.
public static final String TAG_BUBBLES = "Bubbles";
-
- static final boolean DEBUG_BUBBLE_CONTROLLER = false;
- static final boolean DEBUG_BUBBLE_DATA = false;
- static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
- static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
- static final boolean DEBUG_EXPERIMENTS = true;
- static final boolean DEBUG_OVERFLOW = false;
public static final boolean DEBUG_USER_EDUCATION = false;
- static final boolean DEBUG_POSITIONER = false;
- public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
- public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
private static final boolean FORCE_SHOW_USER_EDUCATION = false;
private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index efc4d8b..088660e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -23,10 +23,10 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -67,6 +67,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.AlphaOptimizedButton;
import com.android.wm.shell.common.TriangleShape;
@@ -199,13 +200,9 @@
@Override
public void onInitialized() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
- + " initialized=" + mInitialized
- + " bubble=" + getBubbleKey());
- }
-
if (mDestroyed || mInitialized) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
+ mDestroyed, mInitialized, getBubbleKey());
return;
}
@@ -216,10 +213,8 @@
// TODO: I notice inconsistencies in lifecycle
// Post to keep the lifecycle normal
post(() -> {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: calling startActivity, bubble="
- + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
+ getBubbleKey());
try {
Rect launchBounds = new Rect();
mTaskView.getBoundsOnScreen(launchBounds);
@@ -279,10 +274,8 @@
@Override
public void onTaskCreated(int taskId, ComponentName name) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskCreated: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
@@ -298,15 +291,15 @@
@Override
public void onTaskVisibilityChanged(int taskId, boolean visible) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskVisibilityChanged=%b bubble=%s taskId=%d",
+ visible, getBubbleKey(), taskId);
setContentVisibility(visible);
}
@Override
public void onTaskRemovalStarted(int taskId) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
if (mBubble != null) {
mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
}
@@ -644,9 +637,6 @@
super.onDetachedFromWindow();
mImeVisible = false;
mNeedsNewHeight = false;
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
- }
}
/**
@@ -805,10 +795,6 @@
* and setting {@code false} actually means rendering the contents in transparent.
*/
public void setContentVisibility(boolean visibility) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "setContentVisibility: visibility=" + visibility
- + " bubble=" + getBubbleKey());
- }
mIsContentVisible = visibility;
if (mTaskView != null && !mIsAnimating) {
mTaskView.setAlpha(visibility ? 1f : 0f);
@@ -867,9 +853,6 @@
* Sets the bubble used to populate this view.
*/
void update(Bubble bubble) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "update: bubble=" + bubble);
- }
if (mStackView == null) {
Log.w(TAG, "Stack is null for bubble: " + bubble);
return;
@@ -958,11 +941,6 @@
}
mNeedsNewHeight = false;
}
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()
- + " height=" + height
- + " mNeedsNewHeight=" + mNeedsNewHeight);
- }
}
}
@@ -974,10 +952,6 @@
* waiting for layout.
*/
public void updateView(int[] containerLocationOnScreen) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateView: bubble="
- + getBubbleKey());
- }
mExpandedViewContainerLocation = containerLocationOnScreen;
updateHeight();
if (mTaskView != null
@@ -1103,9 +1077,6 @@
/** Hide the task view. */
public void cleanUpExpandedState() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
- }
if (mTaskView != null) {
mTaskView.setVisibility(GONE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index 9655470..70cdc82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -16,9 +16,9 @@
package com.android.wm.shell.bubbles;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.content.Context;
@@ -28,7 +28,6 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -43,6 +42,7 @@
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ContrastColorUtil;
import com.android.wm.shell.R;
@@ -245,9 +245,6 @@
Bubble toRemove = update.removedOverflowBubble;
if (toRemove != null) {
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "remove: " + toRemove);
- }
toRemove.cleanupViews();
final int indexToRemove = mOverflowBubbles.indexOf(toRemove);
mOverflowBubbles.remove(toRemove);
@@ -257,9 +254,6 @@
Bubble toAdd = update.addedOverflowBubble;
if (toAdd != null) {
final int indexToAdd = mOverflowBubbles.indexOf(toAdd);
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "add: " + toAdd + " prevIndex: " + indexToAdd);
- }
if (indexToAdd > 0) {
mOverflowBubbles.remove(toAdd);
mOverflowBubbles.add(0, toAdd);
@@ -272,10 +266,9 @@
updateEmptyStateVisibility();
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(
- mController.getOverflowBubbles(), null));
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Apply overflow update, added=%s removed=%s",
+ (toAdd != null ? toAdd.getKey() : "null"),
+ (toRemove != null ? toRemove.getKey() : "null"));
}
};
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index d62c86c..c03b6f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -16,18 +16,20 @@
package com.android.wm.shell.bubbles;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
@@ -36,9 +38,6 @@
* placement and positioning calculations to refer to.
*/
public class BubblePositioner {
- private static final String TAG = BubbleDebugConfig.TAG_WITH_CLASS_NAME
- ? "BubblePositioner"
- : BubbleDebugConfig.TAG_BUBBLES;
/** The screen edge the bubble stack is pinned to */
public enum StackPinnedEdge {
@@ -110,16 +109,12 @@
*/
public void update(DeviceConfig deviceConfig) {
mDeviceConfig = deviceConfig;
-
- if (BubbleDebugConfig.DEBUG_POSITIONER) {
- Log.w(TAG, "update positioner:"
- + " rotation: " + mRotation
- + " insets: " + deviceConfig.getInsets()
- + " isLargeScreen: " + deviceConfig.isLargeScreen()
- + " isSmallTablet: " + deviceConfig.isSmallTablet()
- + " showingInBubbleBar: " + mShowingInBubbleBar
- + " bounds: " + deviceConfig.getWindowBounds());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "update positioner: "
+ + "rotation=%d insets=%s largeScreen=%b "
+ + "smallTablet=%b isBubbleBar=%b bounds=%s",
+ mRotation, deviceConfig.getInsets(), deviceConfig.isLargeScreen(),
+ deviceConfig.isSmallTablet(), mShowingInBubbleBar,
+ deviceConfig.getWindowBounds());
updateInternal(mRotation, deviceConfig.getInsets(), deviceConfig.getWindowBounds());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 9facef3..c5bc9eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -21,7 +21,6 @@
import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
@@ -1310,13 +1309,9 @@
final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
&& mExpandedBubble != null && mExpandedBubble.getExpandedView() != null;
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Show manage edu: " + shouldShow);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Show manage edu=%b", shouldShow);
if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Want to show manage edu, but it is forced hidden");
- }
+ Log.w(TAG, "Want to show manage edu, but it is forced hidden");
return false;
}
return shouldShow;
@@ -1360,13 +1355,9 @@
}
final boolean seen = getPrefBoolean(StackEducationView.PREF_STACK_EDUCATION);
final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Show stack edu: " + shouldShow);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Show stack edu=%b", shouldShow);
if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Want to show stack edu, but it is forced hidden");
- }
+ Log.w(TAG, "Want to show stack edu, but it is forced hidden");
return false;
}
return shouldShow;
@@ -1513,7 +1504,7 @@
mBubbleSize = mPositioner.getBubbleSize();
for (Bubble b : mBubbleData.getBubbles()) {
if (b.getIconView() == null) {
- Log.d(TAG, "Display size changed. Icon null: " + b);
+ Log.w(TAG, "Display size changed. Icon null: " + b);
continue;
}
b.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
@@ -1819,10 +1810,6 @@
// via BubbleData.Listener
@SuppressLint("ClickableViewAccessibility")
void addBubble(Bubble bubble) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "addBubble: " + bubble);
- }
-
final boolean firstBubble = getBubbleCount() == 0;
if (firstBubble && shouldShowStackEdu()) {
@@ -1868,9 +1855,6 @@
// via BubbleData.Listener
void removeBubble(Bubble bubble) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "removeBubble: " + bubble);
- }
if (isExpanded() && getBubbleCount() == 1) {
mRemovingLastBubbleWhileExpanded = true;
// We're expanded while the last bubble is being removed. Let the scrim animate away
@@ -1917,7 +1901,7 @@
bubble.cleanupViews();
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
} else {
- Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
+ Log.w(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
}
}
@@ -1985,10 +1969,6 @@
*/
// via BubbleData.Listener
public void setSelectedBubble(@Nullable BubbleViewProvider bubbleToSelect) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
- }
-
if (bubbleToSelect == null) {
mBubbleData.setShowingOverflow(false);
return;
@@ -2081,10 +2061,6 @@
*/
// via BubbleData.Listener
public void setExpanded(boolean shouldExpand) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setExpanded: " + shouldExpand);
- }
-
if (!shouldExpand) {
// If we're collapsing, release the animating-out surface immediately since we have no
// need for it, and this ensures it cannot remain visible as we collapse.
@@ -2126,7 +2102,6 @@
* Monitor for swipe up gesture that is used to collapse expanded view
*/
void startMonitoringSwipeUpGesture() {
- ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
if (isGestureNavEnabled()) {
@@ -2174,7 +2149,6 @@
* Stop monitoring for swipe up gesture
*/
void stopMonitoringSwipeUpGesture() {
- ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
}
@@ -2202,9 +2176,6 @@
}
void setBubbleSuppressed(Bubble bubble, boolean suppressed) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setBubbleSuppressed: suppressed=" + suppressed + " bubble=" + bubble);
- }
if (suppressed) {
int index = getBubbleIndex(bubble);
mBubbleContainer.removeViewAt(index);
@@ -2339,6 +2310,8 @@
}
private void animateExpansion() {
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateExpansion, expandedBubble=%s",
+ mExpandedBubble != null ? mExpandedBubble.getKey() : "null");
cancelDelayedExpandCollapseSwitchAnimations();
final boolean showVertically = mPositioner.showBubblesVertically();
mIsExpanded = true;
@@ -2465,7 +2438,7 @@
private void animateCollapse() {
cancelDelayedExpandCollapseSwitchAnimations();
-
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateCollapse");
if (isManageEduVisible()) {
mManageEduView.hide();
}
@@ -2508,11 +2481,6 @@
mManageEduView.hide();
}
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "animateCollapse");
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
- mExpandedBubble));
- }
updateZOrder();
updateBadges(true /* setBadgeForCollapsedStack */);
afterExpandedViewAnimation();
@@ -2612,6 +2580,18 @@
mExpandedViewTemporarilyHidden = false;
mIsBubbleSwitchAnimating = false;
mExpandedViewContainer.setAnimationMatrix(null);
+
+ // When a bubble is being dragged, the expanded view is temporarily hidden.
+ // If the motion ends with dismissing the bubble, with multiple bubbles in
+ // the stack, we'll end up here to switch to the new bubble. However, the
+ // expanded view animation might not actually set the z ordering for the
+ // expanded view correctly, because the view may still be temporarily
+ // hidden. So set it again here.
+ BubbleExpandedView bev = mExpandedBubble.getExpandedView();
+ if (bev != null) {
+ mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+ mExpandedBubble.getExpandedView().setAnimating(false);
+ }
})
.start();
}, 25);
@@ -2691,7 +2671,7 @@
if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) {
mExpandedViewContainer.animate().translationY(newExpandedViewTop);
}
- List<Animator> animList = new ArrayList();
+ List<Animator> animList = new ArrayList<>();
for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
View child = mBubbleContainer.getChildAt(i);
float transY = mPositioner.getExpandedBubbleXY(i, getState()).y;
@@ -2894,7 +2874,7 @@
if (!shouldShowFlyout(bubble)) {
return;
}
-
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateFlyout=%s", bubble.getKey());
mFlyoutDragDeltaX = 0f;
clearFlyoutOnHide();
mAfterFlyoutHidden = () -> {
@@ -3036,6 +3016,9 @@
@VisibleForTesting
public void showManageMenu(boolean show) {
if ((mManageMenu.getVisibility() == VISIBLE) == show) return;
+ ProtoLog.d(WM_SHELL_BUBBLES, "showManageMenu=%b for bubble=%s",
+ show, (mExpandedBubble != null ? mExpandedBubble.getKey() : "null"));
+
mShowingManage = show;
// This should not happen, since the manage menu is only visible when there's an expanded
@@ -3167,10 +3150,6 @@
}
private void updateExpandedBubble() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "updateExpandedBubble()");
- }
-
mExpandedViewContainer.removeAllViews();
if (mIsExpanded && mExpandedBubble != null
&& mExpandedBubble.getExpandedView() != null) {
@@ -3318,9 +3297,6 @@
}
private void updateExpandedView() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
- }
boolean isOverflowExpanded = mExpandedBubble != null
&& BubbleOverflow.KEY.equals(mExpandedBubble.getKey());
int[] paddings = mPositioner.getExpandedViewContainerPadding(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index dc27133..530ec5a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -20,7 +20,7 @@
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -35,6 +35,7 @@
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.taskview.TaskView;
/**
@@ -79,11 +80,8 @@
@Override
public void onInitialized() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
- + " initialized=" + mInitialized
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
+ mDestroyed, mInitialized, getBubbleKey());
if (mDestroyed || mInitialized) {
return;
@@ -99,10 +97,8 @@
// TODO: I notice inconsistencies in lifecycle
// Post to keep the lifecycle normal
mParentView.post(() -> {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: calling startActivity, bubble="
- + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
+ getBubbleKey());
try {
options.setTaskAlwaysOnTop(true);
options.setLaunchedFromBubble(true);
@@ -159,10 +155,8 @@
@Override
public void onTaskCreated(int taskId, ComponentName name) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskCreated: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
@@ -178,10 +172,8 @@
@Override
public void onTaskRemovalStarted(int taskId) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
if (mBubble != null) {
mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
index 845dca3..e43609f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
@@ -15,14 +15,11 @@
*/
package com.android.wm.shell.bubbles.animation;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_COLLAPSE_ANIMATOR;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_EXPANDED_VIEW_DRAGGING;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubbleExpandedView.BACKGROUND_ALPHA;
import static com.android.wm.shell.bubbles.BubbleExpandedView.BOTTOM_CLIP_PROPERTY;
import static com.android.wm.shell.bubbles.BubbleExpandedView.CONTENT_ALPHA;
import static com.android.wm.shell.bubbles.BubbleExpandedView.MANAGE_BUTTON_ALPHA;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -31,7 +28,6 @@
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
-import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.ViewConfiguration;
@@ -41,6 +37,7 @@
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.bubbles.BubbleExpandedView;
@@ -55,8 +52,6 @@
*/
public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimationController {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "ExpandedViewAnimCtrl" : TAG_BUBBLES;
-
private static final float COLLAPSE_THRESHOLD = 0.02f;
private static final int COLLAPSE_DURATION_MS = 250;
@@ -121,9 +116,6 @@
@Override
public void setExpandedView(BubbleExpandedView expandedView) {
if (mExpandedView != null) {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "updating expandedView, resetting previous");
- }
if (mCollapseAnimation != null) {
mCollapseAnimation.cancel();
}
@@ -140,17 +132,14 @@
if (mExpandedView != null) {
mDraggedAmount = OverScroll.dampedScroll(distance, mExpandedView.getContentHeight());
- if (DEBUG_COLLAPSE_ANIMATOR && DEBUG_EXPANDED_VIEW_DRAGGING) {
- Log.d(TAG, "updateDrag: distance=" + distance + " dragged=" + mDraggedAmount);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "updateDrag: distance=%f dragged=%d", distance, mDraggedAmount);
setCollapsedAmount(mDraggedAmount);
if (!mNotifiedAboutThreshold && isPastCollapseThreshold()) {
mNotifiedAboutThreshold = true;
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "notifying over collapse threshold");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "notifying over collapse threshold");
vibrateIfEnabled();
}
}
@@ -172,45 +161,35 @@
if (mSwipeDownVelocity > mMinFlingVelocity) {
// Swipe velocity is positive and over fling velocity.
// This is a swipe down, always reset to expanded state, regardless of dragged amount.
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG,
- "not collapsing expanded view, swipe down velocity: " + mSwipeDownVelocity
- + " minV: " + mMinFlingVelocity);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "not collapsing expanded view, swipe down velocity=%f minV=%d",
+ mSwipeDownVelocity, mMinFlingVelocity);
return false;
}
if (mSwipeUpVelocity > mMinFlingVelocity) {
// Swiping up and over fling velocity, collapse the view.
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG,
- "collapse expanded view, swipe up velocity: " + mSwipeUpVelocity + " minV: "
- + mMinFlingVelocity);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "collapse expanded view, swipe up velocity=%f minV=%d",
+ mSwipeUpVelocity, mMinFlingVelocity);
return true;
}
if (isPastCollapseThreshold()) {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "collapse expanded view, past threshold, dragged: " + mDraggedAmount);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "collapse expanded view, past threshold, dragged=%d", mDraggedAmount);
return true;
}
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "not collapsing expanded view");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "not collapsing expanded view");
return false;
}
@Override
public void animateCollapse(Runnable startStackCollapse, Runnable after) {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG,
- "expandedView animate collapse swipeVel=" + mSwipeUpVelocity + " minFlingVel="
- + mMinFlingVelocity);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d",
+ mSwipeUpVelocity, mMinFlingVelocity);
if (mExpandedView != null) {
// Mark it as animating immediately to avoid updates to the view before animation starts
mExpandedView.setAnimating(true);
@@ -243,9 +222,7 @@
@Override
public void animateBackToExpanded() {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "expandedView animate back to expanded");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate back to expanded");
BubbleExpandedView expandedView = mExpandedView;
if (expandedView == null) {
return;
@@ -298,9 +275,7 @@
@Override
public void reset() {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "reset expandedView collapsed state");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state");
if (mExpandedView == null) {
return;
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index e272958..4c2eff3 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -137,6 +137,16 @@
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
+ /** Checks [standardAppHelper] layer remains visible throughout the animation */
+ @Postsubmit
+ @Test
+ override fun pipAppLayerAlwaysVisible() {
+ // For Maps the transition goes through the UI mode change that adds a snapshot overlay so
+ // we assert only start/end layers matching the app instead.
+ flicker.assertLayersStart { this.isVisible(standardAppHelper.packageNameMatcher) }
+ flicker.assertLayersEnd { this.isVisible(standardAppHelper.packageNameMatcher) }
+ }
+
@Postsubmit
@Test
override fun focusChanges() {
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index c572944..279f9d6 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -484,8 +484,9 @@
WorkDuration& workDuration = *static_cast<WorkDuration*>(workDurationPtr);
VALIDATE_INT(workDuration.workPeriodStartTimestampNanos, > 0)
VALIDATE_INT(workDuration.actualTotalDurationNanos, > 0)
- VALIDATE_INT(workDuration.actualCpuDurationNanos, > 0)
+ VALIDATE_INT(workDuration.actualCpuDurationNanos, >= 0)
VALIDATE_INT(workDuration.actualGpuDurationNanos, >= 0)
+ VALIDATE_INT(workDuration.actualGpuDurationNanos + workDuration.actualCpuDurationNanos, > 0)
return session->reportActualWorkDuration(workDurationPtr);
}
@@ -517,7 +518,7 @@
void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration,
int64_t actualCpuDurationNanos) {
VALIDATE_PTR(aWorkDuration)
- WARN_INT(actualCpuDurationNanos, > 0)
+ WARN_INT(actualCpuDurationNanos, >= 0)
static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos;
}
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 7680136..dd2e174 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -23,7 +23,7 @@
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setWlcEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
- method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterNfcVendorNciCallback(@NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
+ method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterNfcVendorNciCallback(@NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int MESSAGE_TYPE_COMMAND = 1; // 0x1
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 782af5f..252f46f 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -3103,7 +3103,7 @@
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public void unregisterNfcVendorNciCallback(@NonNull NfcVendorNciCallback callback) {
mNfcVendorNciCallbackListener.unregister(callback);
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index f6458c2..ce32ec4 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -46,4 +46,6 @@
],
platform_apis: true,
+
+ generate_product_characteristics_rro: true,
}
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index dffe4e2..5d03ef5 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -390,7 +390,8 @@
return;
}
- CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
+ CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
+ level, levelToString(level));
// Try our best to reset all settings possible, and once finished
// rethrow any exception that we encountered
Exception res = null;
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 30681f3..0ccb07a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -108,7 +108,8 @@
isReqForAllOptions = intent.getBooleanExtra(
Constants.EXTRA_REQ_FOR_ALL_OPTIONS,
/*defaultValue=*/ false
- )
+ ) || (requestInfo?.isShowAllOptionsRequested ?: false) // TODO(b/323552850) - Remove
+ // usage on Constants.EXTRA_REQ_FOR_ALL_OPTIONS once it is deprecated.
val cancellationRequest = getCancelUiRequest(intent)
val cancelUiRequestState = cancellationRequest?.let {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 07f1fa3..f496c1f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -116,8 +116,10 @@
return
}
+ val responseClientState = Bundle()
+ responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false)
val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
- requestId)
+ requestId, responseClientState)
if (getCredRequest == null) {
Log.i(TAG, "No credential manager request found")
callback.onFailure("No credential manager request found")
@@ -153,7 +155,8 @@
return
}
- val fillResponse = convertToFillResponse(result, request)
+ val fillResponse = convertToFillResponse(result, request,
+ responseClientState)
if (fillResponse != null) {
callback.onSuccess(fillResponse)
} else {
@@ -260,7 +263,8 @@
private fun convertToFillResponse(
getCredResponse: GetCandidateCredentialsResponse,
- filLRequest: FillRequest
+ filLRequest: FillRequest,
+ responseClientState: Bundle
): FillResponse? {
val candidateProviders = getCredResponse.candidateProviderDataList
if (candidateProviders.isEmpty()) {
@@ -281,6 +285,7 @@
if (!validFillResponse) {
return null
}
+ fillResponseBuilder.setClientState(responseClientState)
return fillResponseBuilder.build()
}
@@ -578,10 +583,11 @@
private fun getCredManRequest(
structure: AssistStructure,
sessionId: Int,
- requestId: Int
+ requestId: Int,
+ responseClientState: Bundle
): GetCredentialRequest? {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
- traverseStructure(structure, credentialOptions)
+ traverseStructure(structure, credentialOptions, responseClientState)
if (credentialOptions.isNotEmpty()) {
val dataBundle = Bundle()
@@ -596,7 +602,8 @@
private fun traverseStructure(
structure: AssistStructure,
- cmRequests: MutableList<CredentialOption>
+ cmRequests: MutableList<CredentialOption>,
+ responseClientState: Bundle
) {
val windowNodes: List<AssistStructure.WindowNode> =
structure.run {
@@ -604,16 +611,17 @@
}
windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
- traverseNode(windowNode.rootViewNode, cmRequests)
+ traverseNode(windowNode.rootViewNode, cmRequests, responseClientState)
}
}
private fun traverseNode(
viewNode: AssistStructure.ViewNode,
- cmRequests: MutableList<CredentialOption>
+ cmRequests: MutableList<CredentialOption>,
+ responseClientState: Bundle
) {
viewNode.autofillId?.let {
- val options = getCredentialOptionsFromViewNode(viewNode, it)
+ val options = getCredentialOptionsFromViewNode(viewNode, it, responseClientState)
cmRequests.addAll(options)
}
@@ -623,13 +631,14 @@
}
children.forEach { childNode: AssistStructure.ViewNode ->
- traverseNode(childNode, cmRequests)
+ traverseNode(childNode, cmRequests, responseClientState)
}
}
private fun getCredentialOptionsFromViewNode(
viewNode: AssistStructure.ViewNode,
- autofillId: AutofillId
+ autofillId: AutofillId,
+ responseClientState: Bundle
): List<CredentialOption> {
// TODO(b/293945193) Replace with isCredential check from viewNode
val credentialHints: MutableList<String> = mutableListOf()
@@ -637,6 +646,9 @@
for (hint in viewNode.autofillHints!!) {
if (hint.startsWith(CRED_HINT_PREFIX)) {
credentialHints.add(hint.substringAfter(CRED_HINT_PREFIX))
+ if (viewNode.webDomain != null) {
+ responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, true)
+ }
}
}
}
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 790f5b1..f8e22ee 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -58,9 +58,9 @@
scrollable(Screen.SinglePasswordScreen.route) {
SinglePasswordScreen(
- state = viewModel.uiState.value as SingleEntry,
+ credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+ screenIcon = null,
columnState = it.columnState,
- onCloseApp = onCloseApp,
)
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
index c20ee0c..3ed0c9c 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
@@ -30,20 +30,20 @@
@Composable
fun AccountRow(
- name: String,
- email: String,
+ primaryText: String,
+ secondaryText: String,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Text(
- text = name,
+ text = primaryText,
color = Color(0xFFE6FF7B),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = MaterialTheme.typography.title2
)
Text(
- text = email,
+ text = secondaryText,
modifier = Modifier.padding(top = 7.dp),
color = Color(0xFFCAC5BC),
overflow = TextOverflow.Ellipsis,
@@ -57,7 +57,7 @@
@Composable
fun AccountRowPreview() {
AccountRow(
- name = "Elisa Beckett",
- email = "beckett_bakery@gmail.com",
+ primaryText = "Elisa Beckett",
+ secondaryText = "beckett_bakery@gmail.com",
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
index 956c56b..1ddf4af 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
@@ -16,45 +16,24 @@
package com.android.credentialmanager.ui.components
-import androidx.annotation.DrawableRes
+import android.graphics.drawable.Drawable
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material.MaterialTheme
+import androidx.core.graphics.drawable.toBitmap
import androidx.wear.compose.material.Text
-import com.android.credentialmanager.R
-import com.google.android.horologist.annotations.ExperimentalHorologistApi
-import com.google.android.horologist.compose.material.Icon
-import com.google.android.horologist.compose.material.util.DECORATIVE_ELEMENT_CONTENT_DESCRIPTION
-import com.google.android.horologist.compose.tools.WearPreview
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun SignInHeader(
- @DrawableRes icon: Int,
- title: String,
- modifier: Modifier = Modifier,
-) {
- SignInHeader(
- iconContent = {
- Icon(
- id = icon,
- contentDescription = DECORATIVE_ELEMENT_CONTENT_DESCRIPTION
- )
- },
- title = title,
- modifier = modifier,
- )
-}
+import androidx.compose.material3.Icon
+import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
+import androidx.compose.ui.text.style.TextAlign
@Composable
fun SignInHeader(
- iconContent: @Composable ColumnScope.() -> Unit,
+ icon: Drawable?,
title: String,
modifier: Modifier = Modifier,
) {
@@ -62,22 +41,22 @@
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
- iconContent()
+ if (icon != null) {
+ Icon(
+ bitmap = icon.toBitmap().asImageBitmap(),
+ modifier = Modifier.size(32.dp),
+ // Decorative purpose only.
+ contentDescription = null
+ )
+ }
+
Text(
text = title,
+ textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 6.dp)
.padding(horizontal = 10.dp),
- style = MaterialTheme.typography.title3
+ style = WearMaterialTheme.typography.title3
)
}
}
-
-@WearPreview
-@Composable
-fun SignInHeaderPreview() {
- SignInHeader(
- icon = R.drawable.passkey_icon,
- title = stringResource(R.string.use_passkey_title)
- )
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/UiState.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/UiState.kt
new file mode 100644
index 0000000..42a88e2
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/UiState.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.ui.screens.single
+
+import androidx.activity.result.IntentSenderRequest
+
+sealed class UiState {
+ data object CredentialScreen : UiState()
+
+ data class CredentialSelected(
+ val intentSenderRequest: IntentSenderRequest?
+ ) : UiState()
+
+ data object Cancel : UiState()
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index 9558bb0..2878b0b 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -30,6 +30,7 @@
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
+@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasskeyScreen(
name: String,
@@ -40,14 +41,14 @@
SingleAccountScreen(
headerContent = {
SignInHeader(
- icon = R.drawable.passkey_icon,
+ icon = null,
title = stringResource(R.string.use_passkey_title),
)
},
accountContent = {
AccountRow(
- name = name,
- email = email,
+ primaryText = name,
+ secondaryText = email,
modifier = Modifier.padding(top = 10.dp),
)
},
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
index 5463612..a8be944 100644
--- 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
@@ -18,8 +18,9 @@
package com.android.credentialmanager.ui.screens.single.password
-import android.util.Log
+import android.graphics.drawable.Drawable
import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
@@ -29,47 +30,52 @@
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import com.android.credentialmanager.CredentialSelectorUiState
import com.android.credentialmanager.R
-import com.android.credentialmanager.TAG
import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
import com.android.credentialmanager.ui.components.PasswordRow
+import com.android.credentialmanager.ui.components.ContinueChip
+import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.SignInHeader
-import com.android.credentialmanager.ui.model.PasswordUiModel
+import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.ui.screens.single.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
+@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasswordScreen(
- state: SingleEntry,
+ credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
+ screenIcon: Drawable?,
columnState: ScalingLazyColumnState,
- onCloseApp: () -> Unit,
modifier: Modifier = Modifier,
viewModel: SinglePasswordScreenViewModel = hiltViewModel(),
+ navController: NavHostController = rememberNavController(),
) {
- viewModel.initialize(state.entry)
+ viewModel.initialize(credentialSelectorUiState.entry)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when (val state = uiState) {
- SinglePasswordScreenUiState.Idle -> {
- // TODO: b/301206470 implement latency version of the screen
- }
-
- is SinglePasswordScreenUiState.Loaded -> {
+ UiState.CredentialScreen -> {
SinglePasswordScreen(
- passwordUiModel = state.passwordUiModel,
- columnState = columnState,
- modifier = modifier
+ credentialSelectorUiState.entry,
+ screenIcon,
+ columnState,
+ modifier,
+ viewModel
)
}
- is SinglePasswordScreenUiState.PasswordSelected -> {
+ is UiState.CredentialSelected -> {
val launcher = rememberLauncherForActivityResult(
StartBalIntentSenderForResultContract()
) {
- viewModel.onPasswordInfoRetrieved(it.resultCode, it.data)
+ viewModel.onPasswordInfoRetrieved(it.resultCode, null)
}
SideEffect {
@@ -79,37 +85,32 @@
}
}
- 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()
+ UiState.Cancel -> {
+ // TODO(b/322797032) add valid navigation path here for going back
+ navController.popBackStack()
}
}
}
+@OptIn(ExperimentalHorologistApi::class)
@Composable
-fun SinglePasswordScreen(
- passwordUiModel: PasswordUiModel,
+private fun SinglePasswordScreen(
+ entry: CredentialEntryInfo,
+ screenIcon: Drawable?,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
+ viewModel: SinglePasswordScreenViewModel,
) {
SingleAccountScreen(
headerContent = {
SignInHeader(
- icon = R.drawable.passkey_icon,
+ icon = screenIcon,
title = stringResource(R.string.use_password_title),
)
},
accountContent = {
PasswordRow(
- email = passwordUiModel.email,
+ email = entry.userName,
modifier = Modifier.padding(top = 10.dp),
)
},
@@ -117,6 +118,13 @@
modifier = modifier.padding(horizontal = 10.dp)
) {
item {
+ Column {
+ ContinueChip(viewModel::onContinueClick)
+ SignInOptionsChip(viewModel::onSignInOptionsClick)
+ DismissChip(viewModel::onDismissClick)
+ }
}
}
}
+
+
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
index c9c66b4..3f841b8 100644
--- 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
@@ -17,16 +17,15 @@
package com.android.credentialmanager.ui.screens.single.password
import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
import android.credentials.selection.UserSelectionDialogResult
-import androidx.activity.result.IntentSenderRequest
+import android.credentials.selection.ProviderPendingIntentResponse
import androidx.annotation.MainThread
import androidx.lifecycle.ViewModel
import com.android.credentialmanager.ktx.getIntentSenderRequest
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.client.CredentialManagerClient
import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.model.PasswordUiModel
+import com.android.credentialmanager.ui.screens.single.UiState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -37,36 +36,31 @@
private val credentialManagerClient: CredentialManagerClient,
) : ViewModel() {
- private var initializeCalled = false
-
private lateinit var requestGet: Request.Get
private lateinit var entryInfo: CredentialEntryInfo
private val _uiState =
- MutableStateFlow<SinglePasswordScreenUiState>(SinglePasswordScreenUiState.Idle)
- val uiState: StateFlow<SinglePasswordScreenUiState> = _uiState
+ MutableStateFlow<UiState>(UiState.CredentialScreen)
+ val uiState: StateFlow<UiState> = _uiState
@MainThread
fun initialize(entryInfo: CredentialEntryInfo) {
- if (initializeCalled) return
- initializeCalled = true
- _uiState.value = SinglePasswordScreenUiState.Loaded(
- PasswordUiModel(
- email = entryInfo.userName,
- )
- )
+ this.entryInfo = entryInfo
}
- fun onCancelClick() {
- _uiState.value = SinglePasswordScreenUiState.Cancel
+ fun onDismissClick() {
+ _uiState.value = UiState.Cancel
}
- fun onOKClick() {
- _uiState.value = SinglePasswordScreenUiState.PasswordSelected(
+ fun onContinueClick() {
+ _uiState.value = UiState.CredentialSelected(
intentSenderRequest = entryInfo.getIntentSenderRequest()
)
}
+ fun onSignInOptionsClick() {
+ }
+
fun onPasswordInfoRetrieved(
resultCode: Int? = null,
resultData: Intent? = null,
@@ -79,18 +73,5 @@
if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
)
credentialManagerClient.sendResult(userSelectionDialogResult)
- _uiState.value = SinglePasswordScreenUiState.Completed
}
}
-
-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/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index fb14a17..58e0a89 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -41,6 +41,7 @@
import android.os.UserManager;
import android.print.PrintManager;
import android.provider.Settings;
+import android.provider.Settings.Secure;
import android.telephony.AccessNetworkConstants;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
@@ -66,18 +67,29 @@
import com.android.settingslib.utils.BuildCompatUtils;
import java.text.NumberFormat;
+import java.time.Duration;
import java.util.List;
public class Utils {
private static final String TAG = "Utils";
- @VisibleForTesting
- static final String STORAGE_MANAGER_ENABLED_PROPERTY =
- "ro.storage_manager.enabled";
-
public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED =
"incompatible_charger_warning_disabled";
+ public static final String WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP =
+ "wireless_charging_notification_timestamp";
+
+ @VisibleForTesting
+ static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled";
+
+ @VisibleForTesting static final long WIRELESS_CHARGING_DEFAULT_TIMESTAMP = -1L;
+
+ @VisibleForTesting
+ static final long WIRELESS_CHARGING_NOTIFICATION_THRESHOLD_MILLIS =
+ Duration.ofDays(30).toMillis();
+
+ @VisibleForTesting
+ static final String WIRELESS_CHARGING_WARNING_ENABLED = "wireless_charging_warning_enabled";
private static Signature[] sSystemSignature;
private static String sPermissionControllerPackageName;
@@ -101,19 +113,19 @@
R.drawable.ic_show_x_wifi_signal_4
};
- public static void updateLocationEnabled(Context context, boolean enabled, int userId,
- int source) {
+ /** Update the location enable state. */
+ public static void updateLocationEnabled(
+ @NonNull Context context, boolean enabled, int userId, int source) {
Settings.Secure.putIntForUser(
- context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source,
- userId);
+ context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source, userId);
LocationManager locationManager = context.getSystemService(LocationManager.class);
locationManager.setLocationEnabledForUser(enabled, UserHandle.of(userId));
}
/**
- * Return string resource that best describes combination of tethering
- * options available on this device.
+ * Return string resource that best describes combination of tethering options available on this
+ * device.
*/
public static int getTetheringLabel(TetheringManager tm) {
String[] usbRegexs = tm.getTetherableUsbRegexs();
@@ -141,14 +153,12 @@
}
}
- /**
- * Returns a label for the user, in the form of "User: user name" or "Work profile".
- */
+ /** Returns a label for the user, in the form of "User: user name" or "Work profile". */
public static String getUserLabel(Context context, UserInfo info) {
String name = info != null ? info.name : null;
if (info.isManagedProfile()) {
// We use predefined values for managed profiles
- return BuildCompatUtils.isAtLeastT()
+ return BuildCompatUtils.isAtLeastT()
? getUpdatableManagedUserTitle(context)
: context.getString(R.string.managed_user_title);
} else if (info.isGuest()) {
@@ -164,14 +174,14 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static String getUpdatableManagedUserTitle(Context context) {
- return context.getSystemService(DevicePolicyManager.class).getResources().getString(
- WORK_PROFILE_USER_LABEL,
- () -> context.getString(R.string.managed_user_title));
+ return context.getSystemService(DevicePolicyManager.class)
+ .getResources()
+ .getString(
+ WORK_PROFILE_USER_LABEL,
+ () -> context.getString(R.string.managed_user_title));
}
- /**
- * Returns a circular icon for a user.
- */
+ /** Returns a circular icon for a user. */
public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
final int iconSize = UserIconDrawable.getDefaultSize(context);
if (user.isManagedProfile()) {
@@ -185,12 +195,14 @@
return new UserIconDrawable(iconSize).setIcon(icon).bake();
}
}
- return new UserIconDrawable(iconSize).setIconDrawable(
- UserIcons.getDefaultUserIcon(context.getResources(), user.id, /* light= */ false))
+ return new UserIconDrawable(iconSize)
+ .setIconDrawable(
+ UserIcons.getDefaultUserIcon(
+ context.getResources(), user.id, /* light= */ false))
.bake();
}
- /** Formats a double from 0.0..100.0 with an option to round **/
+ /** Formats a double from 0.0..100.0 with an option to round */
public static String formatPercentage(double percentage, boolean round) {
final int localPercentage = round ? Math.round((float) percentage) : (int) percentage;
return formatPercentage(localPercentage);
@@ -222,23 +234,27 @@
*
* @param context the context
* @param batteryChangedIntent battery broadcast intent received from {@link
- * Intent.ACTION_BATTERY_CHANGED}.
+ * Intent.ACTION_BATTERY_CHANGED}.
* @param compactStatus to present compact battery charging string if {@code true}
* @return battery status string
*/
- public static String getBatteryStatus(Context context, Intent batteryChangedIntent,
- boolean compactStatus) {
- final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_UNKNOWN);
+ @NonNull
+ public static String getBatteryStatus(
+ @NonNull Context context, @NonNull Intent batteryChangedIntent, boolean compactStatus) {
+ final int status =
+ batteryChangedIntent.getIntExtra(
+ BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
final Resources res = context.getResources();
String statusString = res.getString(R.string.battery_info_status_unknown);
final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent);
if (batteryStatus.isCharged()) {
- statusString = res.getString(compactStatus
- ? R.string.battery_info_status_full_charged
- : R.string.battery_info_status_full);
+ statusString =
+ res.getString(
+ compactStatus
+ ? R.string.battery_info_status_full_charged
+ : R.string.battery_info_status_full);
} else {
if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
if (compactStatus) {
@@ -246,12 +262,12 @@
} else if (batteryStatus.isPluggedInWired()) {
switch (batteryStatus.getChargingSpeed(context)) {
case BatteryStatus.CHARGING_FAST:
- statusString = res.getString(
- R.string.battery_info_status_charging_fast);
+ statusString =
+ res.getString(R.string.battery_info_status_charging_fast);
break;
case BatteryStatus.CHARGING_SLOWLY:
- statusString = res.getString(
- R.string.battery_info_status_charging_slow);
+ statusString =
+ res.getString(R.string.battery_info_status_charging_slow);
break;
default:
statusString = res.getString(R.string.battery_info_status_charging);
@@ -311,7 +327,7 @@
@ColorInt
public static int applyAlphaAttr(Context context, int attr, int inputColor) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
float alpha = ta.getFloat(0, 0);
ta.recycle();
return applyAlpha(alpha, inputColor);
@@ -320,7 +336,10 @@
@ColorInt
public static int applyAlpha(float alpha, int inputColor) {
alpha *= Color.alpha(inputColor);
- return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+ return Color.argb(
+ (int) (alpha),
+ Color.red(inputColor),
+ Color.green(inputColor),
Color.blue(inputColor));
}
@@ -329,19 +348,17 @@
return getColorAttrDefaultColor(context, attr, 0);
}
- /**
- * Get color styled attribute {@code attr}, default to {@code defValue} if not found.
- */
+ /** Get color styled attribute {@code attr}, default to {@code defValue} if not found. */
@ColorInt
public static int getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
@ColorInt int colorAccent = ta.getColor(0, defValue);
ta.recycle();
return colorAccent;
}
public static ColorStateList getColorAttr(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
ColorStateList stateList = null;
try {
stateList = ta.getColorStateList(0);
@@ -356,35 +373,38 @@
}
public static int getThemeAttr(Context context, int attr, int defaultValue) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
int theme = ta.getResourceId(0, defaultValue);
ta.recycle();
return theme;
}
public static Drawable getDrawable(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
Drawable drawable = ta.getDrawable(0);
ta.recycle();
return drawable;
}
/**
- * Create a color matrix suitable for a ColorMatrixColorFilter that modifies only the color but
- * preserves the alpha for a given drawable
- * @param color
- * @return a color matrix that uses the source alpha and given color
- */
+ * Create a color matrix suitable for a ColorMatrixColorFilter that modifies only the color but
+ * preserves the alpha for a given drawable
+ *
+ * @return a color matrix that uses the source alpha and given color
+ */
public static ColorMatrix getAlphaInvariantColorMatrixForColor(@ColorInt int color) {
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
- ColorMatrix cm = new ColorMatrix(new float[] {
- 0, 0, 0, 0, r,
- 0, 0, 0, 0, g,
- 0, 0, 0, 0, b,
- 0, 0, 0, 1, 0 });
+ ColorMatrix cm =
+ new ColorMatrix(
+ new float[] {
+ 0, 0, 0, 0, r,
+ 0, 0, 0, 0, g,
+ 0, 0, 0, 0, b,
+ 0, 0, 0, 1, 0
+ });
return cm;
}
@@ -393,7 +413,7 @@
* Create a ColorMatrixColorFilter to tint a drawable but retain its alpha characteristics
*
* @return a ColorMatrixColorFilter which changes the color of the output but is invariant on
- * the source alpha
+ * the source alpha
*/
public static ColorFilter getAlphaInvariantColorFilterForColor(@ColorInt int color) {
return new ColorMatrixColorFilter(getAlphaInvariantColorMatrixForColor(color));
@@ -402,16 +422,17 @@
/**
* Determine whether a package is a "system package", in which case certain things (like
* disabling notifications or disabling the package altogether) should be disallowed.
- * <p>
- * Note: This function is just for UI treatment, and should not be used for security purposes.
*
- * @deprecated Use {@link ApplicationInfo#isSignedWithPlatformKey()} and
- * {@link #isEssentialPackage} instead.
+ * <p>Note: This function is just for UI treatment, and should not be used for security
+ * purposes.
+ *
+ * @deprecated Use {@link ApplicationInfo#isSignedWithPlatformKey()} and {@link
+ * #isEssentialPackage} instead.
*/
@Deprecated
public static boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo pkg) {
if (sSystemSignature == null) {
- sSystemSignature = new Signature[]{getSystemSignature(pm)};
+ sSystemSignature = new Signature[] {getSystemSignature(pm)};
}
return (sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg)))
|| isEssentialPackage(resources, pm, pkg.packageName);
@@ -435,8 +456,8 @@
/**
* Determine whether a package is a "essential package".
- * <p>
- * In which case certain things (like disabling the package) should be disallowed.
+ *
+ * <p>In which case certain things (like disabling the package) should be disallowed.
*/
public static boolean isEssentialPackage(
Resources resources, PackageManager pm, String packageName) {
@@ -462,14 +483,12 @@
* returns {@code false}.
*/
public static boolean isDeviceProvisioningPackage(Resources resources, String packageName) {
- String deviceProvisioningPackage = resources.getString(
- com.android.internal.R.string.config_deviceProvisioningPackage);
+ String deviceProvisioningPackage =
+ resources.getString(com.android.internal.R.string.config_deviceProvisioningPackage);
return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
}
- /**
- * Fetch the package name of the default WebView provider.
- */
+ /** Fetch the package name of the default WebView provider. */
@Nullable
private static String getDefaultWebViewPackageName() {
if (sDefaultWebViewPackageName != null) {
@@ -503,8 +522,8 @@
/**
* Returns the Wifi icon resource for a given RSSI level.
*
- * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x
- * signal icon to users.
+ * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x signal
+ * icon to users.
* @param level The number of bars to show (0-4)
* @throws IllegalArgumentException if an invalid RSSI level is given.
*/
@@ -520,10 +539,7 @@
try {
defaultDays =
resources.getInteger(
- com.android
- .internal
- .R
- .integer
+ com.android.internal.R.integer
.config_storageManagerDaystoRetainDefault);
} catch (Resources.NotFoundException e) {
// We are likely in a test environment.
@@ -535,7 +551,7 @@
return !context.getSystemService(TelephonyManager.class).isDataCapable();
}
- /** Returns if the automatic storage management feature is turned on or not. **/
+ /** Returns if the automatic storage management feature is turned on or not. */
public static boolean isStorageManagerEnabled(Context context) {
boolean isDefaultOn;
try {
@@ -543,15 +559,14 @@
} catch (Resources.NotFoundException e) {
isDefaultOn = false;
}
- return Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
- isDefaultOn ? 1 : 0)
+ return Settings.Secure.getInt(
+ context.getContentResolver(),
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
+ isDefaultOn ? 1 : 0)
!= 0;
}
- /**
- * get that {@link AudioManager#getMode()} is in ringing/call/communication(VoIP) status.
- */
+ /** get that {@link AudioManager#getMode()} is in ringing/call/communication(VoIP) status. */
public static boolean isAudioModeOngoingCall(Context context) {
final AudioManager audioManager = context.getSystemService(AudioManager.class);
final int audioMode = audioManager.getMode();
@@ -561,8 +576,8 @@
}
/**
- * Return the service state is in-service or not.
- * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI
+ * Return the service state is in-service or not. To make behavior consistent with SystemUI and
+ * Settings/AboutPhone/SIM status UI
*
* @param serviceState Service state. {@link ServiceState}
*/
@@ -581,13 +596,12 @@
}
/**
- * Return the combined service state.
- * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI.
+ * Return the combined service state. To make behavior consistent with SystemUI and
+ * Settings/AboutPhone/SIM status UI.
*
- * This method returns a single service state int if either the voice reg state is
- * {@link ServiceState#STATE_IN_SERVICE} or if data network is registered via a
- * WWAN transport type. We consider the combined service state of an IWLAN network
- * to be OOS.
+ * <p>This method returns a single service state int if either the voice reg state is {@link
+ * ServiceState#STATE_IN_SERVICE} or if data network is registered via a WWAN transport type. We
+ * consider the combined service state of an IWLAN network to be OOS.
*
* @param serviceState Service state. {@link ServiceState}
*/
@@ -618,9 +632,10 @@
// on either a WLAN or WWAN network. Since we want to exclude the WLAN network, we can
// query the WWAN network directly and check for its registration state
private static boolean isDataRegInWwanAndInService(ServiceState serviceState) {
- final NetworkRegistrationInfo networkRegWwan = serviceState.getNetworkRegistrationInfo(
- NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ final NetworkRegistrationInfo networkRegWwan =
+ serviceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
if (networkRegWwan == null) {
return false;
@@ -633,8 +648,8 @@
public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
int userType = UserIconInfo.TYPE_MAIN;
try {
- UserInfo ui = context.getSystemService(UserManager.class).getUserInfo(
- user.getIdentifier());
+ UserInfo ui =
+ context.getSystemService(UserManager.class).getUserInfo(user.getIdentifier());
if (ui != null) {
if (ui.isCloneProfile()) {
userType = UserIconInfo.TYPE_CLONED;
@@ -650,15 +665,16 @@
try (IconFactory iconFactory = IconFactory.obtain(context)) {
return iconFactory
.createBadgedIconBitmap(
- icon,
- new IconOptions().setUser(new UserIconInfo(user, userType)))
+ icon, new IconOptions().setUser(new UserIconInfo(user, userType)))
.newIcon(context);
}
}
/** Get the {@link Drawable} that represents the app icon */
public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
- return getBadgedIcon(context, appInfo.loadUnbadgedIcon(context.getPackageManager()),
+ return getBadgedIcon(
+ context,
+ appInfo.loadUnbadgedIcon(context.getPackageManager()),
UserHandle.getUserHandleForUid(appInfo.uid));
}
@@ -669,10 +685,11 @@
* @param source bitmap to apply round corner.
* @param cornerRadius corner radius value.
*/
- public static Bitmap convertCornerRadiusBitmap(@NonNull Context context,
- @NonNull Bitmap source, @NonNull float cornerRadius) {
- final Bitmap roundedBitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(),
- Bitmap.Config.ARGB_8888);
+ @NonNull
+ public static Bitmap convertCornerRadiusBitmap(
+ @NonNull Context context, @NonNull Bitmap source, @NonNull float cornerRadius) {
+ final Bitmap roundedBitmap =
+ Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
final RoundedBitmapDrawable drawable =
RoundedBitmapDrawableFactory.create(context.getResources(), source);
drawable.setAntiAlias(true);
@@ -687,9 +704,6 @@
* Returns the WifiInfo for the underlying WiFi network of the VCN network, returns null if the
* input NetworkCapabilities is not for a VCN network with underlying WiFi network.
*
- * TODO(b/238425913): Move this method to be inside systemui not settingslib once we've migrated
- * off of {@link WifiStatusTracker} and {@link NetworkControllerImpl}.
- *
* @param networkCapabilities NetworkCapabilities of the network.
*/
@Nullable
@@ -708,8 +722,9 @@
// Avoid the caller doesn't have permission to read the "Settings.Secure" data.
try {
// Whether the incompatible charger warning is disabled or not
- if (Settings.Secure.getInt(context.getContentResolver(),
- INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0) == 1) {
+ if (Settings.Secure.getInt(
+ context.getContentResolver(), INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0)
+ == 1) {
Log.d(tag, "containsIncompatibleChargers: disabled");
return false;
}
@@ -718,8 +733,7 @@
return false;
}
- final List<UsbPort> usbPortList =
- context.getSystemService(UsbManager.class).getPorts();
+ final List<UsbPort> usbPortList = context.getSystemService(UsbManager.class).getPorts();
if (usbPortList == null || usbPortList.isEmpty()) {
return false;
}
@@ -760,4 +774,85 @@
return false;
}
+ /** Whether to show the wireless charging notification. */
+ public static boolean shouldShowWirelessChargingNotification(
+ @NonNull Context context, @NonNull String tag) {
+ try {
+ return shouldShowWirelessChargingNotificationInternal(context, tag);
+ } catch (Exception e) {
+ Log.e(tag, "shouldShowWirelessChargingNotification()", e);
+ return false;
+ }
+ }
+
+ /** Stores the timestamp of the wireless charging notification. */
+ public static void updateWirelessChargingNotificationTimestamp(
+ @NonNull Context context, long timestamp, @NonNull String tag) {
+ try {
+ Secure.putLong(
+ context.getContentResolver(),
+ WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+ timestamp);
+ } catch (Exception e) {
+ Log.e(tag, "setWirelessChargingNotificationTimestamp()", e);
+ }
+ }
+
+ /** Whether to show the wireless charging warning in Settings. */
+ public static boolean shouldShowWirelessChargingWarningTip(
+ @NonNull Context context, @NonNull String tag) {
+ try {
+ return Secure.getInt(context.getContentResolver(), WIRELESS_CHARGING_WARNING_ENABLED, 0)
+ == 1;
+ } catch (Exception e) {
+ Log.e(tag, "shouldShowWirelessChargingWarningTip()", e);
+ }
+ return false;
+ }
+
+ /** Stores the state of whether the wireless charging warning in Settings is enabled. */
+ public static void updateWirelessChargingWarningEnabled(
+ @NonNull Context context, boolean enabled, @NonNull String tag) {
+ try {
+ Secure.putInt(
+ context.getContentResolver(),
+ WIRELESS_CHARGING_WARNING_ENABLED,
+ enabled ? 1 : 0);
+ } catch (Exception e) {
+ Log.e(tag, "setWirelessChargingWarningEnabled()", e);
+ }
+ }
+
+ private static boolean shouldShowWirelessChargingNotificationInternal(
+ @NonNull Context context, @NonNull String tag) {
+ final long lastNotificationTimeMillis =
+ Secure.getLong(
+ context.getContentResolver(),
+ WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+ WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+ if (isWirelessChargingNotificationDisabled(lastNotificationTimeMillis)) {
+ return false;
+ }
+ if (isInitialWirelessChargingNotification(lastNotificationTimeMillis)) {
+ updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
+ updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
+ return true;
+ }
+ final long durationMillis = System.currentTimeMillis() - lastNotificationTimeMillis;
+ final boolean show = durationMillis > WIRELESS_CHARGING_NOTIFICATION_THRESHOLD_MILLIS;
+ Log.d(tag, "shouldShowWirelessChargingNotification = " + show);
+ if (show) {
+ updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
+ updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
+ }
+ return show;
+ }
+
+ private static boolean isWirelessChargingNotificationDisabled(long lastNotificationTimeMillis) {
+ return lastNotificationTimeMillis == Long.MIN_VALUE;
+ }
+
+ private static boolean isInitialWirelessChargingNotification(long lastNotificationTimeMillis) {
+ return lastNotificationTimeMillis == WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt
new file mode 100644
index 0000000..5bcb82d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 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.settingslib.view.accessibility.data.repository
+
+import android.view.accessibility.CaptioningManager
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+interface CaptioningRepository {
+
+ /** The system audio caption enabled state. */
+ val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
+
+ /** The system audio caption UI enabled state. */
+ val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
+
+ /** Sets [isSystemAudioCaptioningEnabled]. */
+ suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean)
+}
+
+class CaptioningRepositoryImpl(
+ private val captioningManager: CaptioningManager,
+ private val backgroundCoroutineContext: CoroutineContext,
+ coroutineScope: CoroutineScope,
+) : CaptioningRepository {
+
+ private val captioningChanges: SharedFlow<CaptioningChange> =
+ callbackFlow {
+ val listener = CaptioningChangeProducingListener(this)
+ captioningManager.addCaptioningChangeListener(listener)
+ awaitClose { captioningManager.removeCaptioningChangeListener(listener) }
+ }
+ .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
+
+ override val isSystemAudioCaptioningEnabled: StateFlow<Boolean> =
+ captioningChanges
+ .filterIsInstance(CaptioningChange.IsSystemAudioCaptioningEnabled::class)
+ .map { it.isEnabled }
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ captioningManager.isSystemAudioCaptioningEnabled
+ )
+
+ override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean> =
+ captioningChanges
+ .filterIsInstance(CaptioningChange.IsSystemUICaptioningEnabled::class)
+ .map { it.isEnabled }
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ captioningManager.isSystemAudioCaptioningUiEnabled,
+ )
+
+ override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
+ withContext(backgroundCoroutineContext) {
+ captioningManager.isSystemAudioCaptioningEnabled = isEnabled
+ }
+ }
+
+ private sealed interface CaptioningChange {
+
+ data class IsSystemAudioCaptioningEnabled(val isEnabled: Boolean) : CaptioningChange
+
+ data class IsSystemUICaptioningEnabled(val isEnabled: Boolean) : CaptioningChange
+ }
+
+ private class CaptioningChangeProducingListener(
+ private val scope: ProducerScope<CaptioningChange>
+ ) : CaptioningManager.CaptioningChangeListener() {
+
+ override fun onSystemAudioCaptioningChanged(enabled: Boolean) {
+ emitChange(CaptioningChange.IsSystemAudioCaptioningEnabled(enabled))
+ }
+
+ override fun onSystemAudioCaptioningUiChanged(enabled: Boolean) {
+ emitChange(CaptioningChange.IsSystemUICaptioningEnabled(enabled))
+ }
+
+ private fun emitChange(change: CaptioningChange) {
+ scope.launch { scope.send(change) }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/view/accessibility/domain/interactor/CaptioningInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/domain/interactor/CaptioningInteractor.kt
new file mode 100644
index 0000000..858c8b3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/domain/interactor/CaptioningInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.settingslib.view.accessibility.domain.interactor
+
+import com.android.settingslib.view.accessibility.data.repository.CaptioningRepository
+import kotlinx.coroutines.flow.StateFlow
+
+class CaptioningInteractor(private val repository: CaptioningRepository) {
+
+ val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
+ get() = repository.isSystemAudioCaptioningEnabled
+
+ val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
+ get() = repository.isSystemAudioCaptioningUiEnabled
+
+ suspend fun setIsSystemAudioCaptioningEnabled(enabled: Boolean) =
+ repository.setIsSystemAudioCaptioningEnabled(enabled)
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepositoryTest.kt
new file mode 100644
index 0000000..a5233e7
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepositoryTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 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.settingslib.view.accessibility.data.repository
+
+import android.view.accessibility.CaptioningManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+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.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@Suppress("UnspecifiedRegisterReceiverFlag")
+@RunWith(AndroidJUnit4::class)
+class CaptioningRepositoryTest {
+
+ @Captor
+ private lateinit var listenerCaptor: ArgumentCaptor<CaptioningManager.CaptioningChangeListener>
+
+ @Mock private lateinit var captioningManager: CaptioningManager
+
+ private lateinit var underTest: CaptioningRepository
+
+ private val testScope = TestScope()
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ CaptioningRepositoryImpl(
+ captioningManager,
+ testScope.testScheduler,
+ testScope.backgroundScope
+ )
+ }
+
+ @Test
+ fun isSystemAudioCaptioningEnabled_change_repositoryEmits() {
+ testScope.runTest {
+ `when`(captioningManager.isEnabled).thenReturn(false)
+ val isSystemAudioCaptioningEnabled = mutableListOf<Boolean>()
+ underTest.isSystemAudioCaptioningEnabled
+ .onEach { isSystemAudioCaptioningEnabled.add(it) }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ triggerOnSystemAudioCaptioningChange()
+ runCurrent()
+
+ assertThat(isSystemAudioCaptioningEnabled)
+ .containsExactlyElementsIn(listOf(false, true))
+ .inOrder()
+ }
+ }
+
+ @Test
+ fun isSystemAudioCaptioningUiEnabled_change_repositoryEmits() {
+ testScope.runTest {
+ `when`(captioningManager.isSystemAudioCaptioningUiEnabled).thenReturn(false)
+ val isSystemAudioCaptioningUiEnabled = mutableListOf<Boolean>()
+ underTest.isSystemAudioCaptioningUiEnabled
+ .onEach { isSystemAudioCaptioningUiEnabled.add(it) }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ triggerSystemAudioCaptioningUiChange()
+ runCurrent()
+
+ assertThat(isSystemAudioCaptioningUiEnabled)
+ .containsExactlyElementsIn(listOf(false, true))
+ .inOrder()
+ }
+ }
+
+ private fun triggerSystemAudioCaptioningUiChange(enabled: Boolean = true) {
+ verify(captioningManager).addCaptioningChangeListener(listenerCaptor.capture())
+ listenerCaptor.value.onSystemAudioCaptioningUiChanged(enabled)
+ }
+
+ private fun triggerOnSystemAudioCaptioningChange(enabled: Boolean = true) {
+ verify(captioningManager).addCaptioningChangeListener(listenerCaptor.capture())
+ listenerCaptor.value.onSystemAudioCaptioningChanged(enabled)
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.kt
new file mode 100644
index 0000000..fd253c6
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.settingslib.view.accessibility.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeCaptioningRepository : CaptioningRepository {
+
+ private val mutableIsSystemAudioCaptioningEnabled = MutableStateFlow(false)
+ override val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
+ get() = mutableIsSystemAudioCaptioningEnabled.asStateFlow()
+
+ private val mutableIsSystemAudioCaptioningUiEnabled = MutableStateFlow(false)
+ override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
+ get() = mutableIsSystemAudioCaptioningUiEnabled.asStateFlow()
+
+ override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
+ mutableIsSystemAudioCaptioningEnabled.value = isEnabled
+ }
+
+ fun setIsSystemAudioCaptioningUiEnabled(isSystemAudioCaptioningUiEnabled: Boolean) {
+ mutableIsSystemAudioCaptioningUiEnabled.value = isSystemAudioCaptioningUiEnabled
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index a88a9c7..2d07e5d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -16,6 +16,9 @@
package com.android.settingslib;
import static com.android.settingslib.Utils.STORAGE_MANAGER_ENABLED_PROPERTY;
+import static com.android.settingslib.Utils.WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
+import static com.android.settingslib.Utils.shouldShowWirelessChargingWarningTip;
+import static com.android.settingslib.Utils.updateWirelessChargingNotificationTimestamp;
import static com.google.common.truth.Truth.assertThat;
@@ -28,10 +31,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.hardware.usb.flags.Flags;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
+import android.hardware.usb.flags.Flags;
import android.location.LocationManager;
import android.media.AudioManager;
import android.os.BatteryManager;
@@ -59,6 +62,7 @@
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowSettings;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@@ -72,21 +76,16 @@
private static final String PERCENTAGE_49 = "49%";
private static final String PERCENTAGE_50 = "50%";
private static final String PERCENTAGE_100 = "100%";
+ private static final long CURRENT_TIMESTAMP = System.currentTimeMillis();
private AudioManager mAudioManager;
private Context mContext;
- @Mock
- private LocationManager mLocationManager;
- @Mock
- private ServiceState mServiceState;
- @Mock
- private NetworkRegistrationInfo mNetworkRegistrationInfo;
- @Mock
- private UsbPort mUsbPort;
- @Mock
- private UsbManager mUsbManager;
- @Mock
- private UsbPortStatus mUsbPortStatus;
+ @Mock private LocationManager mLocationManager;
+ @Mock private ServiceState mServiceState;
+ @Mock private NetworkRegistrationInfo mNetworkRegistrationInfo;
+ @Mock private UsbPort mUsbPort;
+ @Mock private UsbManager mUsbManager;
+ @Mock private UsbPortStatus mUsbPortStatus;
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -102,27 +101,37 @@
@After
public void reset() {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0);
+ Settings.Secure.putInt(
+ mContext.getContentResolver(), Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0);
}
@Test
public void testUpdateLocationEnabled() {
int currentUserId = ActivityManager.getCurrentUser();
- Utils.updateLocationEnabled(mContext, true, currentUserId,
- Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+ Utils.updateLocationEnabled(
+ mContext, true, currentUserId, Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
- assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.LOCATION_CHANGER,
- Settings.Secure.LOCATION_CHANGER_UNKNOWN)).isEqualTo(
- Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+ assertThat(
+ Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_CHANGER,
+ Settings.Secure.LOCATION_CHANGER_UNKNOWN))
+ .isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
}
@Test
public void testFormatPercentage_RoundTrue_RoundUpIfPossible() {
- final String[] expectedPercentages =
- {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1, PERCENTAGE_1, PERCENTAGE_49,
- PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50, PERCENTAGE_100};
+ final String[] expectedPercentages = {
+ PERCENTAGE_0,
+ PERCENTAGE_0,
+ PERCENTAGE_1,
+ PERCENTAGE_1,
+ PERCENTAGE_49,
+ PERCENTAGE_49,
+ PERCENTAGE_50,
+ PERCENTAGE_50,
+ PERCENTAGE_100
+ };
for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], true);
@@ -132,9 +141,17 @@
@Test
public void testFormatPercentage_RoundFalse_NoRound() {
- final String[] expectedPercentages =
- {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_49,
- PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_100};
+ final String[] expectedPercentages = {
+ PERCENTAGE_0,
+ PERCENTAGE_0,
+ PERCENTAGE_0,
+ PERCENTAGE_0,
+ PERCENTAGE_49,
+ PERCENTAGE_49,
+ PERCENTAGE_49,
+ PERCENTAGE_50,
+ PERCENTAGE_100
+ };
for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], false);
@@ -146,7 +163,9 @@
public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() {
Resources resources = mock(Resources.class);
when(resources.getInteger(
- eq(com.android.internal.R.integer.config_storageManagerDaystoRetainDefault)))
+ eq(
+ com.android.internal.R.integer
+ .config_storageManagerDaystoRetainDefault)))
.thenReturn(60);
assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
}
@@ -214,8 +233,10 @@
public void isInService_voiceOutOfServiceDataInService_returnTrue() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+ .thenReturn(mNetworkRegistrationInfo);
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
assertThat(Utils.isInService(mServiceState)).isTrue();
@@ -224,8 +245,10 @@
@Test
public void isInService_voiceOutOfServiceDataInServiceOnIwLan_returnFalse() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN))
+ .thenReturn(mNetworkRegistrationInfo);
when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
@@ -235,8 +258,10 @@
@Test
public void isInService_voiceOutOfServiceDataNull_returnFalse() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(null);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+ .thenReturn(null);
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@@ -244,8 +269,10 @@
@Test
public void isInService_voiceOutOfServiceDataOutOfService_returnFalse() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+ .thenReturn(mNetworkRegistrationInfo);
when(mNetworkRegistrationInfo.isInService()).thenReturn(false);
assertThat(Utils.isInService(mServiceState)).isFalse();
@@ -260,96 +287,106 @@
@Test
public void getCombinedServiceState_servicestateNull_returnOutOfService() {
- assertThat(Utils.getCombinedServiceState(null)).isEqualTo(
- ServiceState.STATE_OUT_OF_SERVICE);
+ assertThat(Utils.getCombinedServiceState(null))
+ .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
}
@Test
public void getCombinedServiceState_ServiceStatePowerOff_returnPowerOff() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
- assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
- ServiceState.STATE_POWER_OFF);
+ assertThat(Utils.getCombinedServiceState(mServiceState))
+ .isEqualTo(ServiceState.STATE_POWER_OFF);
}
@Test
public void getCombinedServiceState_voiceInService_returnInService() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
- assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
- ServiceState.STATE_IN_SERVICE);
+ assertThat(Utils.getCombinedServiceState(mServiceState))
+ .isEqualTo(ServiceState.STATE_IN_SERVICE);
}
@Test
public void getCombinedServiceState_voiceOutOfServiceDataInService_returnInService() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+ .thenReturn(mNetworkRegistrationInfo);
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
- assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
- ServiceState.STATE_IN_SERVICE);
+ assertThat(Utils.getCombinedServiceState(mServiceState))
+ .isEqualTo(ServiceState.STATE_IN_SERVICE);
}
@Test
public void getCombinedServiceState_voiceOutOfServiceDataInServiceOnIwLan_returnOutOfService() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN))
+ .thenReturn(mNetworkRegistrationInfo);
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
- assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
- ServiceState.STATE_OUT_OF_SERVICE);
+ assertThat(Utils.getCombinedServiceState(mServiceState))
+ .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
}
@Test
public void getCombinedServiceState_voiceOutOfServiceDataOutOfService_returnOutOfService() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getDataRegistrationState()).thenReturn(
- ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getDataRegistrationState())
+ .thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
- ServiceState.STATE_OUT_OF_SERVICE);
+ assertThat(Utils.getCombinedServiceState(mServiceState))
+ .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
}
@Test
public void getBatteryStatus_statusIsFull_returnFullString() {
- final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
- BatteryManager.EXTRA_SCALE, 100);
+ final Intent intent =
+ new Intent()
+ .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+ .putExtra(BatteryManager.EXTRA_SCALE, 100);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
- resources.getString(R.string.battery_info_status_full));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+ .isEqualTo(resources.getString(R.string.battery_info_status_full));
}
@Test
public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
- final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
- BatteryManager.EXTRA_SCALE, 100);
+ final Intent intent =
+ new Intent()
+ .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+ .putExtra(BatteryManager.EXTRA_SCALE, 100);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
- resources.getString(R.string.battery_info_status_full_charged));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+ .isEqualTo(resources.getString(R.string.battery_info_status_full_charged));
}
@Test
public void getBatteryStatus_batteryLevelIs100_returnFullString() {
- final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_FULL);
+ final Intent intent =
+ new Intent()
+ .putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
- resources.getString(R.string.battery_info_status_full));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+ .isEqualTo(resources.getString(R.string.battery_info_status_full));
}
@Test
public void getBatteryStatus_batteryLevelIs100AndUseCompactStatus_returnFullyString() {
- final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_FULL);
+ final Intent intent =
+ new Intent()
+ .putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
- resources.getString(R.string.battery_info_status_full_charged));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+ .isEqualTo(resources.getString(R.string.battery_info_status_full_charged));
}
@Test
@@ -359,8 +396,8 @@
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
- resources.getString(R.string.battery_info_status_charging));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+ .isEqualTo(resources.getString(R.string.battery_info_status_charging));
}
@Test
@@ -370,8 +407,8 @@
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_DOCK);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
- resources.getString(R.string.battery_info_status_charging_dock));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+ .isEqualTo(resources.getString(R.string.battery_info_status_charging_dock));
}
@Test
@@ -381,8 +418,8 @@
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
- resources.getString(R.string.battery_info_status_charging_wireless));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+ .isEqualTo(resources.getString(R.string.battery_info_status_charging_wireless));
}
@Test
@@ -392,8 +429,8 @@
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
- resources.getString(R.string.battery_info_status_charging));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+ .isEqualTo(resources.getString(R.string.battery_info_status_charging));
}
@Test
@@ -403,8 +440,8 @@
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
- resources.getString(R.string.battery_info_status_charging));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+ .isEqualTo(resources.getString(R.string.battery_info_status_charging));
}
@Test
@@ -503,12 +540,97 @@
@Test
public void containsIncompatibleChargers_disableWarning_returnFalse() {
setupIncompatibleCharging();
- Settings.Secure.putInt(mContext.getContentResolver(),
- Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 1);
+ Settings.Secure.putInt(
+ mContext.getContentResolver(), Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 1);
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
}
+ @Test
+ public void shouldShowWirelessChargingNotification_neverSendNotification_returnTrue() {
+ updateWirelessChargingNotificationTimestamp(
+ mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
+
+ assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void shouldShowNotification_neverSendNotification_updateTimestampAndEnabledState() {
+ updateWirelessChargingNotificationTimestamp(
+ mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
+
+ Utils.shouldShowWirelessChargingNotification(mContext, TAG);
+
+ assertThat(getWirelessChargingNotificationTimestamp())
+ .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+ assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void shouldShowWirelessChargingNotification_notificationDisabled_returnFalse() {
+ updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+ assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
+ }
+
+ @Test
+ public void shouldShowWirelessChargingNotification_withinTimeThreshold_returnFalse() {
+ updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+ assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
+ }
+
+ @Test
+ public void shouldShowWirelessChargingNotification_exceedTimeThreshold_returnTrue() {
+ final long monthAgo = Duration.ofDays(31).toMillis();
+ final long timestamp = CURRENT_TIMESTAMP - monthAgo;
+ updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
+
+ assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void shouldShowNotification_exceedTimeThreshold_updateTimestampAndEnabledState() {
+ final long monthAgo = Duration.ofDays(31).toMillis();
+ final long timestamp = CURRENT_TIMESTAMP - monthAgo;
+ updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
+
+ Utils.shouldShowWirelessChargingNotification(mContext, TAG);
+
+ assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(timestamp);
+ assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void updateWirelessChargingNotificationTimestamp_dismissForever_setMinValue() {
+ updateWirelessChargingNotificationTimestamp(mContext, Long.MIN_VALUE, TAG);
+
+ assertThat(getWirelessChargingNotificationTimestamp()).isEqualTo(Long.MIN_VALUE);
+ }
+
+ @Test
+ public void updateWirelessChargingNotificationTimestamp_notDismissForever_setTimestamp() {
+ updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+ assertThat(getWirelessChargingNotificationTimestamp())
+ .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+ assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(Long.MIN_VALUE);
+ }
+
+ @Test
+ public void shouldShowWirelessChargingWarningTip_enabled_returnTrue() {
+ Utils.updateWirelessChargingWarningEnabled(mContext, true, TAG);
+
+ assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void shouldShowWirelessChargingWarningTip_disabled_returnFalse() {
+ Utils.updateWirelessChargingWarningEnabled(mContext, false, TAG);
+
+ assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isFalse();
+ }
+
private void setupIncompatibleCharging() {
setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY);
}
@@ -520,6 +642,13 @@
when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
when(mUsbPortStatus.isConnected()).thenReturn(true);
- when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{complianceWarningType});
+ when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[] {complianceWarningType});
+ }
+
+ private long getWirelessChargingNotificationTimestamp() {
+ return Settings.Secure.getLong(
+ mContext.getContentResolver(),
+ Utils.WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+ WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1e146a5..e4a762a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1113,6 +1113,10 @@
dumpSetting(s, p,
Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
GlobalSettingsProto.Notification.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS);
+ dumpSetting(s, p,
+ Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
+ GlobalSettingsProto.Notification
+ .DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS);
p.end(notificationToken);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index ae71cec..1c9e748 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -614,8 +614,8 @@
final Iterator<Map.Entry<String, Setting>> iterator = mSettings.entrySet().iterator();
int index = prefix.lastIndexOf('/');
String namespace = index < 0 ? "" : prefix.substring(0, index);
- Map<String, String> trunkFlagMap =
- mNamespaceDefaults.get(namespace);
+ Map<String, String> trunkFlagMap = (mNamespaceDefaults == null)
+ ? null : mNamespaceDefaults.get(namespace);
// Delete old keys with the prefix that are not part of the new set.
// trunk flags will not be configured with restricted propagation
// trunk flags will be explicitly set, so not removing them here
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 16de478..b58187d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -185,6 +185,7 @@
Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH,
Settings.Global.DEVICE_DEMO_MODE,
Settings.Global.DEVICE_IDLE_CONSTANTS,
+ Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
Settings.Global.DISABLE_WINDOW_BLURS,
Settings.Global.BATTERY_SAVER_CONSTANTS,
Settings.Global.BATTERY_TIP_CONSTANTS,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0050676..f877d7a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -475,6 +475,9 @@
<service android:name=".screenrecord.RecordingService"
android:foregroundServiceType="systemExempted"/>
+ <service android:name=".recordissue.IssueRecordingService"
+ android:foregroundServiceType="systemExempted"/>
+
<receiver android:name=".SysuiRestartReceiver"
android:exported="false">
<intent-filter>
@@ -992,7 +995,6 @@
android:theme="@style/Theme.EditWidgetsActivity"
android:excludeFromRecents="true"
android:autoRemoveFromRecents="true"
- android:showOnLockScreen="true"
android:launchMode="singleTop"
android:exported="false">
</activity>
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt
new file mode 100644
index 0000000..846abf7e
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.ui.graphics.painter
+
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.asAndroidColorFilter
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.withSave
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.roundToInt
+
+/**
+ * *************************************************************************************************
+ * This file was forked from
+ * https://github.com/google/accompanist/blob/main/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt
+ */
+private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) { Handler(Looper.getMainLooper()) }
+
+/**
+ * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
+ * should be remembered to be able to start and stop [Animatable] animations.
+ *
+ * Instances are usually retrieved from [rememberDrawablePainter].
+ */
+public class DrawablePainter(public val drawable: Drawable) : Painter(), RememberObserver {
+ private var drawInvalidateTick by mutableStateOf(0)
+ private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
+
+ private val callback: Drawable.Callback by lazy {
+ object : Drawable.Callback {
+ override fun invalidateDrawable(d: Drawable) {
+ // Update the tick so that we get re-drawn
+ drawInvalidateTick++
+ // Update our intrinsic size too
+ drawableIntrinsicSize = drawable.intrinsicSize
+ }
+
+ override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
+ MAIN_HANDLER.postAtTime(what, time)
+ }
+
+ override fun unscheduleDrawable(d: Drawable, what: Runnable) {
+ MAIN_HANDLER.removeCallbacks(what)
+ }
+ }
+ }
+
+ init {
+ if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
+ // Update the drawable's bounds to match the intrinsic size
+ drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
+ }
+ }
+
+ override fun onRemembered() {
+ drawable.callback = callback
+ drawable.setVisible(true, true)
+ if (drawable is Animatable) drawable.start()
+ }
+
+ override fun onAbandoned(): Unit = onForgotten()
+
+ override fun onForgotten() {
+ if (drawable is Animatable) drawable.stop()
+ drawable.setVisible(false, false)
+ drawable.callback = null
+ }
+
+ override fun applyAlpha(alpha: Float): Boolean {
+ drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
+ return true
+ }
+
+ override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
+ drawable.colorFilter = colorFilter?.asAndroidColorFilter()
+ return true
+ }
+
+ override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
+ if (Build.VERSION.SDK_INT >= 23) {
+ return drawable.setLayoutDirection(
+ when (layoutDirection) {
+ LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+ LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+ }
+ )
+ }
+ return false
+ }
+
+ override val intrinsicSize: Size
+ get() = drawableIntrinsicSize
+
+ override fun DrawScope.onDraw() {
+ drawIntoCanvas { canvas ->
+ // Reading this ensures that we invalidate when invalidateDrawable() is called
+ drawInvalidateTick
+
+ // Update the Drawable's bounds
+ drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
+
+ canvas.withSave { drawable.draw(canvas.nativeCanvas) }
+ }
+ }
+}
+
+/**
+ * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the drawable
+ * contents and use Compose primitives where possible.
+ *
+ * If the provided [drawable] is `null`, an empty no-op painter is returned.
+ *
+ * This function tries to dispatch lifecycle events to [drawable] as much as possible from within
+ * Compose.
+ *
+ * @sample com.google.accompanist.sample.drawablepainter.BasicSample
+ */
+@Composable
+public fun rememberDrawablePainter(drawable: Drawable?): Painter =
+ remember(drawable) {
+ when (drawable) {
+ null -> EmptyPainter
+ is ColorDrawable -> ColorPainter(Color(drawable.color))
+ // Since the DrawablePainter will be remembered and it implements RememberObserver, it
+ // will receive the necessary events
+ else -> DrawablePainter(drawable.mutate())
+ }
+ }
+
+private val Drawable.intrinsicSize: Size
+ get() =
+ when {
+ // Only return a finite size if the drawable has an intrinsic size
+ intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
+ Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
+ }
+ else -> Size.Unspecified
+ }
+
+internal object EmptyPainter : Painter() {
+ override val intrinsicSize: Size
+ get() = Size.Unspecified
+ override fun DrawScope.onDraw() {}
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 3fb8254..d70f82f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -61,11 +61,11 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
-import com.android.compose.ui.util.lerp
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.notifications.ui.composable.Notifications.Form
import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index b26194f..37d763b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -30,7 +30,8 @@
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.lerp
-import com.android.compose.ui.util.lerp
+import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.lerp
/**
* A [State] whose [value] is animated.
@@ -282,7 +283,7 @@
} else {
val progress =
if (canOverflow) transition.progress
- else transition.progress.coerceIn(0f, 1f)
+ else transition.progress.fastCoerceIn(0f, 1f)
lerp(fromValue, toValue, progress)
}
} else fromValue ?: toValue
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index e40f6b6..828e34d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -39,6 +39,8 @@
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
+import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.ui.util.lerp
@@ -362,7 +364,7 @@
isSpecified = { true },
::lerp,
)
- .coerceIn(0f, 1f)
+ .fastCoerceIn(0f, 1f)
}
@OptIn(ExperimentalComposeUiApi::class)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 529fc03..3ff869b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -283,21 +283,28 @@
}
onDragStart(drag.position, overSlop, pressed.size)
- onDrag(drag, overSlop)
- val successful =
- when (orientation) {
- Orientation.Horizontal ->
- horizontalDrag(drag.id) {
- onDrag(it, it.positionChange().x)
- it.consume()
- }
- Orientation.Vertical ->
- verticalDrag(drag.id) {
- onDrag(it, it.positionChange().y)
- it.consume()
- }
- }
+ val successful: Boolean
+ try {
+ onDrag(drag, overSlop)
+
+ successful =
+ when (orientation) {
+ Orientation.Horizontal ->
+ horizontalDrag(drag.id) {
+ onDrag(it, it.positionChange().x)
+ it.consume()
+ }
+ Orientation.Vertical ->
+ verticalDrag(drag.id) {
+ onDrag(it, it.positionChange().y)
+ it.consume()
+ }
+ }
+ } catch (t: Throwable) {
+ onDragCancel()
+ throw t
+ }
if (successful) {
onDragEnd()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 23af5ac..b3d2bc9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -701,27 +701,19 @@
gestureHandler.shouldImmediatelyIntercept(startedPosition = null)
if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
- val swipeTransition = gestureHandler.swipeTransition
- val progress = swipeTransition.progress
val threshold = layoutImpl.transitionInterceptionThreshold
- fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
-
- // The transition is always between 0 and 1. If it is close to either of these
- // intervals, we want to go directly to the TransitionState.Idle.
- // The progress value can go beyond this range in the case of overscroll.
- val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
- if (shouldSnapToIdle) {
- swipeTransition.cancelOffsetAnimation()
- layoutState.finishTransition(swipeTransition, swipeTransition.currentScene)
+ val hasSnappedToIdle = layoutState.snapToIdleIfClose(threshold)
+ if (hasSnappedToIdle) {
+ // If the current swipe transition is closed to 0f or 1f, then we want to
+ // interrupt the transition (snapping it to Idle) and scroll the list.
+ return@PriorityNestedScrollConnection false
}
- // Start only if we cannot consume this event
- val canStart = !shouldSnapToIdle
- if (canStart) {
- isIntercepting = true
- }
-
- canStart
+ // If the current swipe transition is *not* closed to 0f or 1f, then we want the
+ // scroll events to intercept the current transition to continue the scene
+ // transition.
+ isIntercepting = true
+ true
},
canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
val behavior: NestedScrollBehavior =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index aee6f9e..a8da551 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -24,6 +24,11 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.ui.util.fastFilter
+import androidx.compose.ui.util.fastForEach
+import com.android.compose.animation.scene.transition.link.LinkedTransition
+import com.android.compose.animation.scene.transition.link.StateLink
+import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
@@ -100,8 +105,9 @@
fun MutableSceneTransitionLayoutState(
initialScene: SceneKey,
transitions: SceneTransitions = SceneTransitions.Empty,
+ stateLinks: List<StateLink> = emptyList(),
): MutableSceneTransitionLayoutState {
- return MutableSceneTransitionLayoutStateImpl(initialScene, transitions)
+ return MutableSceneTransitionLayoutStateImpl(initialScene, transitions, stateLinks)
}
/**
@@ -120,9 +126,12 @@
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
transitions: SceneTransitions = SceneTransitions.Empty,
+ stateLinks: List<StateLink> = emptyList(),
): SceneTransitionLayoutState {
- return remember { HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene) }
- .apply { update(currentScene, onChangeScene, transitions) }
+ return remember {
+ HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene, stateLinks)
+ }
+ .apply { update(currentScene, onChangeScene, transitions, stateLinks) }
}
@Stable
@@ -183,8 +192,10 @@
}
}
-internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) :
- SceneTransitionLayoutState {
+internal abstract class BaseSceneTransitionLayoutState(
+ initialScene: SceneKey,
+ protected var stateLinks: List<StateLink>,
+) : SceneTransitionLayoutState {
override var transitionState: TransitionState by
mutableStateOf(TransitionState.Idle(initialScene))
protected set
@@ -195,6 +206,8 @@
*/
internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
+ private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
+
/**
* Called when the [current scene][TransitionState.currentScene] should be changed to [scene].
*
@@ -223,19 +236,94 @@
transitions
.transitionSpec(transition.fromScene, transition.toScene, key = transitionKey)
.transformationSpec()
-
+ cancelActiveTransitionLinks()
+ setupTransitionLinks(transition)
transitionState = transition
}
+ private fun cancelActiveTransitionLinks() {
+ for ((link, linkedTransition) in activeTransitionLinks) {
+ link.target.finishTransition(linkedTransition, linkedTransition.currentScene)
+ }
+ activeTransitionLinks.clear()
+ }
+
+ private fun setupTransitionLinks(transitionState: TransitionState) {
+ if (transitionState !is TransitionState.Transition) return
+ stateLinks.fastForEach { stateLink ->
+ val matchingLinks =
+ stateLink.transitionLinks.fastFilter { it.isMatchingLink(transitionState) }
+ if (matchingLinks.isEmpty()) return@fastForEach
+ if (matchingLinks.size > 1) error("More than one link matched.")
+
+ val targetCurrentScene = stateLink.target.transitionState.currentScene
+ val matchingLink = matchingLinks[0]
+
+ if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
+
+ val linkedTransition =
+ LinkedTransition(
+ originalTransition = transitionState,
+ fromScene = targetCurrentScene,
+ toScene = matchingLink.targetTo,
+ )
+
+ stateLink.target.startTransition(linkedTransition, matchingLink.targetTransitionKey)
+ activeTransitionLinks[stateLink] = linkedTransition
+ }
+ }
+
/**
* Notify that [transition] was finished and that we should settle to [idleScene]. This will do
* nothing if [transition] was interrupted since it was started.
*/
internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) {
+ resolveActiveTransitionLinks(idleScene)
if (transitionState == transition) {
transitionState = TransitionState.Idle(idleScene)
}
}
+
+ private fun resolveActiveTransitionLinks(idleScene: SceneKey) {
+ val previousTransition = this.transitionState as? TransitionState.Transition ?: return
+ for ((link, linkedTransition) in activeTransitionLinks) {
+ if (previousTransition.fromScene == idleScene) {
+ // The transition ended by arriving at the fromScene, move link to Idle(fromScene).
+ link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
+ } else if (previousTransition.toScene == idleScene) {
+ // The transition ended by arriving at the toScene, move link to Idle(toScene).
+ link.target.finishTransition(linkedTransition, linkedTransition.toScene)
+ } else {
+ // The transition was interrupted by something else, we reset to initial state.
+ link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
+ }
+ }
+ activeTransitionLinks.clear()
+ }
+
+ /**
+ * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
+ * to the closest scene.
+ *
+ * @return true if snapped to the closest scene.
+ */
+ internal fun snapToIdleIfClose(threshold: Float): Boolean {
+ val transition = currentTransition ?: return false
+ val progress = transition.progress
+ fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
+
+ return when {
+ isProgressCloseTo(0f) -> {
+ finishTransition(transition, transition.fromScene)
+ true
+ }
+ isProgressCloseTo(1f) -> {
+ finishTransition(transition, transition.toScene)
+ true
+ }
+ else -> false
+ }
+ }
}
/**
@@ -246,7 +334,8 @@
initialScene: SceneKey,
override var transitions: SceneTransitions,
private var changeScene: (SceneKey) -> Unit,
-) : BaseSceneTransitionLayoutState(initialScene) {
+ stateLinks: List<StateLink> = emptyList(),
+) : BaseSceneTransitionLayoutState(initialScene, stateLinks) {
private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED)
override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene(scene)
@@ -256,10 +345,12 @@
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
transitions: SceneTransitions,
+ stateLinks: List<StateLink>,
) {
SideEffect {
this.changeScene = onChangeScene
this.transitions = transitions
+ this.stateLinks = stateLinks
targetSceneChannel.trySend(currentScene)
}
@@ -283,7 +374,8 @@
internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
override var transitions: SceneTransitions,
-) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene) {
+ stateLinks: List<StateLink> = emptyList(),
+) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene, stateLinks) {
override fun setTargetScene(
targetScene: SceneKey,
coroutineScope: CoroutineScope,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 04254fb..603f7ba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -16,6 +16,9 @@
package com.android.compose.animation.scene.transformation
+import androidx.compose.ui.util.fastCoerceAtLeast
+import androidx.compose.ui.util.fastCoerceAtMost
+import androidx.compose.ui.util.fastCoerceIn
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scene
@@ -106,10 +109,10 @@
fun progress(transitionProgress: Float): Float {
return when {
start.isSpecified() && end.isSpecified() ->
- ((transitionProgress - start) / (end - start)).coerceIn(0f, 1f)
+ ((transitionProgress - start) / (end - start)).fastCoerceIn(0f, 1f)
!start.isSpecified() && !end.isSpecified() -> transitionProgress
- end.isSpecified() -> (transitionProgress / end).coerceAtMost(1f)
- else -> ((transitionProgress - start) / (1f - start)).coerceAtLeast(0f)
+ end.isSpecified() -> (transitionProgress / end).fastCoerceAtMost(1f)
+ else -> ((transitionProgress - start) / (1f - start)).fastCoerceAtLeast(0f)
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
new file mode 100644
index 0000000..33b57b2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.compose.animation.scene.transition.link
+
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionState
+
+/** A linked transition which is driven by a [originalTransition]. */
+internal class LinkedTransition(
+ private val originalTransition: TransitionState.Transition,
+ fromScene: SceneKey,
+ toScene: SceneKey,
+) : TransitionState.Transition(fromScene, toScene) {
+
+ override val currentScene: SceneKey
+ get() {
+ return when (originalTransition.currentScene) {
+ originalTransition.fromScene -> fromScene
+ originalTransition.toScene -> toScene
+ else -> error("Original currentScene is neither FromScene nor ToScene")
+ }
+ }
+
+ override val isInitiatedByUserInput: Boolean
+ get() = originalTransition.isInitiatedByUserInput
+
+ override val isUserInputOngoing: Boolean
+ get() = originalTransition.isUserInputOngoing
+
+ override val progress: Float
+ get() = originalTransition.progress
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
new file mode 100644
index 0000000..6c29946
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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.compose.animation.scene.transition.link
+
+import com.android.compose.animation.scene.BaseSceneTransitionLayoutState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.TransitionState
+
+/** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */
+class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) {
+
+ internal val target = target as BaseSceneTransitionLayoutState
+
+ /**
+ * Links two transitions (source and target) together.
+ *
+ * `null` can be passed to indicate that any SceneKey should match. e.g. passing `null`, `null`,
+ * `null`, `SceneA` means that any transition at the source will trigger a transition in the
+ * target to `SceneA` from any current scene.
+ */
+ class TransitionLink(
+ val sourceFrom: SceneKey?,
+ val sourceTo: SceneKey?,
+ val targetFrom: SceneKey?,
+ val targetTo: SceneKey,
+ val targetTransitionKey: TransitionKey? = null,
+ ) {
+ init {
+ if (
+ (sourceFrom != null && sourceFrom == sourceTo) ||
+ (targetFrom != null && targetFrom == targetTo)
+ )
+ error("From and To can't be the same")
+ }
+
+ internal fun isMatchingLink(transition: TransitionState.Transition): Boolean {
+ return (sourceFrom == null || sourceFrom == transition.fromScene) &&
+ (sourceTo == null || sourceTo == transition.toScene)
+ }
+
+ internal fun targetIsInValidState(targetCurrentScene: SceneKey): Boolean {
+ return (targetFrom == null || targetFrom == targetCurrentScene) &&
+ targetTo != targetCurrentScene
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
index 13747b7..e78ab29 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
@@ -20,24 +20,8 @@
import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.Scale
-import kotlin.math.roundToInt
-import kotlin.math.roundToLong
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Float, stop: Float, fraction: Float): Float {
- return (1 - fraction) * start + fraction * stop
-}
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Int, stop: Int, fraction: Float): Int {
- return start + ((stop - start) * fraction.toDouble()).roundToInt()
-}
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Long, stop: Long, fraction: Float): Long {
- return start + ((stop - start) * fraction.toDouble()).roundToLong()
-}
/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
fun lerp(start: IntSize, stop: IntSize, fraction: Float): IntSize {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index a116501..e8854cf 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -30,8 +30,8 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.util.lerp
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.ui.util.lerp
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Rule
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
new file mode 100644
index 0000000..cd99d05
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 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.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MultiPointerDraggableTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun cancellingPointerCallsOnDragStopped() {
+ val size = 200f
+ val middle = Offset(size / 2f, size / 2f)
+
+ var enabled by mutableStateOf(false)
+ var started = false
+ var dragged = false
+ var stopped = false
+
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ Box(
+ Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .multiPointerDraggable(
+ orientation = Orientation.Vertical,
+ enabled = { enabled },
+ startDragImmediately = { false },
+ onDragStarted = { _, _, _ -> started = true },
+ onDragDelta = { _ -> dragged = true },
+ onDragStopped = { stopped = true },
+ )
+ )
+ }
+
+ fun startDraggingDown() {
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop))
+ }
+ }
+
+ fun releaseFinger() {
+ rule.onRoot().performTouchInput { up() }
+ }
+
+ // Swiping down does nothing because enabled is false.
+ startDraggingDown()
+ assertThat(started).isFalse()
+ assertThat(dragged).isFalse()
+ assertThat(stopped).isFalse()
+ releaseFinger()
+
+ // Enable the draggable and swipe down. This should both call onDragStarted() and
+ // onDragDelta().
+ enabled = true
+ rule.waitForIdle()
+ startDraggingDown()
+ assertThat(started).isTrue()
+ assertThat(dragged).isTrue()
+ assertThat(stopped).isFalse()
+
+ // Disable the pointer input. This should call onDragStopped() even if didn't release the
+ // finger yet.
+ enabled = false
+ rule.waitForIdle()
+ assertThat(started).isTrue()
+ assertThat(dragged).isTrue()
+ assertThat(stopped).isTrue()
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index c61917d..f81a7f2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -18,10 +18,14 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TestScenes.SceneD
+import com.android.compose.animation.scene.transition.link.StateLink
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.junit.Rule
import org.junit.Test
@@ -31,93 +35,241 @@
class SceneTransitionLayoutStateTest {
@get:Rule val rule = createComposeRule()
+ class TestableTransition(
+ fromScene: SceneKey,
+ toScene: SceneKey,
+ ) : TransitionState.Transition(fromScene, toScene) {
+ override var currentScene: SceneKey = fromScene
+ override var progress: Float = 0.0f
+ override var isInitiatedByUserInput: Boolean = false
+ override var isUserInputOngoing: Boolean = false
+ }
+
@Test
fun isTransitioningTo_idle() {
- val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
assertThat(state.isTransitioning()).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
- assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
- .isFalse()
+ assertThat(state.isTransitioning(from = SceneA)).isFalse()
+ assertThat(state.isTransitioning(to = SceneB)).isFalse()
+ assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isFalse()
}
@Test
fun isTransitioningTo_transition() {
- val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
- state.startTransition(
- transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
- transitionKey = null
- )
+ val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+ state.startTransition(transition(from = SceneA, to = SceneB), transitionKey = null)
assertThat(state.isTransitioning()).isTrue()
- assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
- assertThat(state.isTransitioning(from = TestScenes.SceneB)).isFalse()
- assertThat(state.isTransitioning(to = TestScenes.SceneB)).isTrue()
- assertThat(state.isTransitioning(to = TestScenes.SceneA)).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+ assertThat(state.isTransitioning(from = SceneA)).isTrue()
+ assertThat(state.isTransitioning(from = SceneB)).isFalse()
+ assertThat(state.isTransitioning(to = SceneB)).isTrue()
+ assertThat(state.isTransitioning(to = SceneA)).isFalse()
+ assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
}
@Test
fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
}
@Test
fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- val transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ val transition = state.setTargetScene(SceneB, coroutineScope = this)
assertThat(transition).isNotNull()
assertThat(state.transitionState).isEqualTo(transition)
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@Test
fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull()
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@Test
fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
- assertThat(state.setTargetScene(TestScenes.SceneC, coroutineScope = this)).isNotNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.setTargetScene(SceneC, coroutineScope = this)).isNotNull()
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneC))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
}
@Test
fun setTargetScene_transitionToOriginalScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
// Progress is 0f, so we don't animate at all and directly snap back to A.
- assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+ assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
}
@Test
fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+ val state = MutableSceneTransitionLayoutState(SceneA)
lateinit var transition: TransitionState.Transition
val job =
launch(start = CoroutineStart.UNDISPATCHED) {
- transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)!!
+ transition = state.setTargetScene(SceneB, coroutineScope = this)!!
}
assertThat(state.transitionState).isEqualTo(transition)
// Cancelling the scope/job still sets the state to Idle(targetScene).
job.cancel()
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ }
+
+ private fun setupLinkedStates(
+ parentInitialScene: SceneKey = SceneC,
+ childInitialScene: SceneKey = SceneA,
+ sourceFrom: SceneKey? = SceneA,
+ sourceTo: SceneKey? = SceneB,
+ targetFrom: SceneKey? = SceneC,
+ targetTo: SceneKey = SceneD
+ ): Pair<BaseSceneTransitionLayoutState, BaseSceneTransitionLayoutState> {
+ val parentState = MutableSceneTransitionLayoutState(parentInitialScene)
+ val link =
+ listOf(
+ StateLink(
+ parentState,
+ listOf(StateLink.TransitionLink(sourceFrom, sourceTo, targetFrom, targetTo))
+ )
+ )
+ val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link)
+ return Pair(
+ parentState as BaseSceneTransitionLayoutState,
+ childState as BaseSceneTransitionLayoutState
+ )
+ }
+
+ @Test
+ fun linkedTransition_startsLinkAndFinishesLinkInToState() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ }
+
+ @Test
+ fun linkedTransition_transitiveLink() {
+ val parentParentState =
+ MutableSceneTransitionLayoutState(SceneB) as BaseSceneTransitionLayoutState
+ val parentLink =
+ listOf(
+ StateLink(
+ parentParentState,
+ listOf(StateLink.TransitionLink(SceneC, SceneD, SceneB, SceneC))
+ )
+ )
+ val parentState =
+ MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink)
+ as BaseSceneTransitionLayoutState
+ val link =
+ listOf(
+ StateLink(
+ parentState,
+ listOf(StateLink.TransitionLink(SceneA, SceneB, SceneC, SceneD))
+ )
+ )
+ val childState =
+ MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
+ as BaseSceneTransitionLayoutState
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+ assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue()
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_linkProgressIsEqual() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
+
+ childTransition.progress = .5f
+ assertThat(parentState.currentTransition?.progress).isEqualTo(.5f)
+ }
+
+ @Test
+ fun linkedTransition_reverseTransitionIsNotLinked() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneB, SceneA)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+ childState.startTransition(childTransition, null)
+
+ childState.finishTransition(childTransition, SceneA)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+ childState.startTransition(childTransition, null)
+
+ childState.finishTransition(childTransition, SceneD)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_startsLinkButLinkedStateIsTakenOver() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+ val parentTransition = TestableTransition(SceneC, SceneA)
+ childState.startTransition(childTransition, null)
+ parentState.startTransition(parentTransition, null)
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(parentTransition)
}
@Test
@@ -125,11 +277,11 @@
val transitionkey = TransitionKey(debugName = "foo")
val state =
MutableSceneTransitionLayoutState(
- TestScenes.SceneA,
+ SceneA,
transitions =
transitions {
- from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
- from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+ from(SceneA, to = SceneB) { fade(TestElements.Foo) }
+ from(SceneA, to = SceneB, key = transitionkey) {
fade(TestElements.Foo)
fade(TestElements.Bar)
}
@@ -138,19 +290,19 @@
as MutableSceneTransitionLayoutStateImpl
// Default transition from A to B.
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
assertThat(state.transformationSpec.transformations).hasSize(1)
// Go back to A.
- state.setTargetScene(TestScenes.SceneA, coroutineScope = this)
+ state.setTargetScene(SceneA, coroutineScope = this)
testScheduler.advanceUntilIdle()
assertThat(state.currentTransition).isNull()
- assertThat(state.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(state.transitionState.currentScene).isEqualTo(SceneA)
// Specific transition from A to B.
assertThat(
state.setTargetScene(
- TestScenes.SceneB,
+ SceneB,
coroutineScope = this,
transitionKey = transitionkey,
)
@@ -158,4 +310,83 @@
.isNotNull()
assertThat(state.transformationSpec.transformations).hasSize(2)
}
+
+ @Test
+ fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
+ val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ state.startTransition(
+ transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
+ transitionKey = null
+ )
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Ignore the request if the progress is not close to 0 or 1, using the threshold.
+ assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Go to the initial scene if it is close to 0.
+ assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
+ assertThat(state.isTransitioning()).isFalse()
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+ }
+
+ @Test
+ fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
+ val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ state.startTransition(
+ transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
+ transitionKey = null
+ )
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Ignore the request if the progress is not close to 0 or 1, using the threshold.
+ assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Go to the final scene if it is close to 1.
+ assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
+ assertThat(state.isTransitioning()).isFalse()
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ }
+
+ @Test
+ fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
+ val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ }
+
+ @Test
+ fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() {
+ val (parentState, childState) =
+ setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+ childState.finishTransition(childTransition, SceneA)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_fuzzyLinksAreNotMatched() {
+ val (parentState, childState) =
+ setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse()
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 754d5dc..2a87452 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -27,7 +27,11 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
-/** Defines interface for classes that can provide access to data from [Settings.Secure]. */
+/**
+ * Defines interface for classes that can provide access to data from [Settings.Secure].
+ * This repository doesn't guarantee to provide value across different users. For that
+ * see: [UserAwareSecureSettingsRepository]
+ */
interface SecureSettingsRepository {
/** Returns a [Flow] tracking the value of a setting as an [Int]. */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 1519021..c6327ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -24,11 +24,13 @@
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -90,6 +92,8 @@
whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
.thenReturn(mock(ImageView::class.java))
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
+
+ val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
val fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
mSetFlagsRule.enableFlags(AconfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
@@ -111,6 +115,7 @@
postureController,
fakeFeatureFlags,
mSelectedUserInteractor,
+ keyguardKeyboardInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index c82688c..e8a43ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -36,6 +36,7 @@
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
@@ -51,8 +52,9 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.SessionTracker
@@ -202,6 +204,7 @@
whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
+ val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
featureFlags = FakeFeatureFlags()
featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
@@ -232,13 +235,12 @@
postureController,
featureFlags,
mSelectedUserInteractor,
+ keyguardKeyboardInteractor,
)
kosmos = testKosmos()
sceneInteractor = kosmos.sceneInteractor
- keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(kosmos.testScope.backgroundScope)
- .keyguardTransitionInteractor
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 0e257bc..55cfcc2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -16,44 +16,27 @@
package com.android.systemui.biometrics
-import android.os.Handler
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.util.time.SystemClock
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.Assert.assertFalse
@@ -64,9 +47,7 @@
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -77,70 +58,24 @@
@kotlinx.coroutines.ExperimentalCoroutinesApi
class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest :
UdfpsKeyguardViewLegacyControllerBaseTest() {
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
- private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
-
- @Mock private lateinit var bouncerLogger: TableLogBuffer
+ private val keyguardBouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
@Before
override fun setUp() {
allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
MockitoAnnotations.initMocks(this)
- keyguardBouncerRepository =
- KeyguardBouncerRepositoryImpl(
- FakeSystemClock(),
- testScope.backgroundScope,
- bouncerLogger,
- )
- transitionRepository = FakeKeyguardTransitionRepository()
super.setUp()
}
override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewControllerLegacy {
- mPrimaryBouncerInteractor =
- PrimaryBouncerInteractor(
- keyguardBouncerRepository,
- mock(BouncerView::class.java),
- mock(Handler::class.java),
- mKeyguardStateController,
- mock(KeyguardSecurityModel::class.java),
- mock(PrimaryBouncerCallbackInteractor::class.java),
- mock(FalsingCollector::class.java),
- mock(DismissCallbackRegistry::class.java),
- context,
- mKeyguardUpdateMonitor,
- FakeTrustRepository(),
- testScope.backgroundScope,
- mSelectedUserInteractor,
- mock(DeviceEntryFaceAuthInteractor::class.java),
- )
- mAlternateBouncerInteractor =
- AlternateBouncerInteractor(
- mock(StatusBarStateController::class.java),
- mock(KeyguardStateController::class.java),
- keyguardBouncerRepository,
- FakeFingerprintPropertyRepository(),
- mock(BiometricSettingsRepository::class.java),
- mock(SystemClock::class.java),
- mKeyguardUpdateMonitor,
- testScope.backgroundScope,
- )
- mKeyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- )
- .keyguardTransitionInteractor
- mUdfpsOverlayInteractor =
- UdfpsOverlayInteractor(
- context,
- mock(AuthController::class.java),
- mock(SelectedUserInteractor::class.java),
- testScope.backgroundScope,
- )
+ mPrimaryBouncerInteractor = kosmos.primaryBouncerInteractor
+ mAlternateBouncerInteractor = kosmos.alternateBouncerInteractor
+ mKeyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+ mUdfpsOverlayInteractor = kosmos.udfpsOverlayInteractor
return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6808f5d..b611e0a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -37,16 +37,11 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId.fakeInstanceId
import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -59,52 +54,46 @@
import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
-import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.display
+import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.BiometricType
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.captureMany
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -129,6 +118,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos().apply { this.commandQueue = this.fakeCommandQueue }
private lateinit var underTest: DeviceEntryFaceAuthRepositoryImpl
@Mock private lateinit var faceManager: FaceManager
@@ -136,7 +126,6 @@
@Mock private lateinit var sessionTracker: SessionTracker
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Captor
private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
@@ -151,11 +140,11 @@
@Captor
private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
- private lateinit var testDispatcher: TestDispatcher
+ private val testDispatcher = kosmos.testDispatcher
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
- private lateinit var testScope: TestScope
- private lateinit var fakeUserRepository: FakeUserRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val testScope = kosmos.testScope
+ private val fakeUserRepository = kosmos.fakeUserRepository
private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
@@ -163,88 +152,30 @@
private lateinit var lockedOut: FlowValue<Boolean?>
private lateinit var canFaceAuthRun: FlowValue<Boolean?>
private lateinit var authenticated: FlowValue<Boolean?>
- private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
- private lateinit var deviceEntryFingerprintAuthRepository:
- FakeDeviceEntryFingerprintAuthRepository
- private lateinit var trustRepository: FakeTrustRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var powerRepository: FakePowerRepository
- private lateinit var powerInteractor: PowerInteractor
- private lateinit var keyguardInteractor: KeyguardInteractor
- private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
- private lateinit var displayStateInteractor: DisplayStateInteractor
- private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var displayRepository: FakeDisplayRepository
- private lateinit var fakeCommandQueue: FakeCommandQueue
+ private val biometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
+ private val deviceEntryFingerprintAuthRepository =
+ kosmos.fakeDeviceEntryFingerprintAuthRepository
+ private val trustRepository = kosmos.fakeTrustRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val powerInteractor = kosmos.powerInteractor
+ private val keyguardInteractor = kosmos.keyguardInteractor
+ private val alternateBouncerInteractor = kosmos.alternateBouncerInteractor
+ private val displayStateInteractor = kosmos.displayStateInteractor
+ private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val displayRepository = kosmos.displayRepository
+ private val fakeCommandQueue = kosmos.fakeCommandQueue
+ private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
private lateinit var featureFlags: FakeFeatureFlags
- private lateinit var fakeFacePropertyRepository: FakeFacePropertyRepository
private var wasAuthCancelled = false
private var wasDetectCancelled = false
- private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
-
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- testDispatcher = StandardTestDispatcher()
- testScope = TestScope(testDispatcher)
- fakeUserRepository = FakeUserRepository()
fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
- biometricSettingsRepository = FakeBiometricSettingsRepository()
- deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
- trustRepository = FakeTrustRepository()
featureFlags = FakeFeatureFlags()
- powerRepository = FakePowerRepository()
- powerInteractor =
- PowerInteractorFactory.create(
- repository = powerRepository,
- )
- .powerInteractor
-
- val withDeps =
- KeyguardInteractorFactory.create(
- featureFlags = featureFlags,
- powerInteractor = powerInteractor,
- )
- keyguardInteractor = withDeps.keyguardInteractor
- keyguardRepository = withDeps.repository
- bouncerRepository = withDeps.bouncerRepository
-
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = keyguardTransitionRepository,
- keyguardInteractor = keyguardInteractor,
- )
- .keyguardTransitionInteractor
-
- fakeCommandQueue = withDeps.commandQueue
-
- alternateBouncerInteractor =
- AlternateBouncerInteractor(
- bouncerRepository = bouncerRepository,
- fingerprintPropertyRepository = FakeFingerprintPropertyRepository(),
- biometricSettingsRepository = biometricSettingsRepository,
- systemClock = mock(SystemClock::class.java),
- keyguardStateController = FakeKeyguardStateController(),
- statusBarStateController = mock(StatusBarStateController::class.java),
- keyguardUpdateMonitor = keyguardUpdateMonitor,
- scope = testScope.backgroundScope,
- )
-
- displayRepository = FakeDisplayRepository()
- displayStateInteractor =
- DisplayStateInteractorImpl(
- applicationScope = testScope.backgroundScope,
- context = context,
- mainExecutor = FakeExecutor(FakeSystemClock()),
- displayStateRepository = FakeDisplayStateRepository(),
- displayRepository = displayRepository,
- )
-
bypassStateChangedListener =
KotlinArgumentCaptor(KeyguardBypassController.OnBypassStateChangedListener::class.java)
whenever(sessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(keyguardSessionId)
@@ -282,7 +213,6 @@
testScope.backgroundScope
)
- fakeFacePropertyRepository = FakeFacePropertyRepository()
return DeviceEntryFaceAuthRepositoryImpl(
mContext,
fmOverride,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index 98f0211..9d34903 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -18,23 +18,27 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
+import android.view.accessibility.accessibilityManagerWrapper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -43,7 +47,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -51,27 +54,25 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardLongPressInteractorTest : SysuiTestCase() {
-
- @Mock private lateinit var logger: UiEventLogger
- @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
+ private val kosmos =
+ testKosmos().apply {
+ this.accessibilityManagerWrapper = mock<AccessibilityManagerWrapper>()
+ this.uiEventLogger = mock<UiEventLoggerFake>()
+ }
private lateinit var underTest: KeyguardLongPressInteractor
- private lateinit var testScope: TestScope
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private val logger = kosmos.uiEventLogger
+ private val testScope = kosmos.testScope
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, true)
- whenever(accessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenAnswer {
- it.arguments[0]
- }
-
- testScope = TestScope()
- keyguardRepository = FakeKeyguardRepository()
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ whenever(kosmos.accessibilityManagerWrapper.getRecommendedTimeoutMillis(anyInt(), anyInt()))
+ .thenAnswer { it.arguments[0] }
runBlocking { createUnderTest() }
}
@@ -284,25 +285,22 @@
isRevampedWppFeatureEnabled: Boolean = true,
isOpenWppDirectlyEnabled: Boolean = false,
) {
+ // This needs to be re-created for each test outside of kosmos since the flag values are
+ // read during initialization to set up flows. Maybe there is a better way to handle that.
underTest =
KeyguardLongPressInteractor(
appContext = mContext,
scope = testScope.backgroundScope,
- transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = keyguardTransitionRepository,
- )
- .keyguardTransitionInteractor,
+ transitionInteractor = kosmos.keyguardTransitionInteractor,
repository = keyguardRepository,
logger = logger,
featureFlags =
- FakeFeatureFlags().apply {
+ kosmos.fakeFeatureFlagsClassic.apply {
set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled)
},
broadcastDispatcher = fakeBroadcastDispatcher,
- accessibilityManager = accessibilityManager
+ accessibilityManager = kosmos.accessibilityManagerWrapper
)
setUpState()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index ce43d4e..9b302ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -19,23 +19,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.fakeLightRevealScrimRepository
import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.testKosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
@@ -43,29 +43,22 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class LightRevealScrimInteractorTest : SysuiTestCase() {
- private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ val kosmos =
+ testKosmos().apply {
+ this.fakeLightRevealScrimRepository = Mockito.spy(FakeLightRevealScrimRepository())
+ }
- private val fakeLightRevealScrimRepository by lazy {
- Mockito.spy(FakeLightRevealScrimRepository())
- }
+ private val fakeLightRevealScrimRepository = kosmos.fakeLightRevealScrimRepository
- private val testScope = TestScope()
+ private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val testScope = kosmos.testScope
- private val keyguardTransitionInteractor by lazy {
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = fakeKeyguardTransitionRepository,
- )
- .keyguardTransitionInteractor
- }
-
- private lateinit var underTest: LightRevealScrimInteractor
+ private val underTest = kosmos.lightRevealScrimInteractor
private val reveal1 =
object : LightRevealEffect {
@@ -77,19 +70,6 @@
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {}
}
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- underTest =
- LightRevealScrimInteractor(
- keyguardTransitionInteractor,
- fakeLightRevealScrimRepository,
- testScope.backgroundScope,
- mock(),
- mock()
- )
- }
-
@Test
fun lightRevealEffect_doesNotChangeDuringKeyguardTransition() =
runTest(UnconfinedTestDispatcher()) {
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml
new file mode 100644
index 0000000..ad22894
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true">
+ <shape android:shape="oval">
+ <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/>
+ </shape>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml
new file mode 100644
index 0000000..8c2b036
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true">
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp" />
+ <stroke android:width="3dp"
+ android:color="@color/bouncer_password_focus_color" />
+ <padding android:bottom="8dp" android:left="8dp" android:right="8dp" android:top="8dp"/>
+ </shape>
+ </item>
+ <item>
+ <inset android:insetLeft="-4dp"
+ android:insetRight="-4dp"
+ android:insetTop="-4dp">
+ <shape android:shape="rectangle">
+ <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/>
+ </shape>
+ </inset>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml
similarity index 100%
rename from packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
rename to packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 2fc1d2e..909d4fc 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -54,7 +54,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/keyguard_accessibility_password"
- android:gravity="center_horizontal"
+ android:gravity="center"
android:singleLine="true"
android:textStyle="normal"
android:inputType="textPassword"
@@ -68,14 +68,14 @@
<ImageView android:id="@+id/switch_ime_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="12dp"
android:src="@drawable/ic_lockscreen_ime"
android:contentDescription="@string/accessibility_ime_switch_button"
android:clickable="true"
- android:padding="8dip"
+ android:layout_marginRight="8dp"
+ android:padding="12dip"
android:tint="?android:attr/textColorPrimary"
android:layout_gravity="end|center_vertical"
- android:background="?android:attr/selectableItemBackground"
+ android:background="@drawable/bouncer_input_method_background"
android:visibility="gone"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index ddad1e3..e853f02 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -28,8 +28,14 @@
<!-- Width for the keyguard pin input field -->
<dimen name="keyguard_pin_field_width">292dp</dimen>
- <!-- Width for the keyguard pin input field -->
- <dimen name="keyguard_pin_field_height">48dp</dimen>
+ <!-- height for the keyguard pin input field -->
+ <dimen name="keyguard_pin_field_height">56dp</dimen>
+
+ <!-- height for the keyguard password input field -->
+ <dimen name="keyguard_password_field_height">56dp</dimen>
+
+ <!-- width for the keyguard password input field -->
+ <dimen name="keyguard_password_field_width">276dp</dimen>
<!-- Height of the sliding KeyguardSecurityContainer
(includes 2x keyguard_security_view_top_margin) -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 4789a22..c43e394 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -76,7 +76,7 @@
</style>
<style name="Widget.TextView.Password" parent="@android:style/Widget.TextView">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item>
+ <item name="android:background">@drawable/bouncer_pin_view_focused_background</item>
<item name="android:gravity">center</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 51012a4..cc31754 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -174,7 +174,7 @@
<dimen name="status_bar_clock_size">14sp</dimen>
<!-- The starting padding for the clock in the status bar. -->
- <dimen name="status_bar_clock_starting_padding">7dp</dimen>
+ <dimen name="status_bar_clock_starting_padding">4dp</dimen>
<!-- The end padding for the clock in the status bar. -->
<dimen name="status_bar_clock_end_padding">0dp</dimen>
@@ -395,7 +395,7 @@
<dimen name="status_bar_icon_horizontal_margin">0sp</dimen>
<!-- the padding on the start of the statusbar -->
- <dimen name="status_bar_padding_start">8dp</dimen>
+ <dimen name="status_bar_padding_start">4dp</dimen>
<!-- the padding on the end of the statusbar -->
<dimen name="status_bar_padding_end">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8971859..f0cbe7a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -305,6 +305,27 @@
<!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] -->
<string name="screenrecord_start_error">Error starting screen recording</string>
+ <!-- Notification title displayed for issue recording [CHAR LIMIT=50]-->
+ <string name="issuerecord_title">Issue Recorder</string>
+ <!-- Processing issue recoding data in the background [CHAR LIMIT=30]-->
+ <string name="issuerecord_background_processing_label">Processing issue recording</string>
+ <!-- Description of the screen recording notification channel [CHAR LIMIT=NONE]-->
+ <string name="issuerecord_channel_description">Ongoing notification for an issue collection session</string>
+
+ <!-- Notification text displayed when we are recording the screen [CHAR LIMIT=100]-->
+ <string name="issuerecord_ongoing_screen_only">Recording issue</string>
+ <!-- Label for notification action to share issue recording [CHAR LIMIT=35] -->
+ <string name="issuerecord_share_label">Share</string>
+ <!-- A toast message shown after successfully canceling a issue recording [CHAR LIMIT=NONE] -->
+ <!-- Notification text shown after saving a issue recording [CHAR LIMIT=100] -->
+ <string name="issuerecord_save_title">Issue recording saved</string>
+ <!-- Subtext for a notification shown after saving a issue recording to prompt the user to view it [CHAR_LIMIT=100] -->
+ <string name="issuerecord_save_text">Tap to view</string>
+ <!-- A toast message shown when there is an error saving a issue recording [CHAR LIMIT=NONE] -->
+ <string name="issuerecord_save_error">Error saving issue recording</string>
+ <!-- A toast message shown when the issue recording cannot be started due to a generic error [CHAR LIMIT=NONE] -->
+ <string name="issuerecord_start_error">Error starting issue recording</string>
+
<!-- Cling help message title when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
<string name="immersive_cling_title">Viewing full screen</string>
<!-- Cling help message description when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index efd8f7f..1a10c7a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -30,6 +30,7 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
import com.android.systemui.Flags;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.ui.BouncerMessageView;
@@ -212,6 +213,7 @@
private final SelectedUserInteractor mSelectedUserInteractor;
private final UiEventLogger mUiEventLogger;
private final KeyboardRepository mKeyboardRepository;
+ private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -226,7 +228,8 @@
KeyguardViewController keyguardViewController,
FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
UiEventLogger uiEventLogger,
- KeyboardRepository keyboardRepository) {
+ KeyboardRepository keyboardRepository,
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -244,6 +247,7 @@
mSelectedUserInteractor = selectedUserInteractor;
mUiEventLogger = uiEventLogger;
mKeyboardRepository = keyboardRepository;
+ mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -265,7 +269,8 @@
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
mFalsingCollector, mKeyguardViewController,
- mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
+ mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
+ mKeyguardKeyboardInteractor);
} else if (keyguardInputView instanceof KeyguardPINView) {
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 3d8aaaf..7473e0c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -17,8 +17,10 @@
package com.android.keyguard;
import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.text.Editable;
import android.text.InputType;
@@ -27,6 +29,7 @@
import android.text.method.TextKeyListener;
import android.view.KeyEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodInfo;
@@ -39,6 +42,8 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
+import com.android.systemui.Flags;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -52,6 +57,7 @@
public class KeyguardPasswordViewController
extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
+ private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
private final DevicePostureController mPostureController;
private final DevicePostureController.Callback mPostureCallback = posture ->
@@ -60,6 +66,8 @@
private final DelayableExecutor mMainExecutor;
private final KeyguardViewController mKeyguardViewController;
private final boolean mShowImeAtScreenOn;
+ private Drawable mDefaultPasswordFieldBackground;
+ private Drawable mFocusedPasswordFieldBackground;
private EditText mPasswordEntry;
private ImageView mSwitchImeButton;
private boolean mPaused;
@@ -121,7 +129,8 @@
KeyguardViewController keyguardViewController,
DevicePostureController postureController,
FeatureFlags featureFlags,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor,
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController, featureFlags, selectedUserInteractor);
@@ -130,11 +139,15 @@
mPostureController = postureController;
mMainExecutor = mainExecutor;
mKeyguardViewController = keyguardViewController;
+ mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
if (featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
view.setIsLockScreenLandscapeEnabled();
}
mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+ mDefaultPasswordFieldBackground = mPasswordEntry.getBackground();
+ mFocusedPasswordFieldBackground = getResources().getDrawable(
+ R.drawable.bouncer_password_view_background);
mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
}
@@ -175,6 +188,27 @@
// If there's more than one IME, enable the IME switcher button
updateSwitchImeButton();
+
+ if (Flags.pinInputFieldStyledFocusState()) {
+ collectFlow(mPasswordEntry,
+ mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
+ this::setPasswordFieldFocusBackground);
+
+ ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams();
+ layoutParams.height = (int) getResources()
+ .getDimension(R.dimen.keyguard_password_field_height);
+ layoutParams.width = (int) getResources()
+ .getDimension(R.dimen.keyguard_password_field_width);
+ }
+
+ }
+
+ private void setPasswordFieldFocusBackground(boolean isAnyKeyboardConnected) {
+ if (isAnyKeyboardConnected) {
+ mPasswordEntry.setBackground(mFocusedPasswordFieldBackground);
+ } else {
+ mPasswordEntry.setBackground(mDefaultPasswordFieldBackground);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt
new file mode 100644
index 0000000..c39d3e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class KeyguardKeyboardInteractor @Inject constructor(keyboardRepository: KeyboardRepository) {
+ val isAnyKeyboardConnected: Flow<Boolean> = keyboardRepository.isAnyKeyboardConnected
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index 92eeace..904d5898 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -23,6 +23,7 @@
import com.android.systemui.dreams.DreamOverlayService;
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
import com.android.systemui.keyguard.KeyguardService;
+import com.android.systemui.recordissue.IssueRecordingService;
import com.android.systemui.screenrecord.RecordingService;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
import com.android.systemui.wallpapers.ImageWallpaper;
@@ -85,4 +86,11 @@
@IntoMap
@ClassKey(RecordingService.class)
public abstract Service bindRecordingService(RecordingService service);
+
+ /** Inject into IssueRecordingService */
+ @Binds
+ @IntoMap
+ @ClassKey(IssueRecordingService.class)
+ public abstract Service bindIssueRecordingService(IssueRecordingService service);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index ec29bd6..89cdd25 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -32,19 +32,13 @@
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
-import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
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.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import javax.inject.Inject
interface StickyKeysRepository {
@@ -53,14 +47,12 @@
}
@SysUISingleton
-@OptIn(ExperimentalCoroutinesApi::class)
class StickyKeysRepositoryImpl
@Inject
constructor(
private val inputManager: InputManager,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- private val secureSettings: SecureSettings,
- userRepository: UserRepository,
+ secureSettingsRepository: UserAwareSecureSettingsRepository,
private val stickyKeysLogger: StickyKeysLogger,
) : StickyKeysRepository {
@@ -78,25 +70,10 @@
.flowOn(backgroundDispatcher)
override val settingEnabled: Flow<Boolean> =
- userRepository.selectedUserInfo
- .flatMapLatest { stickyKeySettingObserver(it.id) }
- .flowOn(backgroundDispatcher)
-
- private fun stickyKeySettingObserver(userId: Int): Flow<Boolean> {
- return secureSettings
- .observerFlow(userId, SETTING_KEY)
- .onStart { emit(Unit) }
- .map { isSettingEnabledForCurrentUser(userId) }
- .distinctUntilChanged()
+ secureSettingsRepository
+ .boolSettingForActiveUser(SETTING_KEY, defaultValue = false)
.onEach { stickyKeysLogger.logNewSettingValue(it) }
- }
-
- private fun isSettingEnabledForCurrentUser(userId: Int) =
- secureSettings.getIntForUser(
- /* name= */ SETTING_KEY,
- /* default= */ 0,
- /* userHandle= */ userId
- ) != 0
+ .flowOn(backgroundDispatcher)
private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
val keys = linkedMapOf<ModifierKey, Locked>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 53c81e5..e18e463 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -109,7 +109,7 @@
* This animation will take place entirely within the Launcher window. We can safely unlock the
* device, end remote animations, etc. even if this is still running.
*/
-const val LAUNCHER_ICONS_ANIMATION_DURATION_MS = 633L
+const val LAUNCHER_ICONS_ANIMATION_DURATION_MS = 1633L
/**
* How long to wait for the shade to get out of the way before starting the canned unlock animation.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 8fa33ee7..5606d43 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -21,18 +21,18 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
@@ -85,15 +85,16 @@
keyguardInteractor
.dozeTransitionTo(DozeStateModel.FINISH)
.sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isKeyguardOccluded,
- ::Pair
- ),
- ::toTriple
+ startedKeyguardTransitionStep,
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.biometricUnlockState,
)
- .collect { (_, lastStartedStep, occluded) ->
- if (lastStartedStep.to == KeyguardState.AOD && !occluded) {
+ .collect { (_, lastStartedStep, occluded, biometricUnlockState) ->
+ if (
+ lastStartedStep.to == KeyguardState.AOD &&
+ !occluded &&
+ !isWakeAndUnlock(biometricUnlockState)
+ ) {
val modeOnCanceled =
if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
TransitionModeOnCanceled.REVERSE
@@ -126,15 +127,29 @@
}
private fun listenForAodToGone() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
scope.launch {
keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect {
(biometricUnlockState, keyguardState) ->
+ KeyguardWmStateRefactor.assertInLegacyMode()
if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) {
startTransitionTo(KeyguardState.GONE)
}
}
}
}
+
+ /**
+ * Dismisses AOD and transitions to GONE. This is called whenever authentication occurs while on
+ * AOD.
+ */
+ fun dismissAod() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 7477624..6b85a63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -68,11 +68,11 @@
scope.launch {
keyguardInteractor.isKeyguardShowing
.sample(
- startedKeyguardTransitionStep,
+ currentKeyguardState,
communalInteractor.isIdleOnCommunal,
)
- .collect { (isKeyguardShowing, lastStartedStep, isIdleOnCommunal) ->
- if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
+ .collect { (isKeyguardShowing, currentState, isIdleOnCommunal) ->
+ if (isKeyguardShowing && currentState == KeyguardState.GONE) {
val to =
if (isIdleOnCommunal) {
KeyguardState.GLANCEABLE_HUB
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 8784723..c496a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.isSurfaceVisible
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.util.kotlin.toPx
import dagger.Lazy
import javax.inject.Inject
@@ -44,6 +45,7 @@
transitionInteractor: KeyguardTransitionInteractor,
inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
swipeToDismissInteractor: SwipeToDismissInteractor,
+ notificationLaunchInteractor: NotificationLaunchAnimationInteractor,
) {
/**
* The view params to use for the surface. These params describe the alpha/translation values to
@@ -53,10 +55,20 @@
combine(
transitionInteractor.startedKeyguardTransitionStep,
transitionInteractor.currentKeyguardState,
- ) { startedStep, currentState ->
+ notificationLaunchInteractor.isLaunchAnimationRunning,
+ ) { startedStep, currentState, notifAnimationRunning ->
// If we're in transition to GONE, special unlock animation params apply.
if (startedStep.to == KeyguardState.GONE && currentState != KeyguardState.GONE) {
- if (inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath()) {
+ if (notifAnimationRunning) {
+ // If the notification launch animation is running, leave the alpha at 0f.
+ // The ActivityLaunchAnimator will morph it from the notification at the
+ // appropriate time.
+ return@combine KeyguardSurfaceBehindModel(
+ alpha = 0f,
+ )
+ } else if (
+ inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath()
+ ) {
// The Launcher icons have their own translation/alpha animations during the
// in-window animation. We'll just make the surface visible and let Launcher
// do its thing.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index b43ab5e..310f13d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -60,6 +60,7 @@
private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
private val fromPrimaryBouncerTransitionInteractor:
dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
+ private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
) {
private val TAG = this::class.simpleName
@@ -346,6 +347,7 @@
when (val startedState = startedKeyguardState.replayCache.last()) {
LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+ AOD -> fromAodTransitionInteractor.get().dismissAod()
else ->
Log.e(
"KeyguardTransitionInteractor",
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 19d00cf..c7f262a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -42,7 +42,7 @@
private val lightRevealScrimRepository: LightRevealScrimRepository,
@Application private val scope: CoroutineScope,
private val scrimLogger: ScrimLogger,
- powerInteractor: PowerInteractor,
+ private val powerInteractor: PowerInteractor,
) {
init {
@@ -83,11 +83,13 @@
// (invisible) jank. However, we need to still pass through 1f and 0f to ensure that the
// correct end states are respected even if the screen turned off (or was still off)
// when the animation finished
- powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF ||
- it == 1f ||
- it == 0f
+ screenIsShowingContent() || it == 1f || it == 0f
}
+ private fun screenIsShowingContent() =
+ powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF &&
+ powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON
+
companion object {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 5c2df45..3ccbdba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -60,6 +60,7 @@
// The following are MutableSharedFlows, and do not require flowOn
val startedKeyguardState = transitionInteractor.startedKeyguardState
val finishedKeyguardState = transitionInteractor.finishedKeyguardState
+ val currentKeyguardState = transitionInteractor.currentKeyguardState
suspend fun startTransitionTo(
toState: KeyguardState,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 49af664..b81793e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -19,6 +19,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -26,7 +28,6 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import javax.inject.Inject
@SysUISingleton
class WindowManagerLockscreenVisibilityInteractor
@@ -37,6 +38,7 @@
surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
fromLockscreenInteractor: FromLockscreenTransitionInteractor,
fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+ notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
) {
private val defaultSurfaceBehindVisibility =
transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
@@ -72,8 +74,7 @@
*/
@OptIn(ExperimentalCoroutinesApi::class)
val surfaceBehindVisibility: Flow<Boolean> =
- transitionInteractor
- .isInTransitionToAnyState
+ transitionInteractor.isInTransitionToAnyState
.flatMapLatest { isInTransition ->
if (!isInTransition) {
defaultSurfaceBehindVisibility
@@ -99,12 +100,16 @@
combine(
transitionInteractor.isInTransitionToState(KeyguardState.GONE),
transitionInteractor.finishedKeyguardState,
- surfaceBehindInteractor.isAnimatingSurface
- ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+ surfaceBehindInteractor.isAnimatingSurface,
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
+ ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
+ // Using the animation if we're animating it directly, or if the
+ // ActivityLaunchAnimator is in the process of animating it.
+ val animationsRunning = isAnimatingSurface || notifLaunchRunning
// We may still be animating the surface after the keyguard is fully GONE, since
// some animations (like the translation spring) are not tied directly to the
// transition step amount.
- isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+ isInTransitionToGone || (finishedState == KeyguardState.GONE && animationsRunning)
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index c33ab12..523414c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -586,7 +586,7 @@
coroutineScope.launch {
communalInteractor.isCommunalShowing.collect { value ->
isCommunalShowing = value
- updateDesiredLocation()
+ updateDesiredLocation(forceNoAnimation = true)
}
}
}
@@ -1149,17 +1149,13 @@
when {
mediaFlags.isSceneContainerEnabled() -> desiredLocation
dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
-
- // UMO should show in communal unless the shade is expanding or visible.
- isCommunalShowing && qsExpansion == 0.0f -> LOCATION_COMMUNAL_HUB
(qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
-
- // Communal does not have its own StatusBarState so it should always have higher
- // priority for the UMO over the lockscreen.
- isCommunalShowing -> LOCATION_COMMUNAL_HUB
+ // TODO(b/311234666): revisit logic once interactions between the hub and
+ // shade/keyguard state are finalized
+ isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
index f1cade7..afe6285 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
@@ -16,6 +16,9 @@
package com.android.systemui.mediaprojection
+import android.compat.annotation.ChangeId
+import android.compat.annotation.Disabled
+import android.compat.annotation.Overridable
import android.content.Context
import android.media.projection.IMediaProjection
import android.media.projection.IMediaProjectionManager
@@ -31,6 +34,18 @@
*/
class MediaProjectionServiceHelper {
companion object {
+ /**
+ * This change id ensures that users are presented with a choice of capturing a single app
+ * or the entire screen when initiating a MediaProjection session, overriding the usage of
+ * MediaProjectionConfig#createConfigForDefaultDisplay.
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ const val OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION = 316897322L // buganizer id
+
private const val TAG = "MediaProjectionServiceHelper"
private val service =
IMediaProjectionManager.Stub.asInterface(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 8b034b2..0769731 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -16,21 +16,26 @@
package com.android.systemui.mediaprojection.permission;
+import static android.Manifest.permission.LOG_COMPAT_CHANGE;
+import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static com.android.systemui.mediaprojection.MediaProjectionServiceHelper.OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION;
import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.ENTIRE_SCREEN;
import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AlertDialog;
import android.app.StatusBarManager;
+import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -104,6 +109,7 @@
}
@Override
+ @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE})
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -231,6 +237,9 @@
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
if (isPartialScreenSharingEnabled()) {
+ final boolean overrideDisableSingleAppOption = CompatChanges.isChangeEnabled(
+ OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+ mPackageName, getHostUserHandle());
MediaProjectionPermissionDialogDelegate delegate =
new MediaProjectionPermissionDialogDelegate(
dialogContext,
@@ -242,6 +251,7 @@
},
() -> finish(RECORD_CANCEL, /* projection= */ null),
appName,
+ overrideDisableSingleAppOption,
mUid,
mMediaProjectionMetricsLogger);
mDialog =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 0f54e93..9ce8070 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -30,11 +30,12 @@
private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
private val onCancelClicked: Runnable,
private val appName: String?,
+ forceShowPartialScreenshare: Boolean,
hostUid: Int,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
- createOptionList(context, appName, mediaProjectionConfig),
+ createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
appName,
hostUid,
mediaProjectionMetricsLogger
@@ -65,7 +66,8 @@
private fun createOptionList(
context: Context,
appName: String?,
- mediaProjectionConfig: MediaProjectionConfig?
+ mediaProjectionConfig: MediaProjectionConfig?,
+ overrideDisableSingleAppOption: Boolean = false,
): List<ScreenShareOption> {
val singleAppWarningText =
if (appName == null) {
@@ -80,8 +82,13 @@
R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
}
+ // The single app option should only be disabled if there is an app name provided,
+ // the client has setup a MediaProjection with
+ // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
+ // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
val singleAppOptionDisabled =
appName != null &&
+ !overrideDisableSingleAppOption &&
mediaProjectionConfig?.regionToCapture ==
MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 88863cb..a474868 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -42,6 +42,7 @@
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.recordissue.IssueRecordingService
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingService
@@ -107,7 +108,7 @@
PendingIntent.getService(
userContextProvider.userContext,
RecordingService.REQUEST_CODE,
- RecordingService.getStopIntent(userContextProvider.userContext),
+ IssueRecordingService.getStopIntent(userContextProvider.userContext),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
.send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
new file mode 100644
index 0000000..f487258
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 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.recordissue
+
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.os.Handler
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.LongRunning
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.screenrecord.RecordingController
+import com.android.systemui.screenrecord.RecordingService
+import com.android.systemui.screenrecord.RecordingServiceStrings
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+class IssueRecordingService
+@Inject
+constructor(
+ controller: RecordingController,
+ @LongRunning executor: Executor,
+ @Main handler: Handler,
+ uiEventLogger: UiEventLogger,
+ notificationManager: NotificationManager,
+ userContextProvider: UserContextProvider,
+ keyguardDismissUtil: KeyguardDismissUtil
+) :
+ RecordingService(
+ controller,
+ executor,
+ handler,
+ uiEventLogger,
+ notificationManager,
+ userContextProvider,
+ keyguardDismissUtil
+ ) {
+
+ override fun getTag(): String = TAG
+
+ override fun getChannelId(): String = CHANNEL_ID
+
+ override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources)
+
+ companion object {
+ private const val TAG = "IssueRecordingService"
+ private const val CHANNEL_ID = "issue_record"
+
+ /**
+ * Get an intent to stop the issue recording service.
+ *
+ * @param context Context from the requesting activity
+ * @return
+ */
+ fun getStopIntent(context: Context): Intent =
+ Intent(context, RecordingService::class.java)
+ .setAction(ACTION_STOP)
+ .putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
+
+ /**
+ * Get an intent to start the issue recording service.
+ *
+ * @param context Context from the requesting activity
+ */
+ fun getStartIntent(context: Context): Intent =
+ Intent(context, RecordingService::class.java).setAction(ACTION_START)
+ }
+}
+
+private class IrsStrings(private val res: Resources) : RecordingServiceStrings(res) {
+ override val title
+ get() = res.getString(R.string.issuerecord_title)
+ override val notificationChannelDescription
+ get() = res.getString(R.string.issuerecord_channel_description)
+ override val startErrorResId
+ get() = R.string.issuerecord_start_error
+ override val startError
+ get() = res.getString(R.string.issuerecord_start_error)
+ override val saveErrorResId
+ get() = R.string.issuerecord_save_error
+ override val saveError
+ get() = res.getString(R.string.issuerecord_save_error)
+ override val ongoingRecording
+ get() = res.getString(R.string.issuerecord_ongoing_screen_only)
+ override val backgroundProcessingLabel
+ get() = res.getString(R.string.issuerecord_background_processing_label)
+ override val saveTitle
+ get() = res.getString(R.string.issuerecord_save_title)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index e051df4..80f11f1 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -17,7 +17,6 @@
package com.android.systemui.recordissue
import android.annotation.SuppressLint
-import android.app.Activity
import android.app.BroadcastOptions
import android.app.PendingIntent
import android.content.Context
@@ -45,7 +44,6 @@
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingService
-import com.android.systemui.screenrecord.ScreenRecordingAudioSource
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
@@ -183,13 +181,7 @@
PendingIntent.getForegroundService(
userContextProvider.userContext,
RecordingService.REQUEST_CODE,
- RecordingService.getStartIntent(
- userContextProvider.userContext,
- Activity.RESULT_OK,
- ScreenRecordingAudioSource.NONE.ordinal,
- false,
- null
- ),
+ IssueRecordingService.getStartIntent(userContextProvider.userContext),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
.send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b5a1313..b355d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -24,7 +24,6 @@
import android.app.Service;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.media.MediaRecorder;
import android.net.Uri;
@@ -71,8 +70,8 @@
private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
- private static final String ACTION_START = "com.android.systemui.screenrecord.START";
- private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
+ protected static final String ACTION_START = "com.android.systemui.screenrecord.START";
+ protected static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
private static final String ACTION_STOP_NOTIF =
"com.android.systemui.screenrecord.STOP_FROM_NOTIF";
private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
@@ -90,6 +89,7 @@
private final NotificationManager mNotificationManager;
private final UserContextProvider mUserContextTracker;
private int mNotificationId = NOTIF_BASE_ID;
+ private RecordingServiceStrings mStrings;
@Inject
public RecordingService(RecordingController controller, @LongRunning Executor executor,
@@ -134,9 +134,9 @@
return Service.START_NOT_STICKY;
}
String action = intent.getAction();
- Log.d(TAG, "onStartCommand " + action);
+ Log.d(getTag(), "onStartCommand " + action);
NotificationChannel channel = new NotificationChannel(
- CHANNEL_ID,
+ getChannelId(),
getString(R.string.screenrecord_title),
NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription(getString(R.string.screenrecord_channel_description));
@@ -152,7 +152,7 @@
mNotificationId = NOTIF_BASE_ID + (int) SystemClock.uptimeMillis();
mAudioSource = ScreenRecordingAudioSource
.values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)];
- Log.d(TAG, "recording with audio source " + mAudioSource);
+ Log.d(getTag(), "recording with audio source " + mAudioSource);
mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
MediaProjectionCaptureTarget captureTarget =
intent.getParcelableExtra(EXTRA_CAPTURE_TARGET,
@@ -207,7 +207,7 @@
.setType("video/mp4")
.putExtra(Intent.EXTRA_STREAM, shareUri);
mKeyguardDismissUtil.executeWhenUnlocked(() -> {
- String shareLabel = getResources().getString(R.string.screenrecord_share_label);
+ String shareLabel = strings().getShareLabel();
startActivity(Intent.createChooser(shareIntent, shareLabel)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
// Remove notification
@@ -270,13 +270,11 @@
*/
@VisibleForTesting
protected void createErrorNotification() {
- Resources res = getResources();
Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- res.getString(R.string.screenrecord_title));
- String notificationTitle = res.getString(R.string.screenrecord_start_error);
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
+ String notificationTitle = strings().getStartError();
- Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+ Notification.Builder builder = new Notification.Builder(this, getChannelId())
.setSmallIcon(R.drawable.ic_screenrecord)
.setContentTitle(notificationTitle)
.addExtras(extras);
@@ -290,14 +288,12 @@
@VisibleForTesting
protected void createRecordingNotification() {
- Resources res = getResources();
Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- res.getString(R.string.screenrecord_title));
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
- ? res.getString(R.string.screenrecord_ongoing_screen_only)
- : res.getString(R.string.screenrecord_ongoing_screen_and_audio);
+ ? strings().getOngoingRecording()
+ : strings().getOngoingRecordingWithAudio();
PendingIntent pendingIntent = PendingIntent.getService(
this,
@@ -306,9 +302,9 @@
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Notification.Action stopAction = new Notification.Action.Builder(
Icon.createWithResource(this, R.drawable.ic_android),
- getResources().getString(R.string.screenrecord_stop_label),
+ strings().getStopLabel(),
pendingIntent).build();
- Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+ Notification.Builder builder = new Notification.Builder(this, getChannelId())
.setSmallIcon(R.drawable.ic_screenrecord)
.setContentTitle(notificationTitle)
.setUsesChronometer(true)
@@ -323,19 +319,17 @@
@VisibleForTesting
protected Notification createProcessingNotification() {
- Resources res = getApplicationContext().getResources();
String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
- ? res.getString(R.string.screenrecord_ongoing_screen_only)
- : res.getString(R.string.screenrecord_ongoing_screen_and_audio);
+ ? strings().getOngoingRecording()
+ : strings().getOngoingRecordingWithAudio();
Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- res.getString(R.string.screenrecord_title));
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
- Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+ Notification.Builder builder = new Notification.Builder(this, getChannelId())
.setContentTitle(notificationTitle)
.setContentText(
- getResources().getString(R.string.screenrecord_background_processing_label))
+ strings().getBackgroundProcessingLabel())
.setSmallIcon(R.drawable.ic_screenrecord)
.setGroup(GROUP_KEY)
.addExtras(extras);
@@ -351,7 +345,7 @@
Notification.Action shareAction = new Notification.Action.Builder(
Icon.createWithResource(this, R.drawable.ic_screenrecord),
- getResources().getString(R.string.screenrecord_share_label),
+ strings().getShareLabel(),
PendingIntent.getService(
this,
REQUEST_CODE,
@@ -360,13 +354,12 @@
.build();
Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- getResources().getString(R.string.screenrecord_title));
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
- Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+ Notification.Builder builder = new Notification.Builder(this, getChannelId())
.setSmallIcon(R.drawable.ic_screenrecord)
- .setContentTitle(getResources().getString(R.string.screenrecord_save_title))
- .setContentText(getResources().getString(R.string.screenrecord_save_text))
+ .setContentTitle(strings().getSaveTitle())
+ .setContentText(strings().getSaveText())
.setContentIntent(PendingIntent.getActivity(
this,
REQUEST_CODE,
@@ -394,15 +387,15 @@
private void postGroupNotification(UserHandle currentUser) {
Bundle extras = new Bundle();
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- getResources().getString(R.string.screenrecord_title));
- Notification groupNotif = new Notification.Builder(this, CHANNEL_ID)
+ strings().getTitle());
+ Notification groupNotif = new Notification.Builder(this, getChannelId())
.setSmallIcon(R.drawable.ic_screenrecord)
- .setContentTitle(getResources().getString(R.string.screenrecord_save_title))
+ .setContentTitle(strings().getSaveTitle())
.setGroup(GROUP_KEY)
.setGroupSummary(true)
.setExtras(extras)
.build();
- mNotificationManager.notifyAsUser(TAG, NOTIF_BASE_ID, groupNotif, currentUser);
+ mNotificationManager.notifyAsUser(getTag(), NOTIF_BASE_ID, groupNotif, currentUser);
}
private void stopService() {
@@ -413,7 +406,7 @@
if (userId == USER_ID_NOT_SPECIFIED) {
userId = mUserContextTracker.getUserContext().getUserId();
}
- Log.d(TAG, "notifying for user " + userId);
+ Log.d(getTag(), "notifying for user " + userId);
setTapsVisible(mOriginalShowTaps);
if (getRecorder() != null) {
try {
@@ -424,7 +417,7 @@
// let's release the recorder and delete all temporary files in this case
getRecorder().release();
showErrorToast(R.string.screenrecord_start_error);
- Log.e(TAG, "stopRecording called, but there was an error when ending"
+ Log.e(getTag(), "stopRecording called, but there was an error when ending"
+ "recording");
exception.printStackTrace();
createErrorNotification();
@@ -435,7 +428,7 @@
throw new RuntimeException(throwable);
}
} else {
- Log.e(TAG, "stopRecording called, but recorder was null");
+ Log.e(getTag(), "stopRecording called, but recorder was null");
}
updateState(false);
stopForeground(STOP_FOREGROUND_DETACH);
@@ -449,13 +442,13 @@
mLongExecutor.execute(() -> {
try {
- Log.d(TAG, "saving recording");
+ Log.d(getTag(), "saving recording");
Notification notification = createSaveNotification(getRecorder().save());
postGroupNotification(currentUser);
mNotificationManager.notifyAsUser(null, mNotificationId, notification,
currentUser);
} catch (IOException | IllegalStateException e) {
- Log.e(TAG, "Error saving screen recording: " + e.getMessage());
+ Log.e(getTag(), "Error saving screen recording: " + e.getMessage());
e.printStackTrace();
showErrorToast(R.string.screenrecord_save_error);
mNotificationManager.cancelAsUser(null, mNotificationId, currentUser);
@@ -468,6 +461,26 @@
Settings.System.putInt(getContentResolver(), Settings.System.SHOW_TOUCHES, value);
}
+ protected String getTag() {
+ return TAG;
+ }
+
+ protected String getChannelId() {
+ return CHANNEL_ID;
+ }
+
+ private RecordingServiceStrings strings() {
+ if (mStrings == null) {
+ mStrings = provideRecordingServiceStrings();
+ }
+ return mStrings;
+ }
+
+ protected RecordingServiceStrings provideRecordingServiceStrings() {
+ return new RecordingServiceStrings(getResources());
+ }
+
+
/**
* Get an intent to stop the recording service.
* @param context Context from the requesting activity
@@ -484,25 +497,25 @@
* @param context
* @return
*/
- protected static Intent getNotificationIntent(Context context) {
- return new Intent(context, RecordingService.class).setAction(ACTION_STOP_NOTIF);
+ protected Intent getNotificationIntent(Context context) {
+ return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF);
}
- private static Intent getShareIntent(Context context, String path) {
- return new Intent(context, RecordingService.class).setAction(ACTION_SHARE)
+ private Intent getShareIntent(Context context, String path) {
+ return new Intent(context, this.getClass()).setAction(ACTION_SHARE)
.putExtra(EXTRA_PATH, path);
}
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
- Log.d(TAG, "Media recorder info: " + what);
+ Log.d(getTag(), "Media recorder info: " + what);
onStartCommand(getStopIntent(this), 0, 0);
}
@Override
public void onStopped() {
if (mController.isRecording()) {
- Log.d(TAG, "Stopping recording because the system requested the stop");
+ Log.d(getTag(), "Stopping recording because the system requested the stop");
stopService();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt
new file mode 100644
index 0000000..fdb1eb6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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.screenrecord
+
+import android.content.res.Resources
+import com.android.systemui.res.R
+
+open class RecordingServiceStrings(private val res: Resources) {
+ open val title
+ get() = res.getString(R.string.screenrecord_title)
+ open val notificationChannelDescription
+ get() = res.getString(R.string.screenrecord_channel_description)
+ open val startErrorResId
+ get() = R.string.screenrecord_start_error
+ open val startError
+ get() = res.getString(R.string.screenrecord_start_error)
+ open val saveErrorResId
+ get() = R.string.screenrecord_save_error
+ open val saveError
+ get() = res.getString(R.string.screenrecord_save_error)
+ open val ongoingRecording
+ get() = res.getString(R.string.screenrecord_ongoing_screen_only)
+ open val backgroundProcessingLabel
+ get() = res.getString(R.string.screenrecord_background_processing_label)
+ open val saveTitle
+ get() = res.getString(R.string.screenrecord_save_title)
+
+ val saveText
+ get() = res.getString(R.string.screenrecord_save_text)
+ val ongoingRecordingWithAudio
+ get() = res.getString(R.string.screenrecord_ongoing_screen_and_audio)
+ val stopLabel
+ get() = res.getString(R.string.screenrecord_stop_label)
+ val shareLabel
+ get() = res.getString(R.string.screenrecord_share_label)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index be1fa2b..e9af295 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -30,11 +30,11 @@
import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.haptics.slider.SeekableSliderEventProducer;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.util.ViewController;
@@ -236,7 +236,7 @@
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mTracking = true;
- mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH);
+ mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH);
if (mListener != null) {
mListener.onChanged(mTracking, getValue(), false);
SeekableSliderEventProducer eventProducer =
@@ -255,7 +255,7 @@
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mTracking = false;
- mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH);
+ mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH);
if (mListener != null) {
mListener.onChanged(mTracking, getValue(), true);
SeekableSliderEventProducer eventProducer =
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
index 3a30880..a8641bf 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
@@ -21,10 +21,10 @@
public enum BrightnessSliderEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "slider started to track touch")
- SLIDER_STARTED_TRACKING_TOUCH(1472),
- @UiEvent(doc = "slider stopped tracking touch")
- SLIDER_STOPPED_TRACKING_TOUCH(1473);
+ @UiEvent(doc = "brightness slider started to track touch")
+ BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH(1472),
+ @UiEvent(doc = "brightness slider stopped tracking touch")
+ BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH(1473);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
index 52a1c15..095d30e 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
@@ -21,9 +21,7 @@
import android.widget.RemoteViews
import com.android.systemui.communal.smartspace.CommunalSmartspaceController
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.BcSmartspaceDataPlugin
-import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,7 +41,6 @@
@Inject
constructor(
private val communalSmartspaceController: CommunalSmartspaceController,
- @Main private val uiExecutor: Executor,
) : SmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
override val isSmartspaceRemoteViewsEnabled: Boolean
@@ -54,18 +51,12 @@
override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> =
_communalSmartspaceTargets
.onStart {
- uiExecutor.execute {
- communalSmartspaceController.addListener(
- listener = this@SmartspaceRepositoryImpl
- )
- }
+ communalSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl)
}
.onCompletion {
- uiExecutor.execute {
- communalSmartspaceController.removeListener(
- listener = this@SmartspaceRepositoryImpl
- )
- }
+ communalSmartspaceController.removeListener(
+ listener = this@SmartspaceRepositoryImpl
+ )
}
override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
index 783488af..4e40888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
@@ -37,11 +37,12 @@
) {
suspend fun bind(view: NotificationIconContainer) {
viewModel.icons.bindIcons(
- view,
- configuration,
- systemBarUtilsState,
+ logTag = "shelf",
+ view = view,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
notifyBindingFailures = { failureTracker.shelfFailures = it },
- viewStore,
+ viewStore = viewStore,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index f375ebc..de3a626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -72,11 +72,12 @@
val iconColors: StateFlow<NotificationIconColors> =
viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) }.stateIn(this)
viewModel.icons.bindIcons(
- view,
- configuration,
- systemBarUtilsState,
+ logTag = "statusbar",
+ view = view,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
notifyBindingFailures = { failureTracker.statusBarFailures = it },
- viewStore,
+ viewStore = viewStore,
) { _, sbiv ->
StatusBarIconViewBinder.bindIconColors(
sbiv,
@@ -124,11 +125,12 @@
val tintAlpha = viewModel.tintAlpha.stateIn(this)
val animsEnabled = viewModel.areIconAnimationsEnabled.stateIn(this)
viewModel.icons.bindIcons(
- view,
- configuration,
- systemBarUtilsState,
+ logTag = "aod",
+ view = view,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
notifyBindingFailures = { failureTracker.aodFailures = it },
- viewStore,
+ viewStore = viewStore,
) { _, sbiv ->
coroutineScope {
launch { StatusBarIconViewBinder.bindColor(sbiv, color) }
@@ -176,6 +178,7 @@
* view is to be unbound.
*/
suspend fun Flow<NotificationIconsViewData>.bindIcons(
+ logTag: String,
view: NotificationIconContainer,
configuration: ConfigurationState,
systemBarUtilsState: SystemBarUtilsState,
@@ -197,7 +200,7 @@
}
.stateIn(this)
try {
- bindIcons(view, layoutParams, notifyBindingFailures, viewStore, bindIcon)
+ bindIcons(logTag, view, layoutParams, notifyBindingFailures, viewStore, bindIcon)
} finally {
// Detach everything so that child SBIVs don't hold onto a reference to the container.
view.detachAllIcons()
@@ -205,6 +208,7 @@
}
private suspend fun Flow<NotificationIconsViewData>.bindIcons(
+ logTag: String,
view: NotificationIconContainer,
layoutParams: StateFlow<FrameLayout.LayoutParams>,
notifyBindingFailures: (Collection<String>) -> Unit,
@@ -214,7 +218,7 @@
val failedBindings = mutableSetOf<String>()
val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>()
var prevIcons = NotificationIconsViewData()
- collectTracingEach("NIC#bindIcons") { iconsData: NotificationIconsViewData ->
+ collectTracingEach({ "NIC($logTag)#bindIcons" }) { iconsData: NotificationIconsViewData ->
val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
prevIcons = iconsData
@@ -249,7 +253,7 @@
if (this !== view) {
Log.wtf(
TAG,
- "StatusBarIconView($notifKey) has an unexpected parent",
+ "[$logTag] SBIV($notifKey) has an unexpected parent",
)
}
// If the container was re-inflated and re-bound, then SBIVs might still
@@ -266,7 +270,9 @@
sbiv,
launch {
launch {
- layoutParams.collectTracingEach("SBIV#bindLayoutParams") {
+ layoutParams.collectTracingEach(
+ tag = { "[$logTag] SBIV#bindLayoutParams" },
+ ) {
if (it != sbiv.layoutParams) {
sbiv.layoutParams = it
}
@@ -307,7 +313,12 @@
val childCount = view.childCount
for (i in 0 until childCount) {
val actual = view.getChildAt(i)
- val expected = expectedChildren[i]
+ val expected = expectedChildren.getOrNull(i)
+ if (expected == null) {
+ Log.wtf(TAG, "[$logTag] Unexpected child $actual")
+ view.removeView(actual)
+ continue
+ }
if (actual === expected) {
continue
}
@@ -379,3 +390,11 @@
tag: String,
crossinline collector: (T) -> Unit,
) = collect { traceSection(tag) { collector(it) } }
+
+private suspend inline fun <T> Flow<T>.collectTracingEach(
+ noinline tag: () -> String,
+ crossinline collector: (T) -> Unit,
+) {
+ val lazyTag = lazy(mode = LazyThreadSafetyMode.PUBLICATION, tag)
+ collect { traceSection({ lazyTag.value }) { collector(it) } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 69282ae..3ad60d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1442,10 +1442,6 @@
hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
-
- if (KeyguardWmStateRefactor.isEnabled()) {
- mKeyguardTransitionInteractor.startDismissKeyguardTransition();
- }
}
/** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 3741f14..c0e36b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -91,7 +91,9 @@
@StatusBarFragmentScope
@Named(OPERATOR_NAME_VIEW)
static View provideOperatorNameView(@RootView PhoneStatusBarView view) {
- return ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
+ View operatorName = ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
+ operatorName.setVisibility(View.GONE);
+ return operatorName;
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 11e374f..d4c180d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -18,25 +18,36 @@
import static com.android.server.notification.Flags.screenshareNotificationHiding;
+import android.annotation.MainThread;
+import android.app.IActivityManager;
+import android.content.Context;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.Trace;
import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.Assert;
import com.android.systemui.util.ListenerSet;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
/** Implementation of SensitiveNotificationProtectionController. **/
@SysUISingleton
public class SensitiveNotificationProtectionControllerImpl
implements SensitiveNotificationProtectionController {
- private final MediaProjectionManager mMediaProjectionManager;
+ private static final String LOG_TAG = "SNPC";
+ private final ArraySet<String> mExemptPackages = new ArraySet<>();
private final ListenerSet<Runnable> mListeners = new ListenerSet<>();
private volatile MediaProjectionInfo mProjection;
@@ -45,44 +56,100 @@
new MediaProjectionManager.Callback() {
@Override
public void onStart(MediaProjectionInfo info) {
- Trace.beginSection(
- "SNPC.onProjectionStart");
- // Only enable sensitive content protection if sharing full screen
- // Launch cookie only set (non-null) if sharing single app/task
- updateProjectionState((info.getLaunchCookie() == null) ? info : null);
- Trace.endSection();
+ Trace.beginSection("SNPC.onProjectionStart");
+ try {
+ // Only enable sensitive content protection if sharing full screen
+ // Launch cookie only set (non-null) if sharing single app/task
+ updateProjectionStateAndNotifyListeners(
+ (info.getLaunchCookie() == null) ? info : null);
+ } finally {
+ Trace.endSection();
+ }
}
@Override
public void onStop(MediaProjectionInfo info) {
- Trace.beginSection(
- "SNPC.onProjectionStop");
- updateProjectionState(null);
- Trace.endSection();
- }
-
- private void updateProjectionState(MediaProjectionInfo info) {
- // capture previous state
- boolean wasSensitive = isSensitiveStateActive();
-
- // update internal state
- mProjection = info;
-
- // if either previous or new state is sensitive, notify listeners.
- if (wasSensitive || isSensitiveStateActive()) {
- mListeners.forEach(Runnable::run);
+ Trace.beginSection("SNPC.onProjectionStop");
+ try {
+ updateProjectionStateAndNotifyListeners(null);
+ } finally {
+ Trace.endSection();
}
}
};
@Inject
public SensitiveNotificationProtectionControllerImpl(
+ Context context,
MediaProjectionManager mediaProjectionManager,
- @Main Handler mainHandler) {
- mMediaProjectionManager = mediaProjectionManager;
+ IActivityManager activityManager,
+ @Main Handler mainHandler,
+ @Background Executor bgExecutor) {
+ if (!screenshareNotificationHiding()) {
+ return;
+ }
- if (screenshareNotificationHiding()) {
- mMediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler);
+ bgExecutor.execute(() -> {
+ ArraySet<String> exemptPackages = new ArraySet<>();
+ // Exempt SystemUI
+ exemptPackages.add(context.getPackageName());
+
+ // Exempt approved bug report handlers
+ try {
+ exemptPackages.addAll(activityManager.getBugreportWhitelistedPackages());
+ } catch (RemoteException e) {
+ Log.e(
+ LOG_TAG,
+ "Error adding bug report handlers to exemption, continuing without",
+ e);
+ // silent failure, skip adding packages to exemption
+ }
+
+ // if currently projecting, notify listeners of exemption changes
+ mainHandler.post(() -> {
+ Trace.beginSection("SNPC.exemptPackagesUpdated");
+ try {
+ updateExemptPackagesAndNotifyListeners(exemptPackages);
+ } finally {
+ Trace.endSection();
+ }
+ });
+ });
+
+ mediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler);
+ }
+
+ /**
+ * Notify listeners of possible ProjectionState change regardless of current
+ * isSensitiveStateActive value. Method used to ensure updates occur after mExemptPackages gets
+ * updated, which directly changes the outcome of isSensitiveStateActive
+ */
+ @MainThread
+ private void updateExemptPackagesAndNotifyListeners(ArraySet<String> exemptPackages) {
+ Assert.isMainThread();
+ mExemptPackages.addAll(exemptPackages);
+
+ if (mProjection != null) {
+ mListeners.forEach(Runnable::run);
+ }
+ }
+
+ /**
+ * Update ProjectionState respecting current isSensitiveStateActive value. Only notifies
+ * listeners
+ */
+ @MainThread
+ private void updateProjectionStateAndNotifyListeners(MediaProjectionInfo info) {
+ Assert.isMainThread();
+ // capture previous state
+ boolean wasSensitive = isSensitiveStateActive();
+
+ // update internal state
+ mProjection = info;
+
+ // if either previous or new state is sensitive, notify listeners.
+ if (wasSensitive || isSensitiveStateActive()) {
+ mListeners.forEach(Runnable::run);
}
}
@@ -96,11 +163,17 @@
mListeners.remove(onSensitiveStateChanged);
}
+ // TODO(b/323396693): opportunity for optimization
@Override
public boolean isSensitiveStateActive() {
+ MediaProjectionInfo projection = mProjection;
+ if (projection == null) {
+ return false;
+ }
+
// TODO(b/316955558): Add disabled by developer option
- // TODO(b/316955306): Add feature exemption for sysui and bug handlers
- return mProjection != null;
+
+ return !mExemptPackages.contains(projection.getPackageName());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
index f36c335e..d509b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
@@ -16,6 +16,9 @@
package com.android.systemui.util.settings;
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository;
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl;
+
import dagger.Binds;
import dagger.Module;
@@ -36,4 +39,9 @@
/** Bind GlobalSettingsImpl to GlobalSettings. */
@Binds
GlobalSettings bindsGlobalSettings(GlobalSettingsImpl impl);
+
+ /** Bind UserAwareSecureSettingsRepositoryImpl to UserAwareSecureSettingsRepository. */
+ @Binds
+ UserAwareSecureSettingsRepository bindsUserAwareSecureSettingsRepository(
+ UserAwareSecureSettingsRepositoryImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
new file mode 100644
index 0000000..d3e5080
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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.util.settings.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxy
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
+
+/**
+ * Repository for observing values of [Settings.Secure] for the currently active user. That means
+ * when user is switched and the new user has different value, flow will emit new value.
+ */
+interface UserAwareSecureSettingsRepository {
+
+ /**
+ * Emits boolean value of the setting for active user. Also emits starting value when
+ * subscribed.
+ * See: [SettingsProxy.getBool].
+ */
+ fun boolSettingForActiveUser(name: String, defaultValue: Boolean = false): Flow<Boolean>
+}
+
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+class UserAwareSecureSettingsRepositoryImpl @Inject constructor(
+ private val secureSettings: SecureSettings,
+ private val userRepository: UserRepository,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : UserAwareSecureSettingsRepository {
+
+ override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { userInfo -> settingObserver(name, defaultValue, userInfo.id) }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ private fun settingObserver(name: String, defaultValue: Boolean, userId: Int): Flow<Boolean> {
+ return secureSettings
+ .observerFlow(userId, name)
+ .onStart { emit(Unit) }
+ .map { secureSettings.getBoolForUser(name, defaultValue, userId) }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
new file mode 100644
index 0000000..ea67eea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 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.volume.dagger
+
+import android.view.accessibility.CaptioningManager
+import com.android.settingslib.view.accessibility.data.repository.CaptioningRepository
+import com.android.settingslib.view.accessibility.data.repository.CaptioningRepositoryImpl
+import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+
+@Module
+interface CaptioningModule {
+
+ companion object {
+
+ @Provides
+ fun provideCaptioningRepository(
+ captioningManager: CaptioningManager,
+ @Background coroutineContext: CoroutineContext,
+ @Application coroutineScope: CoroutineScope,
+ ): CaptioningRepository =
+ CaptioningRepositoryImpl(captioningManager, coroutineContext, coroutineScope)
+
+ @Provides
+ fun provideCaptioningInteractor(repository: CaptioningRepository): CaptioningInteractor =
+ CaptioningInteractor(repository)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 5cb6fa8..2718839 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -62,6 +62,7 @@
@Module(
includes = {
AudioModule.class,
+ CaptioningModule.class,
MediaDevicesModule.class
},
subcomponents = {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index da6bfe8..d742da7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -47,7 +47,6 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -167,9 +166,7 @@
mVibrator,
mAuthRippleController,
mResources,
- KeyguardTransitionInteractorFactory.create(
- TestScopeProvider.getTestScope().getBackgroundScope())
- .getKeyguardTransitionInteractor(),
+ mKosmos.getKeyguardTransitionInteractor(),
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
mFeatureFlags,
mPrimaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index fc34255..3f83ce3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -26,7 +26,7 @@
import com.android.systemui.display.data.repository.fakeDeviceStateRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -47,7 +47,6 @@
@SmallTest
@RunWith(JUnit4::class)
class LogContextInteractorImplTest : SysuiTestCase() {
-
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
private val kosmos = testKosmos()
@@ -64,11 +63,7 @@
LogContextInteractorImpl(
testScope.backgroundScope,
deviceStateRepository,
- KeyguardTransitionInteractorFactory.create(
- repository = keyguardTransitionRepository,
- scope = testScope.backgroundScope,
- )
- .keyguardTransitionInteractor,
+ kosmos.keyguardTransitionInteractor,
udfpsOverlayInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 368d1d9..b28d0c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -16,71 +16,56 @@
package com.android.systemui.deviceentry.domain.interactor
-import android.app.trust.TrustManager
import android.content.pm.UserInfo
import android.hardware.biometrics.BiometricFaceConstants
import android.hardware.biometrics.BiometricSourceType
-import android.os.Handler
import android.os.PowerManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.keyguard.trustManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.facePropertyRepository
import com.android.systemui.biometrics.shared.model.LockoutMode
import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig
+import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
-import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
-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.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -89,84 +74,39 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
+ val kosmos =
+ testKosmos().apply { this.faceWakeUpTriggersConfig = mock<FaceWakeUpTriggersConfig>() }
private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor
- private lateinit var testScope: TestScope
- private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
- private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
- private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository
- private lateinit var fakeUserRepository: FakeUserRepository
- private lateinit var facePropertyRepository: FakeFacePropertyRepository
- private lateinit var fakeDeviceEntryFingerprintAuthRepository:
- FakeDeviceEntryFingerprintAuthRepository
- private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
- private lateinit var powerInteractor: PowerInteractor
- private lateinit var fakeBiometricSettingsRepository: FakeBiometricSettingsRepository
+ private val testScope = kosmos.testScope
+ private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+ private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+ private val fakeUserRepository = kosmos.fakeUserRepository
+ private val facePropertyRepository = kosmos.facePropertyRepository
+ private val fakeDeviceEntryFingerprintAuthRepository =
+ kosmos.fakeDeviceEntryFingerprintAuthRepository
+ private val powerInteractor = kosmos.powerInteractor
+ private val fakeBiometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
- @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
- @Mock private lateinit var trustManager: TrustManager
+ private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+ private val faceWakeUpTriggersConfig = kosmos.faceWakeUpTriggersConfig
+ private val trustManager = kosmos.trustManager
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- val scheduler = TestCoroutineScheduler()
- val dispatcher = StandardTestDispatcher(scheduler)
- testScope = TestScope(dispatcher)
- bouncerRepository = FakeKeyguardBouncerRepository()
- faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- repository = keyguardTransitionRepository,
- )
- .keyguardTransitionInteractor
-
- fakeDeviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
- fakeUserRepository = FakeUserRepository()
fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
- facePropertyRepository = FakeFacePropertyRepository()
- fakeKeyguardRepository = FakeKeyguardRepository()
- powerInteractor = PowerInteractorFactory.create().powerInteractor
- fakeBiometricSettingsRepository = FakeBiometricSettingsRepository()
underTest =
SystemUIDeviceEntryFaceAuthInteractor(
mContext,
testScope.backgroundScope,
- dispatcher,
+ kosmos.testDispatcher,
faceAuthRepository,
- {
- PrimaryBouncerInteractor(
- bouncerRepository,
- mock(BouncerView::class.java),
- mock(Handler::class.java),
- mock(KeyguardStateController::class.java),
- mock(KeyguardSecurityModel::class.java),
- mock(PrimaryBouncerCallbackInteractor::class.java),
- mock(FalsingCollector::class.java),
- mock(DismissCallbackRegistry::class.java),
- context,
- keyguardUpdateMonitor,
- FakeTrustRepository(),
- testScope.backgroundScope,
- selectedUserInteractor,
- underTest,
- )
- },
- AlternateBouncerInteractor(
- mock(StatusBarStateController::class.java),
- mock(KeyguardStateController::class.java),
- bouncerRepository,
- FakeFingerprintPropertyRepository(),
- fakeBiometricSettingsRepository,
- FakeSystemClock(),
- keyguardUpdateMonitor,
- testScope.backgroundScope,
- ),
+ { kosmos.primaryBouncerInteractor },
+ kosmos.alternateBouncerInteractor,
keyguardTransitionInteractor,
FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
keyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
deleted file mode 100644
index ed80a86..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-package com.android.systemui.keyboard.stickykeys.data.repository
-
-import android.content.pm.UserInfo
-import android.hardware.input.InputManager
-import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.mock
-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.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class StickyKeysRepositoryImplTest : SysuiTestCase() {
-
- private val dispatcher = StandardTestDispatcher()
- private val testScope = TestScope(dispatcher)
- private val secureSettings = FakeSettings()
- private val userRepository = Kosmos().fakeUserRepository
- private lateinit var stickyKeysRepository: StickyKeysRepositoryImpl
-
- @Before
- fun setup() {
- stickyKeysRepository = StickyKeysRepositoryImpl(
- mock<InputManager>(),
- dispatcher,
- secureSettings,
- userRepository,
- mock<StickyKeysLogger>()
- )
- userRepository.setUserInfos(USER_INFOS)
- setStickyKeySettingForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
- setStickyKeySettingForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
- }
-
- @Test
- fun settingEnabledEmitsValueForCurrentUser() {
- testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-
- val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
-
- assertThat(enabled).isTrue()
- }
- }
-
- @Test
- fun settingEnabledEmitsNewValueWhenSettingChanges() {
- testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
- val enabled by collectValues(stickyKeysRepository.settingEnabled)
- runCurrent()
-
- setStickyKeySettingForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
-
- assertThat(enabled).containsExactly(true, false).inOrder()
- }
- }
-
- @Test
- fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
- testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
- val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
- runCurrent()
-
- userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
-
- assertThat(enabled).isFalse()
- }
- }
-
- private fun setStickyKeySettingForUser(enabled: Boolean, userInfo: UserInfo) {
- val newValue = if (enabled) "1" else "0"
- secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, userInfo.id)
- }
-
- private companion object {
- val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
- val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
- val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 6eebb6d..d14d72d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -68,11 +69,15 @@
@Before
fun setup() {
+ val settingsRepository = UserAwareSecureSettingsRepositoryImpl(
+ secureSettings,
+ userRepository,
+ dispatcher
+ )
val stickyKeysRepository = StickyKeysRepositoryImpl(
inputManager,
dispatcher,
- secureSettings,
- userRepository,
+ settingsRepository,
mock<StickyKeysLogger>()
)
setStickyKeySetting(enabled = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 852f9a5..cf8fe79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -5,16 +5,16 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.utils.GlobalWindowManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,13 +36,14 @@
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ResourceTrimmerTest : SysuiTestCase() {
+ val kosmos = testKosmos()
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
- private val keyguardRepository = FakeKeyguardRepository()
- private val featureFlags = FakeFeatureFlags()
- private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- private lateinit var powerInteractor: PowerInteractor
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val featureFlags = kosmos.fakeFeatureFlagsClassic
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val powerInteractor = kosmos.powerInteractor
@Mock private lateinit var globalWindowManager: GlobalWindowManager
private lateinit var resourceTrimmer: ResourceTrimmer
@@ -52,7 +53,6 @@
MockitoAnnotations.initMocks(this)
featureFlags.set(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK, true)
featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true)
- powerInteractor = PowerInteractorFactory.create().powerInteractor
keyguardRepository.setDozeAmount(0f)
keyguardRepository.setKeyguardGoingAway(false)
@@ -66,11 +66,7 @@
ResourceTrimmer(
keyguardInteractor,
powerInteractor,
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- repository = keyguardTransitionRepository,
- )
- .keyguardTransitionInteractor,
+ kosmos.keyguardTransitionInteractor,
globalWindowManager,
testScope.backgroundScope,
testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
new file mode 100644
index 0000000..c174cb8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 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.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+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.Mockito.reset
+import org.mockito.Mockito.spy
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromGoneTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromGoneTransitionInteractor
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ underTest.start()
+ }
+
+ @Test
+ fun testDoesNotTransitionToLockscreen_ifStartedButNotFinishedInGone() =
+ testScope.runTest {
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.STARTED,
+ ),
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.RUNNING,
+ ),
+ ),
+ testScope,
+ )
+ reset(keyguardTransitionRepository)
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // We're in the middle of a LOCKSCREEN -> GONE transition.
+ assertThat(keyguardTransitionRepository).noTransitionsStarted()
+ }
+
+ @Test
+ fun testTransitionsToLockscreen_ifFinishedInGone() =
+ testScope.runTest {
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ reset(keyguardTransitionRepository)
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // We're in the middle of a LOCKSCREEN -> GONE transition.
+ assertThat(keyguardTransitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 668fb64..6d8e7aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -27,17 +27,15 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.data.repository.FlingInfo
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
-import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -132,13 +130,11 @@
)
runCurrent()
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- .also {
- assertEquals(KeyguardState.LOCKSCREEN, it.from)
- assertEquals(KeyguardState.GONE, it.to)
- }
+ assertThatRepository(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
}
@Test
@@ -155,6 +151,6 @@
)
runCurrent()
- verify(transitionRepository, never()).startTransition(any())
+ assertThatRepository(transitionRepository).noTransitionsStarted()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index e75f557..62855d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -21,16 +21,15 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -42,11 +41,12 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardDismissActionInteractorTest : SysuiTestCase() {
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ val kosmos = testKosmos()
- private lateinit var dispatcher: TestDispatcher
- private lateinit var testScope: TestScope
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ private val testScope = kosmos.testScope
private lateinit var dismissInteractorWithDependencies:
KeyguardDismissInteractorFactory.WithDependencies
@@ -55,25 +55,18 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- dispatcher = StandardTestDispatcher()
- testScope = TestScope(dispatcher)
dismissInteractorWithDependencies =
KeyguardDismissInteractorFactory.create(
context = context,
testScope = testScope,
+ keyguardRepository = keyguardRepository,
)
- keyguardRepository = dismissInteractorWithDependencies.keyguardRepository
- transitionRepository = FakeKeyguardTransitionRepository()
underTest =
KeyguardDismissActionInteractor(
keyguardRepository,
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- )
- .keyguardTransitionInteractor,
+ kosmos.keyguardTransitionInteractor,
dismissInteractorWithDependencies.interactor,
testScope.backgroundScope,
)
@@ -180,7 +173,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- testScope
+ testScope,
)
assertThat(executeDismissAction).isNotNull()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
index f23dd55..3f05bfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.util.mockTopActivityClassName
import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.system.activityManagerWrapper
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.assertValuesMatch
import com.google.common.truth.Truth.assertThat
@@ -192,4 +193,38 @@
)
.inOrder()
}
+
+ @Test
+ fun testSurfaceBehindModel_fromNotificationLaunch() =
+ testScope.runTest {
+ val values by collectValues(underTest.viewParams)
+ runCurrent()
+
+ kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+ )
+ runCurrent()
+
+ values.assertValuesMatch(
+ // We should be at alpha = 0f during the animation.
+ { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
deleted file mode 100644
index a03aed0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
+++ /dev/null
@@ -1,78 +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.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.keyguard.domain.interactor
-
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
-import dagger.Lazy
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-
-open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
- private val kosmos = testKosmos()
- val testDispatcher = StandardTestDispatcher()
- var testScope = TestScope(testDispatcher)
-
- lateinit var keyguardRepository: FakeKeyguardRepository
- lateinit var transitionRepository: FakeKeyguardTransitionRepository
-
- lateinit var keyguardInteractor: KeyguardInteractor
- lateinit var communalInteractor: CommunalInteractor
- lateinit var transitionInteractor: KeyguardTransitionInteractor
-
- /**
- * Replace these lazy providers with non-null ones if you want test dependencies to use a real
- * instance of the interactor for the test.
- */
- open var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
- null
- open var fromPrimaryBouncerTransitionInteractorLazy:
- Lazy<FromPrimaryBouncerTransitionInteractor>? =
- null
-
- open fun setUp() {
- keyguardRepository = FakeKeyguardRepository()
- transitionRepository = FakeKeyguardTransitionRepository()
-
- keyguardInteractor =
- KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
-
- communalInteractor = kosmos.communalInteractor
-
- transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- repository = transitionRepository,
- keyguardInteractor = keyguardInteractor,
- scope = testScope.backgroundScope,
- fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractorLazy
- ?: Lazy { mock() },
- fromPrimaryBouncerTransitionInteractor =
- fromPrimaryBouncerTransitionInteractorLazy ?: Lazy { mock() },
- )
- .also {
- fromLockscreenTransitionInteractorLazy = it.fromLockscreenTransitionInteractor
- fromPrimaryBouncerTransitionInteractorLazy =
- it.fromPrimaryBouncerTransitionInteractor
- }
- .keyguardTransitionInteractor
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index bb61d18..4d57670 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -37,9 +37,9 @@
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -51,8 +51,6 @@
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.MutableStateFlow
@@ -254,15 +252,13 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.PRIMARY_BOUNCER,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -281,15 +277,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.OCCLUDED,
+ ownerName = "FromOccludedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -308,15 +302,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.OCCLUDED,
+ ownerName = "FromOccludedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -338,15 +330,13 @@
keyguardRepository.setDreamingWithOverlay(true)
advanceUntilIdle()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -369,15 +359,13 @@
keyguardRepository.setIsActiveDreamLockscreenHosted(true)
advanceUntilIdle()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -396,15 +384,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -423,15 +409,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -456,15 +440,13 @@
keyguardRepository.setIsActiveDreamLockscreenHosted(false)
advanceUntilIdle()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to Lockscreen should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -484,15 +466,13 @@
)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to Gone should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GONE,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -514,15 +494,13 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.PRIMARY_BOUNCER,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -547,15 +525,13 @@
)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -579,15 +555,13 @@
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.OCCLUDED,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -603,15 +577,13 @@
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ from = KeyguardState.DOZING,
+ ownerName = "FromDozingTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -667,15 +639,13 @@
keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GONE,
+ from = KeyguardState.DOZING,
+ ownerName = "FromDozingTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -699,15 +669,13 @@
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo(FromDozingTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GLANCEABLE_HUB,
+ from = KeyguardState.DOZING,
+ ownerName = FromDozingTransitionInteractor::class.simpleName,
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -726,15 +694,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -753,15 +719,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -776,15 +740,13 @@
keyguardRepository.setKeyguardShowing(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -806,15 +768,13 @@
keyguardRepository.setDreamingWithOverlay(true)
advanceUntilIdle()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -837,15 +797,13 @@
keyguardRepository.setIsActiveDreamLockscreenHosted(true)
advanceUntilIdle()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -868,15 +826,13 @@
keyguardRepository.setKeyguardShowing(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo(FromGoneTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GLANCEABLE_HUB,
+ from = KeyguardState.GONE,
+ ownerName = FromGoneTransitionInteractor::class.simpleName,
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -894,15 +850,13 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.PRIMARY_BOUNCER,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -926,15 +880,13 @@
bouncerRepository.setAlternateVisible(false)
advanceUntilIdle()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -959,15 +911,13 @@
bouncerRepository.setAlternateVisible(false)
advanceUntilIdle()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -989,15 +939,13 @@
bouncerRepository.setAlternateVisible(false)
advanceUntilIdle()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1027,16 +975,14 @@
bouncerRepository.setAlternateVisible(false)
advanceUntilIdle()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName)
- .isEqualTo(FromAlternateBouncerTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromAlternateBouncerTransitionInteractor::class.simpleName,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1056,15 +1002,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.AOD,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1084,15 +1029,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.DOZING,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1108,15 +1052,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1140,16 +1083,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName)
- .isEqualTo(FromPrimaryBouncerTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromPrimaryBouncerTransitionInteractor::class.simpleName,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1171,15 +1112,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1202,15 +1142,14 @@
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to GONE should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GONE,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1231,15 +1170,14 @@
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1268,15 +1206,14 @@
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to GLANCEABLE_HUB should occur
- assertThat(info.ownerName).isEqualTo(FromOccludedTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromOccludedTransitionInteractor::class.simpleName,
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1293,15 +1230,14 @@
bouncerRepository.setAlternateVisible(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AlternateBouncer should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1318,15 +1254,14 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AlternateBouncer should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1344,15 +1279,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1369,15 +1303,14 @@
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromDozingTransitionInteractor",
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1396,15 +1329,14 @@
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromDreamingTransitionInteractor",
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1426,15 +1358,14 @@
)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromDreamingTransitionInteractor",
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.AOD,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1450,15 +1381,14 @@
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromLockscreenTransitionInteractor",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1474,15 +1404,14 @@
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.AOD)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromAodTransitionInteractor",
+ from = KeyguardState.AOD,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1498,15 +1427,14 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.AOD)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromAodTransitionInteractor",
+ from = KeyguardState.AOD,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1532,14 +1460,13 @@
runCurrent()
// THEN a transition from LOCKSCREEN => OCCLUDED should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromLockscreenTransitionInteractor",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1559,14 +1486,13 @@
runCurrent()
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNull() // dragging should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromLockscreenTransitionInteractor",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNull() }, // dragging should be manually animated
+ )
// WHEN the user stops dragging and shade is back to expanded
clearInvocations(transitionRepository)
@@ -1575,14 +1501,13 @@
shadeRepository.setLegacyShadeExpansion(1f)
runCurrent()
- // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
- val info2 =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info2.animator).isNotNull()
+ // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1614,15 +1539,13 @@
runCurrent()
// THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName)
- .isEqualTo(FromLockscreenTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNull() // transition should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromLockscreenTransitionInteractor::class.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
+ )
// WHEN the user stops dragging and the glanceable hub opening is cancelled
clearInvocations(transitionRepository)
@@ -1634,14 +1557,13 @@
communalInteractor.setTransitionState(idleTransitionState)
runCurrent()
- // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
- val info2 =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info2.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNull() // transition should be manually animated
+ // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromLockscreenTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ )
coroutineContext.cancelChildren()
}
@@ -1672,16 +1594,13 @@
progress.value = .1f
runCurrent()
- // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNull() // transition should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
+ )
// WHEN the user stops dragging and the glanceable hub closing is cancelled
clearInvocations(transitionRepository)
@@ -1693,14 +1612,11 @@
communalInteractor.setTransitionState(idleTransitionState)
runCurrent()
- // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
- val info2 =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info2.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info2.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNull() // transition should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
coroutineContext.cancelChildren()
}
@@ -1715,16 +1631,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DOZING,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1739,16 +1652,13 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1763,16 +1673,13 @@
bouncerRepository.setAlternateVisible(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1796,16 +1703,13 @@
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1820,16 +1724,13 @@
keyguardRepository.setKeyguardGoingAway(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.GONE,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1851,31 +1752,17 @@
keyguardRepository.setDreamingWithOverlay(true)
advanceUntilIdle()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DREAMING,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
- private fun createKeyguardInteractor(): KeyguardInteractor {
- return KeyguardInteractorFactory.create(
- featureFlags = featureFlags,
- repository = keyguardRepository,
- commandQueue = commandQueue,
- bouncerRepository = bouncerRepository,
- powerInteractor = powerInteractor,
- )
- .keyguardInteractor
- }
-
private suspend fun TestScope.runTransitionAndSetWakefulness(
from: KeyguardState,
to: KeyguardState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 6eed427..a4483bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -20,76 +20,46 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.flow.MutableStateFlow
-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.initMocks
@SmallTest
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
-
- private lateinit var underTest: WindowManagerLockscreenVisibilityInteractor
-
- @Mock private lateinit var surfaceBehindInteractor: KeyguardSurfaceBehindInteractor
- @Mock
- private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
- @Mock
- private lateinit var fromPrimaryBouncerTransitionInteractor:
- FromPrimaryBouncerTransitionInteractor
-
private val lockscreenSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
private val surfaceBehindIsAnimatingFlow = MutableStateFlow(false)
- private val testScope = TestScope()
+ private val kosmos =
+ testKosmos().apply {
+ fromLockscreenTransitionInteractor = mock<FromLockscreenTransitionInteractor>()
+ fromPrimaryBouncerTransitionInteractor = mock<FromPrimaryBouncerTransitionInteractor>()
+ keyguardSurfaceBehindInteractor = mock<KeyguardSurfaceBehindInteractor>()
- private lateinit var keyguardInteractor: KeyguardInteractor
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
- private lateinit var transitionInteractor: KeyguardTransitionInteractor
+ whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
+ .thenReturn(lockscreenSurfaceVisibilityFlow)
+ whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
+ .thenReturn(primaryBouncerSurfaceVisibilityFlow)
+ whenever(keyguardSurfaceBehindInteractor.isAnimatingSurface)
+ .thenReturn(surfaceBehindIsAnimatingFlow)
+ }
- @Before
- fun setUp() {
- initMocks(this)
-
- whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
- .thenReturn(lockscreenSurfaceVisibilityFlow)
- whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
- .thenReturn(primaryBouncerSurfaceVisibilityFlow)
- whenever(surfaceBehindInteractor.isAnimatingSurface)
- .thenReturn(surfaceBehindIsAnimatingFlow)
-
- transitionRepository = FakeKeyguardTransitionRepository()
-
- transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- )
- .also { keyguardInteractor = it.keyguardInteractor }
- .keyguardTransitionInteractor
-
- underTest =
- WindowManagerLockscreenVisibilityInteractor(
- keyguardInteractor = keyguardInteractor,
- transitionInteractor = transitionInteractor,
- surfaceBehindInteractor = surfaceBehindInteractor,
- fromLockscreenTransitionInteractor,
- fromPrimaryBouncerTransitionInteractor,
- )
- }
+ private val underTest = kosmos.windowManagerLockscreenVisibilityInteractor
+ private val testScope = kosmos.testScope
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
@Test
fun surfaceBehindVisibility_switchesToCorrectFlow() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index af38523c..90943de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -46,7 +46,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
@@ -67,7 +67,6 @@
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -83,7 +82,6 @@
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@@ -208,11 +206,7 @@
KeyguardLongPressInteractor(
appContext = mContext,
scope = testScope.backgroundScope,
- transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- )
- .keyguardTransitionInteractor,
+ transitionInteractor = kosmos.keyguardTransitionInteractor,
repository = repository,
logger = UiEventLoggerFake(),
featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
new file mode 100644
index 0000000..655a551
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 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.keyguard.util
+
+import androidx.core.animation.ValueAnimator
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertAbout
+import junit.framework.Assert.assertEquals
+import kotlin.test.fail
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/** [Subject] used to make assertions about a [Mockito.spy] KeyguardTransitionRepository. */
+class KeyguardTransitionRepositorySpySubject
+private constructor(
+ failureMetadata: FailureMetadata,
+ private val repository: KeyguardTransitionRepository,
+) : Subject(failureMetadata, repository) {
+
+ /**
+ * Asserts that we started a transition to the given state, optionally checking additional
+ * parameters. If an animator param or assertion is not provided, we will not assert anything
+ * about the animator.
+ */
+ fun startedTransition(
+ ownerName: String? = null,
+ from: KeyguardState? = null,
+ to: KeyguardState,
+ modeOnCanceled: TransitionModeOnCanceled? = null,
+ ) {
+ startedTransition(ownerName, from, to, {}, modeOnCanceled)
+ }
+
+ /**
+ * Asserts that we started a transition to the given state, optionally verifying additional
+ * params.
+ */
+ fun startedTransition(
+ ownerName: String? = null,
+ from: KeyguardState? = null,
+ to: KeyguardState,
+ animator: ValueAnimator?,
+ modeOnCanceled: TransitionModeOnCanceled? = null,
+ ) {
+ startedTransition(ownerName, from, to, { assertEquals(animator, it) }, modeOnCanceled)
+ }
+
+ /**
+ * Asserts that we started a transition to the given state, optionally verifying additional
+ * params.
+ */
+ fun startedTransition(
+ ownerName: String? = null,
+ from: KeyguardState? = null,
+ to: KeyguardState,
+ animatorAssertion: (Subject) -> Unit,
+ modeOnCanceled: TransitionModeOnCanceled? = null,
+ ) {
+ withArgCaptor<TransitionInfo> { verify(repository).startTransition(capture()) }
+ .also { transitionInfo ->
+ assertEquals(to, transitionInfo.to)
+ animatorAssertion.invoke(Truth.assertThat(transitionInfo.animator))
+ from?.let { assertEquals(it, transitionInfo.from) }
+ ownerName?.let { assertEquals(it, transitionInfo.ownerName) }
+ modeOnCanceled?.let { assertEquals(it, transitionInfo.modeOnCanceled) }
+ }
+ }
+
+ /** Verifies that [KeyguardTransitionRepository.startTransition] was never called. */
+ fun noTransitionsStarted() {
+ verify(repository, never()).startTransition(any())
+ }
+
+ companion object {
+ fun assertThat(
+ repository: KeyguardTransitionRepository
+ ): KeyguardTransitionRepositorySpySubject =
+ assertAbout { failureMetadata, repository: KeyguardTransitionRepository ->
+ if (!Mockito.mockingDetails(repository).isSpy) {
+ fail(
+ "Cannot assert on a non-spy KeyguardTransitionRepository. " +
+ "Use Mockito.spy(keyguardTransitionRepository)."
+ )
+ }
+ KeyguardTransitionRepositorySpySubject(failureMetadata, repository)
+ }
+ .that(repository)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 310e0b8..f3b9102 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -30,13 +30,12 @@
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.keyguard.TestScopeProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.models.player.MediaData
@@ -53,6 +52,7 @@
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -93,6 +93,7 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner::class)
class MediaCarouselControllerTest : SysuiTestCase() {
+ val kosmos = testKosmos()
@Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel>
@Mock lateinit var panel: MediaControlPanel
@@ -115,7 +116,7 @@
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var globalSettings: GlobalSettings
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
@Captor
lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
@@ -132,7 +133,6 @@
fun setup() {
MockitoAnnotations.initMocks(this)
context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK))
- transitionRepository = FakeKeyguardTransitionRepository()
bgExecutor = FakeExecutor(clock)
mediaCarouselController =
MediaCarouselController(
@@ -152,11 +152,7 @@
debugLogger,
mediaFlags,
keyguardUpdateMonitor,
- KeyguardTransitionInteractorFactory.create(
- scope = TestScopeProvider.getTestScope().backgroundScope,
- repository = transitionRepository,
- )
- .keyguardTransitionInteractor,
+ kosmos.keyguardTransitionInteractor,
globalSettings
)
verify(configurationController).addCallback(capture(configListener))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index edba902..87d093f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -530,58 +530,6 @@
}
@Test
- fun testCommunalLocation_showsOverLockscreen() =
- testScope.runTest {
- // Device is on lock screen.
- whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-
- // UMO goes to communal even over the lock screen.
- communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
- runCurrent()
- verify(mediaCarouselController)
- .onDesiredLocationChanged(
- eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
- nullable(),
- eq(false),
- anyLong(),
- anyLong()
- )
- }
-
- @Test
- fun testCommunalLocation_showsUntilQsExpands() =
- testScope.runTest {
- // Device is on lock screen.
- whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-
- communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
- runCurrent()
- verify(mediaCarouselController)
- .onDesiredLocationChanged(
- eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
- nullable(),
- eq(false),
- anyLong(),
- anyLong()
- )
- clearInvocations(mediaCarouselController)
-
- // Start opening the shade.
- mediaHierarchyManager.qsExpansion = 0.1f
- runCurrent()
-
- // UMO goes to the shade instead.
- verify(mediaCarouselController)
- .onDesiredLocationChanged(
- eq(MediaHierarchyManager.LOCATION_QS),
- any(MediaHostState::class.java),
- eq(false),
- anyLong(),
- anyLong()
- )
- }
-
- @Test
fun testQsExpandedChanged_noQqsMedia() {
// When we are looking at QQS with active media
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
new file mode 100644
index 0000000..e044eec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.media.projection.MediaProjectionConfig
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.WindowManager
+import android.widget.Spinner
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.mock
+import junit.framework.Assert.assertEquals
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+
+ private lateinit var dialog: AlertDialog
+
+ private val flags = mock<FeatureFlagsClassic>()
+ private val onStartRecordingClicked = mock<Runnable>()
+ private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
+
+ private val mediaProjectionConfig: MediaProjectionConfig =
+ MediaProjectionConfig.createConfigForDefaultDisplay()
+ private val appName: String = "testApp"
+ private val hostUid: Int = 12345
+
+ private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
+ private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
+ private val resIdSingleAppDisabled =
+ R.string.media_projection_entry_app_permission_dialog_single_app_disabled
+
+ @Before
+ fun setUp() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+ }
+
+ @After
+ fun teardown() {
+ if (::dialog.isInitialized) {
+ dialog.dismiss()
+ }
+ }
+
+ @Test
+ fun showDialog_forceShowPartialScreenShareFalse() {
+ // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+ // overrideDisableSingleAppOption = false
+ val overrideDisableSingleAppOption = false
+ setUpAndShowDialog(overrideDisableSingleAppOption)
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val secondOptionText =
+ spinner.adapter
+ .getDropDownView(1, null, spinner)
+ .findViewById<TextView>(android.R.id.text2)
+ ?.text
+
+ // check that the first option is full screen and enabled
+ assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+
+ // check that the second option is single app and disabled
+ assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText)
+ }
+
+ @Test
+ fun showDialog_forceShowPartialScreenShareTrue() {
+ // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+ // overrideDisableSingleAppOption = true
+ val overrideDisableSingleAppOption = true
+ setUpAndShowDialog(overrideDisableSingleAppOption)
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val secondOptionText =
+ spinner.adapter
+ .getDropDownView(1, null, spinner)
+ .findViewById<TextView>(android.R.id.text1)
+ ?.text
+
+ // check that the first option is single app and enabled
+ assertEquals(context.getString(resIdSingleApp), spinner.selectedItem)
+
+ // check that the second option is full screen and enabled
+ assertEquals(context.getString(resIdFullScreen), secondOptionText)
+ }
+
+ private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
+ val delegate =
+ MediaProjectionPermissionDialogDelegate(
+ context,
+ mediaProjectionConfig,
+ {},
+ onStartRecordingClicked,
+ appName,
+ overrideDisableSingleAppOption,
+ hostUid,
+ mediaProjectionMetricsLogger
+ )
+
+ dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
+ SystemUIDialog.applyFlags(dialog)
+ SystemUIDialog.setDialogSize(dialog)
+
+ dialog.window?.addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+ )
+
+ delegate.onCreate(dialog, savedInstanceState = null)
+ dialog.show()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 9ce77e5..deecc5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -166,7 +166,7 @@
@Test
public void testLogStopFromNotificationIntent() {
- Intent stopIntent = RecordingService.getNotificationIntent(mContext);
+ Intent stopIntent = mRecordingService.getNotificationIntent(mContext);
mRecordingService.onStartCommand(stopIntent, 0, 0);
// Verify that we log the correct event
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 707a297..d757d71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -202,7 +202,7 @@
@Test
fun testSeekBarTrackingStarted() {
whenever(brightnessSliderView.value).thenReturn(42)
- val event = BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH
+ val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH
mController.onViewAttached()
mController.setMirrorControllerAndMirror(mirrorController)
@@ -220,7 +220,7 @@
@Test
fun testSeekBarTrackingStopped() {
whenever(brightnessSliderView.value).thenReturn(23)
- val event = BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH
+ val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH
mController.onViewAttached()
mController.setMirrorControllerAndMirror(mirrorController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index c772ee2..8a22f4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -63,7 +63,6 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -197,16 +196,8 @@
() -> sceneInteractor);
CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
- FakeKeyguardTransitionRepository keyguardTransitionRepository =
- new FakeKeyguardTransitionRepository();
-
KeyguardTransitionInteractor keyguardTransitionInteractor =
- new KeyguardTransitionInteractor(
- mTestScope.getBackgroundScope(),
- keyguardTransitionRepository,
- () -> keyguardInteractor,
- () -> mFromLockscreenTransitionInteractor,
- () -> mFromPrimaryBouncerTransitionInteractor);
+ mKosmos.getKeyguardTransitionInteractor();
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
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 72c52ec..f582402 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -41,7 +41,6 @@
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
@@ -50,7 +49,6 @@
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -223,18 +221,9 @@
new ConfigurationInteractor(configurationRepository),
mShadeRepository,
() -> sceneInteractor);
- CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
-
- FakeKeyguardTransitionRepository keyguardTransitionRepository =
- new FakeKeyguardTransitionRepository();
KeyguardTransitionInteractor keyguardTransitionInteractor =
- new KeyguardTransitionInteractor(
- mTestScope.getBackgroundScope(),
- keyguardTransitionRepository,
- () -> keyguardInteractor,
- () -> mFromLockscreenTransitionInteractor,
- () -> mFromPrimaryBouncerTransitionInteractor);
+ mKosmos.getKeyguardTransitionInteractor();
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 8fd9c80..fb105e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -35,9 +35,9 @@
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -133,14 +133,7 @@
shadeRepository,
{ kosmos.sceneInteractor },
)
- val keyguardTransitionInteractor =
- KeyguardTransitionInteractor(
- testScope.backgroundScope,
- keyguardTransitionRepository,
- { keyguardInteractor },
- { fromLockscreenTransitionInteractor },
- { fromPrimaryBouncerTransitionInteractor }
- )
+ val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
fromPrimaryBouncerTransitionInteractor = kosmos.fromPrimaryBouncerTransitionInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
new file mode 100644
index 0000000..98be163
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 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.statusbar.policy
+
+import android.app.IActivityManager
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.server.notification.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@DisableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+class SensitiveNotificationProtectionControllerFlagDisabledTest : SysuiTestCase() {
+ @Mock private lateinit var handler: Handler
+ @Mock private lateinit var activityManager: IActivityManager
+ @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
+ private lateinit var controller: SensitiveNotificationProtectionControllerImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ controller =
+ SensitiveNotificationProtectionControllerImpl(
+ mContext,
+ mediaProjectionManager,
+ activityManager,
+ handler,
+ FakeExecutor(FakeSystemClock())
+ )
+ }
+
+ @Test
+ fun init_noRegisterMediaProjectionManagerCallback() {
+ verifyZeroInteractions(mediaProjectionManager)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index 9919c6b..a1aff48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -17,88 +17,93 @@
package com.android.systemui.statusbar.policy
import android.app.ActivityOptions
+import android.app.IActivityManager
import android.app.Notification
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
-import android.os.Handler
+import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.server.notification.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.concurrency.mockExecutorHandler
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
-import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.mock
-import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@EnableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
- @Mock private lateinit var handler: Handler
-
+ @Mock private lateinit var activityManager: IActivityManager
@Mock private lateinit var mediaProjectionManager: MediaProjectionManager
-
@Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
-
@Mock private lateinit var listener1: Runnable
@Mock private lateinit var listener2: Runnable
@Mock private lateinit var listener3: Runnable
- @Captor
- private lateinit var mediaProjectionCallbackCaptor:
- ArgumentCaptor<MediaProjectionManager.Callback>
-
+ private lateinit var mediaProjectionCallback: MediaProjectionManager.Callback
private lateinit var controller: SensitiveNotificationProtectionControllerImpl
@Before
fun setUp() {
+ allowTestableLooperAsMainThread() // for updating exempt packages and notifying listeners
MockitoAnnotations.initMocks(this)
- mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
setShareFullScreen()
+ whenever(activityManager.bugreportWhitelistedPackages)
+ .thenReturn(listOf(BUGREPORT_PACKAGE_NAME))
- controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+ val executor = FakeExecutor(FakeSystemClock())
+
+ controller =
+ SensitiveNotificationProtectionControllerImpl(
+ mContext,
+ mediaProjectionManager,
+ activityManager,
+ mockExecutorHandler(executor),
+ executor
+ )
+
+ // Process exemption processing
+ executor.runAllReady()
// Obtain useful MediaProjectionCallback
- verify(mediaProjectionManager).addCallback(mediaProjectionCallbackCaptor.capture(), any())
+ mediaProjectionCallback = withArgCaptor {
+ verify(mediaProjectionManager).addCallback(capture(), any())
+ }
}
@Test
- fun init_flagEnabled_registerMediaProjectionManagerCallback() {
- assertNotNull(mediaProjectionCallbackCaptor.value)
- }
-
- @Test
- fun init_flagDisabled_noRegisterMediaProjectionManagerCallback() {
- mSetFlagsRule.disableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
- reset(mediaProjectionManager)
-
- controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
-
- verifyZeroInteractions(mediaProjectionManager)
+ fun init_registerMediaProjectionManagerCallback() {
+ assertNotNull(mediaProjectionCallback)
}
@Test
fun registerSensitiveStateListener_singleListener() {
controller.registerSensitiveStateListener(listener1)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verify(listener1, times(2)).run()
}
@@ -108,8 +113,8 @@
controller.registerSensitiveStateListener(listener1)
controller.registerSensitiveStateListener(listener2)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verify(listener1, times(2)).run()
verify(listener2, times(2)).run()
@@ -117,12 +122,12 @@
@Test
fun registerSensitiveStateListener_afterProjectionActive() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
controller.registerSensitiveStateListener(listener1)
verifyZeroInteractions(listener1)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verify(listener1).run()
}
@@ -131,15 +136,15 @@
fun unregisterSensitiveStateListener_singleListener() {
controller.registerSensitiveStateListener(listener1)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verify(listener1, times(2)).run()
controller.unregisterSensitiveStateListener(listener1)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verifyNoMoreInteractions(listener1)
}
@@ -150,8 +155,8 @@
controller.registerSensitiveStateListener(listener2)
controller.registerSensitiveStateListener(listener3)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verify(listener1, times(2)).run()
verify(listener2, times(2)).run()
@@ -160,8 +165,8 @@
controller.unregisterSensitiveStateListener(listener1)
controller.unregisterSensitiveStateListener(listener2)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verifyNoMoreInteractions(listener1)
verifyNoMoreInteractions(listener2)
@@ -175,24 +180,24 @@
@Test
fun isSensitiveStateActive_projectionActive_true() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
assertTrue(controller.isSensitiveStateActive)
}
@Test
fun isSensitiveStateActive_projectionInactiveAfterActive_false() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
assertFalse(controller.isSensitiveStateActive)
}
@Test
fun isSensitiveStateActive_projectionActiveAfterInactive_true() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
assertTrue(controller.isSensitiveStateActive)
}
@@ -200,7 +205,25 @@
@Test
fun isSensitiveStateActive_projectionActive_singleActivity_false() {
setShareSingleApp()
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionActive_sysuiExempt_false() {
+ // SystemUi context packge name is exempt, but in test scenarios its
+ // com.android.systemui.tests so use that instead of hardcoding
+ whenever(mediaProjectionInfo.packageName).thenReturn(mContext.packageName)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionActive_bugReportHandlerExempt_false() {
+ whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
assertFalse(controller.isSensitiveStateActive)
}
@@ -215,7 +238,7 @@
@Test
fun shouldProtectNotification_projectionActive_singleActivity_false() {
setShareSingleApp()
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
@@ -224,7 +247,7 @@
@Test
fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
@@ -233,7 +256,7 @@
@Test
fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME)
@@ -242,21 +265,43 @@
@Test
fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
assertTrue(controller.shouldProtectNotification(notificationEntry))
}
+ @Test
+ fun shouldProtectNotification_projectionActive_sysuiExempt_false() {
+ // SystemUi context packge name is exempt, but in test scenarios its
+ // com.android.systemui.tests so use that instead of hardcoding
+ whenever(mediaProjectionInfo.packageName).thenReturn(mContext.packageName)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_bugReportHandlerExempt_false() {
+ whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
private fun setShareFullScreen() {
- `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
- `when`(mediaProjectionInfo.launchCookie).thenReturn(null)
+ whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ whenever(mediaProjectionInfo.launchCookie).thenReturn(null)
}
private fun setShareSingleApp() {
- `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
- `when`(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
+ whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ whenever(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
}
private fun setupNotificationEntry(
@@ -266,10 +311,10 @@
val notificationEntry = mock(NotificationEntry::class.java)
val sbn = mock(StatusBarNotification::class.java)
val notification = mock(Notification::class.java)
- `when`(notificationEntry.sbn).thenReturn(sbn)
- `when`(sbn.packageName).thenReturn(packageName)
- `when`(sbn.notification).thenReturn(notification)
- `when`(notification.isFgsOrUij).thenReturn(isFgs)
+ whenever(notificationEntry.sbn).thenReturn(sbn)
+ whenever(sbn.packageName).thenReturn(packageName)
+ whenever(sbn.notification).thenReturn(notification)
+ whenever(notification.isFgsOrUij).thenReturn(isFgs)
return notificationEntry
}
@@ -282,5 +327,6 @@
private const val TEST_PROJECTION_PACKAGE_NAME =
"com.android.systemui.statusbar.policy.projectionpackage"
private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage"
+ private const val BUGREPORT_PACKAGE_NAME = "com.android.test.bugreporthandler"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
new file mode 100644
index 0000000..913759f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 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.util.settings.repository
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+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.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+ private val secureSettings = FakeSettings()
+ private val userRepository = Kosmos().fakeUserRepository
+ private lateinit var repository: UserAwareSecureSettingsRepository
+
+ @Before
+ fun setup() {
+ repository = UserAwareSecureSettingsRepositoryImpl(
+ secureSettings,
+ userRepository,
+ dispatcher,
+ )
+ userRepository.setUserInfos(USER_INFOS)
+ setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
+ setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+ }
+
+ @Test
+ fun settingEnabledEmitsValueForCurrentUser() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+
+ val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+
+ assertThat(enabled).isTrue()
+ }
+ }
+
+ @Test
+ fun settingEnabledEmitsNewValueWhenSettingChanges() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+ val enabled by collectValues(repository.boolSettingForActiveUser(SETTING_NAME))
+ runCurrent()
+
+ setSettingValueForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+
+ assertThat(enabled).containsExactly(true, false).inOrder()
+ }
+ }
+
+ @Test
+ fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+ val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+ runCurrent()
+
+ userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
+
+ assertThat(enabled).isFalse()
+ }
+ }
+
+ private fun setSettingValueForUser(enabled: Boolean, userInfo: UserInfo) {
+ secureSettings.putBoolForUser(SETTING_NAME, enabled, userInfo.id)
+ }
+
+ private companion object {
+ const val SETTING_NAME = "SETTING_NAME"
+ val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
+ val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
+ val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 1d428c8..d45a9a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -95,11 +95,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -107,7 +105,6 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -428,17 +425,8 @@
shadeRepository,
() -> sceneInteractor);
- FakeKeyguardTransitionRepository keyguardTransitionRepository =
- new FakeKeyguardTransitionRepository();
-
KeyguardTransitionInteractor keyguardTransitionInteractor =
- new KeyguardTransitionInteractor(
- mTestScope.getBackgroundScope(),
- keyguardTransitionRepository,
- () -> keyguardInteractor,
- () -> mFromLockscreenTransitionInteractor,
- () -> mFromPrimaryBouncerTransitionInteractor);
- CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
+ mKosmos.getKeyguardTransitionInteractor();
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
index 9059da2..20b9e84 100644
--- a/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
@@ -20,4 +20,4 @@
import com.android.systemui.kosmos.Kosmos
var Kosmos.uiEventLogger: UiEventLogger by Kosmos.Fixture { uiEventLoggerFake }
-val Kosmos.uiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
+var Kosmos.uiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt
similarity index 63%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
rename to packages/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt
index 52bbfaa..901bdcc 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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
+ * 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,
@@ -14,6 +14,9 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.model
+package com.android.keyguard.logging
-data class PasswordUiModel(val email: String)
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.scrimLogger by Kosmos.Fixture { mock<ScrimLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index 7b36a29..365d97f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -35,6 +35,7 @@
FakeFeatureFlagsClassic().apply {
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
set(Flags.NSSL_DEBUG_LINES, false)
+ set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/LightRevealScrimRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/LightRevealScrimRepositoryKosmos.kt
new file mode 100644
index 0000000..046946e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/LightRevealScrimRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.keyguard.data
+
+import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.lightRevealScrimRepository by Kosmos.Fixture { fakeLightRevealScrimRepository }
+
+var Kosmos.fakeLightRevealScrimRepository by Kosmos.Fixture { FakeLightRevealScrimRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..da5cd67
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.fromAodTransitionInteractor by
+ Kosmos.Fixture {
+ FromAodTransitionInteractor(
+ transitionRepository = fakeKeyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..25fc67a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.fromGoneTransitionInteractor by
+ Kosmos.Fixture {
+ FromGoneTransitionInteractor(
+ transitionRepository = fakeKeyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ communalInteractor = communalInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index d9882fc..3b52676 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -24,7 +24,7 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.shade.data.repository.shadeRepository
-val Kosmos.fromLockscreenTransitionInteractor by
+var Kosmos.fromLockscreenTransitionInteractor by
Kosmos.Fixture {
FromLockscreenTransitionInteractor(
transitionRepository = keyguardTransitionRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 719686e..6b76449 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -26,7 +26,7 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.user.domain.interactor.selectedUserInteractor
-val Kosmos.fromPrimaryBouncerTransitionInteractor by
+var Kosmos.fromPrimaryBouncerTransitionInteractor by
Kosmos.Fixture {
FromPrimaryBouncerTransitionInteractor(
transitionRepository = keyguardTransitionRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
index 638a6a3..c06f833 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -18,12 +18,12 @@
import android.content.applicationContext
import android.view.accessibility.accessibilityManagerWrapper
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.qs.uiEventLogger
val Kosmos.keyguardLongPressInteractor by
Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
index d756f9a61..c9c17d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
@@ -19,8 +19,9 @@
import android.content.applicationContext
import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
-val Kosmos.keyguardSurfaceBehindInteractor by
+var Kosmos.keyguardSurfaceBehindInteractor by
Kosmos.Fixture {
KeyguardSurfaceBehindInteractor(
repository = keyguardSurfaceBehindRepository,
@@ -30,5 +31,6 @@
inWindowLauncherUnlockAnimationInteractor
},
swipeToDismissInteractor = swipeToDismissInteractor,
+ notificationLaunchInteractor = notificationLaunchAnimationInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
deleted file mode 100644
index 5cf656c..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
+++ /dev/null
@@ -1,69 +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.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.keyguard.domain.interactor
-
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.util.mockito.mock
-import dagger.Lazy
-import kotlinx.coroutines.CoroutineScope
-
-/**
- * Helper to create a new KeyguardTransitionInteractor in a way that doesn't require modifying 20+
- * tests whenever we add a constructor param.
- */
-object KeyguardTransitionInteractorFactory {
- @JvmOverloads
- @JvmStatic
- fun create(
- scope: CoroutineScope,
- repository: FakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
- featureFlags: FakeFeatureFlags = FakeFeatureFlagsClassic(),
- keyguardInteractor: KeyguardInteractor =
- KeyguardInteractorFactory.create(featureFlags = featureFlags).keyguardInteractor,
- fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor> = Lazy {
- mock<FromLockscreenTransitionInteractor>()
- },
- fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor> =
- Lazy {
- mock<FromPrimaryBouncerTransitionInteractor>()
- },
- ): WithDependencies {
- return WithDependencies(
- repository = repository,
- keyguardInteractor = keyguardInteractor,
- fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
- fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
- KeyguardTransitionInteractor(
- scope = scope,
- repository = repository,
- keyguardInteractor = { keyguardInteractor },
- fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
- fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
- )
- )
- }
-
- data class WithDependencies(
- val repository: FakeKeyguardTransitionRepository,
- val keyguardInteractor: KeyguardInteractor,
- val fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor>,
- val fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor>,
- val keyguardTransitionInteractor: KeyguardTransitionInteractor,
- )
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index e4d115e..0c38fd9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -30,5 +30,6 @@
fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor },
fromPrimaryBouncerTransitionInteractor =
Lazy { fromPrimaryBouncerTransitionInteractor },
+ fromAodTransitionInteractor = Lazy { fromAodTransitionInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
new file mode 100644
index 0000000..58e0a3b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.keyguard.domain.interactor
+
+import com.android.keyguard.logging.scrimLogger
+import com.android.systemui.keyguard.data.lightRevealScrimRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.lightRevealScrimInteractor by
+ Kosmos.Fixture {
+ LightRevealScrimInteractor(
+ keyguardTransitionInteractor,
+ lightRevealScrimRepository,
+ applicationCoroutineScope,
+ scrimLogger,
+ powerInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
new file mode 100644
index 0000000..d84988d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.keyguard.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
+
+val Kosmos.windowManagerLockscreenVisibilityInteractor by
+ Kosmos.Fixture {
+ WindowManagerLockscreenVisibilityInteractor(
+ keyguardInteractor = keyguardInteractor,
+ transitionInteractor = keyguardTransitionInteractor,
+ surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
+ fromLockscreenInteractor = fromLockscreenTransitionInteractor,
+ fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+ notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 73b7c50..be559ef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -16,9 +16,9 @@
package com.android.systemui.plugins.statusbar
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.uiEventLogger
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 6332c1a..23d657d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -16,7 +16,7 @@
package com.android.systemui.qs
-import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.qs.QSFactory
@@ -24,9 +24,8 @@
val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by
Kosmos.Fixture { InstanceIdSequenceFake(0) }
-val Kosmos.uiEventLogger: UiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
val Kosmos.qsEventLogger: QsEventLoggerFake by
- Kosmos.Fixture { QsEventLoggerFake(uiEventLogger, instanceIdSequenceFake) }
+ Kosmos.Fixture { QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake) }
var Kosmos.newQSTileFactory by Kosmos.Fixture<NewQSTileFactory>()
var Kosmos.qsTileFactory by Kosmos.Fixture<QSFactory>()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
similarity index 60%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
index 52bbfaa..5638cfc 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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
+ * 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,
@@ -14,6 +14,9 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.model
+package com.android.systemui.statusbar.notification.data.repository
-data class PasswordUiModel(val email: String)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.notificationLaunchAnimationRepository by
+ Kosmos.Fixture { NotificationLaunchAnimationRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt
new file mode 100644
index 0000000..0d84bab
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.statusbar.notification.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.data.repository.notificationLaunchAnimationRepository
+
+val Kosmos.notificationLaunchAnimationInteractor by
+ Kosmos.Fixture {
+ NotificationLaunchAnimationInteractor(
+ repository = notificationLaunchAnimationRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
new file mode 100644
index 0000000..5054e29
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.util.settings
+
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+
+class FakeUserAwareSecureSettingsRepository : UserAwareSecureSettingsRepository {
+
+ private val settings = MutableStateFlow<Map<String, Boolean>>(mutableMapOf())
+
+ override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> {
+ return settings.map { it.getOrDefault(name, defaultValue) }
+ }
+
+ fun setBoolSettingForActiveUser(name: String, value: Boolean) {
+ settings.value = settings.value.toMutableMap().apply { this[name] = value }
+ }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
similarity index 62%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
index 52bbfaa..94b2bdf 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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
+ * 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,
@@ -14,6 +14,9 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.model
+package com.android.systemui.util.settings
-data class PasswordUiModel(val email: String)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.userAwareSecureSettingsRepository by
+ Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 44682e2..f902439 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -52,6 +52,13 @@
}
flag {
+ name: "fullscreen_fling_gesture"
+ namespace: "accessibility"
+ description: "When true, adds a fling gesture animation for fullscreen magnification"
+ bug: "319175022"
+}
+
+flag {
name: "pinch_zoom_zero_min_span"
namespace: "accessibility"
description: "Whether to set min span of ScaleGestureDetector to zero."
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 805f6e3..351760b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -25,7 +25,9 @@
import android.accessibilityservice.MagnificationConfig;
import android.animation.Animator;
+import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -51,6 +53,7 @@
import android.view.WindowManager;
import android.view.accessibility.MagnificationAnimationCallback;
import android.view.animation.DecelerateInterpolator;
+import android.widget.Scroller;
import com.android.internal.R;
import com.android.internal.accessibility.common.MagnificationConstants;
@@ -60,6 +63,7 @@
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.Flags;
import com.android.server.wm.WindowManagerInternal;
import java.util.ArrayList;
@@ -86,6 +90,8 @@
private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
private final Object mLock;
+ private final Supplier<Scroller> mScrollerSupplier;
+ private final Supplier<TimeAnimator> mTimeAnimatorSupplier;
private final ControllerContext mControllerCtx;
@@ -149,7 +155,13 @@
DisplayMagnification(int displayId) {
mDisplayId = displayId;
- mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId);
+ mSpecAnimationBridge =
+ new SpecAnimationBridge(
+ mControllerCtx,
+ mLock,
+ mDisplayId,
+ mScrollerSupplier,
+ mTimeAnimatorSupplier);
}
/**
@@ -406,6 +418,52 @@
}
}
+ void startFlingAnimation(
+ float xPixelsPerSecond,
+ float yPixelsPerSecond,
+ MagnificationAnimationCallback animationCallback
+ ) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG,
+ "startFlingAnimation(spec = " + xPixelsPerSecond + ", animationCallback = "
+ + animationCallback + ")");
+ }
+ if (Thread.currentThread().getId() == mMainThreadId) {
+ mSpecAnimationBridge.startFlingAnimation(
+ xPixelsPerSecond,
+ yPixelsPerSecond,
+ getMinOffsetXLocked(),
+ getMaxOffsetXLocked(),
+ getMinOffsetYLocked(),
+ getMaxOffsetYLocked(),
+ animationCallback);
+ } else {
+ final Message m =
+ PooledLambda.obtainMessage(
+ SpecAnimationBridge::startFlingAnimation,
+ mSpecAnimationBridge,
+ xPixelsPerSecond,
+ yPixelsPerSecond,
+ getMinOffsetXLocked(),
+ getMaxOffsetXLocked(),
+ getMinOffsetYLocked(),
+ getMaxOffsetYLocked(),
+ animationCallback);
+ mControllerCtx.getHandler().sendMessage(m);
+ }
+ }
+
+ void cancelFlingAnimation() {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "cancelFlingAnimation()");
+ }
+ if (Thread.currentThread().getId() == mMainThreadId) {
+ mSpecAnimationBridge.cancelFlingAnimation();
+ } else {
+ mControllerCtx.getHandler().post(mSpecAnimationBridge::cancelFlingAnimation);
+ }
+ }
+
/**
* Get the ID of the last service that changed the magnification spec.
*
@@ -759,6 +817,63 @@
sendSpecToAnimation(mCurrentMagnificationSpec, null);
}
+ @GuardedBy("mLock")
+ void startFling(float xPixelsPerSecond, float yPixelsPerSecond, int id) {
+ if (!mRegistered) {
+ return;
+ }
+ if (!isActivated()) {
+ return;
+ }
+
+ if (id != INVALID_SERVICE_ID) {
+ mIdOfLastServiceToMagnify = id;
+ }
+
+ startFlingAnimation(
+ xPixelsPerSecond,
+ yPixelsPerSecond,
+ new MagnificationAnimationCallback() {
+ @Override
+ public void onResult(boolean success) {
+ // never called
+ }
+
+ @Override
+ public void onResult(boolean success, MagnificationSpec lastSpecSent) {
+ if (DEBUG) {
+ Slog.i(
+ LOG_TAG,
+ "startFlingAnimation finished( "
+ + success
+ + " = "
+ + lastSpecSent.offsetX
+ + ", "
+ + lastSpecSent.offsetY
+ + ")");
+ }
+ synchronized (mLock) {
+ mCurrentMagnificationSpec.setTo(lastSpecSent);
+ onMagnificationChangedLocked();
+ }
+ }
+ });
+ }
+
+
+ @GuardedBy("mLock")
+ void cancelFling(int id) {
+ if (!mRegistered) {
+ return;
+ }
+
+ if (id != INVALID_SERVICE_ID) {
+ mIdOfLastServiceToMagnify = id;
+ }
+
+ cancelFlingAnimation();
+ }
+
boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
if (DEBUG) {
Slog.i(LOG_TAG,
@@ -838,7 +953,9 @@
magnificationInfoChangedCallback,
scaleProvider,
/* thumbnailSupplier= */ null,
- backgroundExecutor);
+ backgroundExecutor,
+ () -> new Scroller(context),
+ TimeAnimator::new);
}
/** Constructor for tests */
@@ -849,9 +966,13 @@
@NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
@NonNull MagnificationScaleProvider scaleProvider,
Supplier<MagnificationThumbnail> thumbnailSupplier,
- @NonNull Executor backgroundExecutor) {
+ @NonNull Executor backgroundExecutor,
+ Supplier<Scroller> scrollerSupplier,
+ Supplier<TimeAnimator> timeAnimatorSupplier) {
mControllerCtx = ctx;
mLock = lock;
+ mScrollerSupplier = scrollerSupplier;
+ mTimeAnimatorSupplier = timeAnimatorSupplier;
mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
addInfoChangedCallback(magnificationInfoChangedCallback);
@@ -1437,6 +1558,42 @@
}
/**
+ * Call after a pan ends, if the velocity has passed the threshold, to start a fling animation.
+ *
+ * @param displayId The logical display id.
+ * @param xPixelsPerSecond the velocity of the last pan gesture in the X direction, in current
+ * screen pixels per second.
+ * @param yPixelsPerSecond the velocity of the last pan gesture in the Y direction, in current
+ * screen pixels per second.
+ * @param id the ID of the service requesting the change
+ */
+ public void startFling(int displayId, float xPixelsPerSecond, float yPixelsPerSecond, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.startFling(xPixelsPerSecond, yPixelsPerSecond, id);
+ }
+ }
+
+ /**
+ * Call to cancel the fling animation if it is running. Call this on any ACTION_DOWN event.
+ *
+ * @param displayId The logical display id.
+ * @param id the ID of the service requesting the change
+ */
+ public void cancelFling(int displayId, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.cancelFling(id);
+ }
+ }
+
+ /**
* Get the ID of the last service that changed the magnification spec.
*
* @param displayId The logical display id.
@@ -1698,7 +1855,15 @@
@GuardedBy("mLock")
private boolean mEnabled = false;
- private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) {
+ private final Scroller mScroller;
+ private final TimeAnimator mScrollAnimator;
+
+ private SpecAnimationBridge(
+ ControllerContext ctx,
+ Object lock,
+ int displayId,
+ Supplier<Scroller> scrollerSupplier,
+ Supplier<TimeAnimator> timeAnimatorSupplier) {
mControllerCtx = ctx;
mLock = lock;
mDisplayId = displayId;
@@ -1709,6 +1874,37 @@
mValueAnimator.setFloatValues(0.0f, 1.0f);
mValueAnimator.addUpdateListener(this);
mValueAnimator.addListener(this);
+
+ if (Flags.fullscreenFlingGesture()) {
+ mScroller = scrollerSupplier.get();
+ mScrollAnimator = timeAnimatorSupplier.get();
+ mScrollAnimator.addListener(this);
+ mScrollAnimator.setTimeListener(
+ (animation, totalTime, deltaTime) -> {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.v(
+ LOG_TAG,
+ "onScrollAnimationUpdate: "
+ + mEnabled + " : " + totalTime);
+ }
+
+ if (mEnabled) {
+ if (!mScroller.computeScrollOffset()) {
+ animation.end();
+ return;
+ }
+
+ mEndMagnificationSpec.offsetX = mScroller.getCurrX();
+ mEndMagnificationSpec.offsetY = mScroller.getCurrY();
+ setMagnificationSpecLocked(mEndMagnificationSpec);
+ }
+ }
+ });
+ } else {
+ mScroller = null;
+ mScrollAnimator = null;
+ }
}
/**
@@ -1735,16 +1931,20 @@
}
}
- void updateSentSpecMainThread(MagnificationSpec spec,
- MagnificationAnimationCallback animationCallback) {
- if (mValueAnimator.isRunning()) {
- mValueAnimator.cancel();
- }
+ @MainThread
+ void updateSentSpecMainThread(
+ MagnificationSpec spec, MagnificationAnimationCallback animationCallback) {
+ cancelAnimations();
mAnimationCallback = animationCallback;
// If the current and sent specs don't match, update the sent spec.
synchronized (mLock) {
final boolean changed = !mSentMagnificationSpec.equals(spec);
+ if (DEBUG_SET_MAGNIFICATION_SPEC) {
+ Slog.d(
+ LOG_TAG,
+ "updateSentSpecMainThread: " + mEnabled + " : changed: " + changed);
+ }
if (changed) {
if (mAnimationCallback != null) {
animateMagnificationSpecLocked(spec);
@@ -1757,12 +1957,13 @@
}
}
+ @MainThread
private void sendEndCallbackMainThread(boolean success) {
if (mAnimationCallback != null) {
if (DEBUG) {
Slog.d(LOG_TAG, "sendEndCallbackMainThread: " + success);
}
- mAnimationCallback.onResult(success);
+ mAnimationCallback.onResult(success, mSentMagnificationSpec);
mAnimationCallback = null;
}
}
@@ -1830,6 +2031,77 @@
public void onAnimationRepeat(Animator animation) {
}
+
+ /**
+ * Call after a pan ends, if the velocity has passed the threshold, to start a fling
+ * animation.
+ */
+ @MainThread
+ public void startFlingAnimation(
+ float xPixelsPerSecond,
+ float yPixelsPerSecond,
+ float minX,
+ float maxX,
+ float minY,
+ float maxY,
+ MagnificationAnimationCallback animationCallback
+ ) {
+ if (!Flags.fullscreenFlingGesture()) {
+ return;
+ }
+ cancelAnimations();
+
+ mAnimationCallback = animationCallback;
+
+ // We use this as a temp object to send updates every animation frame, so make sure it
+ // matches the current spec before we start.
+ mEndMagnificationSpec.setTo(mSentMagnificationSpec);
+
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "startFlingAnimation: "
+ + "offsetX " + mSentMagnificationSpec.offsetX
+ + "offsetY " + mSentMagnificationSpec.offsetY
+ + "xPixelsPerSecond " + xPixelsPerSecond
+ + "yPixelsPerSecond " + yPixelsPerSecond
+ + "minX " + minX
+ + "maxX " + maxX
+ + "minY " + minY
+ + "maxY " + maxY
+ );
+ }
+
+ mScroller.fling(
+ (int) mSentMagnificationSpec.offsetX,
+ (int) mSentMagnificationSpec.offsetY,
+ (int) xPixelsPerSecond,
+ (int) yPixelsPerSecond,
+ (int) minX,
+ (int) maxX,
+ (int) minY,
+ (int) maxY);
+
+ mScrollAnimator.start();
+ }
+
+ @MainThread
+ void cancelAnimations() {
+ if (mValueAnimator.isRunning()) {
+ mValueAnimator.cancel();
+ }
+
+ cancelFlingAnimation();
+ }
+
+ @MainThread
+ void cancelFlingAnimation() {
+ if (!Flags.fullscreenFlingGesture()) {
+ return;
+ }
+ if (mScrollAnimator.isRunning()) {
+ mScrollAnimator.cancel();
+ }
+ mScroller.forceFinished(true);
+ }
}
private static class ScreenStateObserver extends BroadcastReceiver {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index baae1d93..f4ea754 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -62,6 +62,7 @@
import android.view.MotionEvent.PointerProperties;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.internal.R;
@@ -174,6 +175,10 @@
private final boolean mIsWatch;
+ @Nullable private VelocityTracker mVelocityTracker;
+ private final int mMinimumVelocity;
+ private final int mMaximumVelocity;
+
public FullScreenMagnificationGestureHandler(@UiContext Context context,
FullScreenMagnificationController fullScreenMagnificationController,
AccessibilityTraceManager trace,
@@ -184,15 +189,25 @@
@NonNull WindowMagnificationPromptController promptController,
int displayId,
FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) {
- this(context, fullScreenMagnificationController, trace, callback,
- detectSingleFingerTripleTap, detectTwoFingerTripleTap,
- detectShortcutTrigger, promptController, displayId,
- fullScreenMagnificationVibrationHelper, /* magnificationLogger= */ null);
+ this(
+ context,
+ fullScreenMagnificationController,
+ trace,
+ callback,
+ detectSingleFingerTripleTap,
+ detectTwoFingerTripleTap,
+ detectShortcutTrigger,
+ promptController,
+ displayId,
+ fullScreenMagnificationVibrationHelper,
+ /* magnificationLogger= */ null,
+ ViewConfiguration.get(context));
}
/** Constructor for tests. */
@VisibleForTesting
- FullScreenMagnificationGestureHandler(@UiContext Context context,
+ FullScreenMagnificationGestureHandler(
+ @UiContext Context context,
FullScreenMagnificationController fullScreenMagnificationController,
AccessibilityTraceManager trace,
Callback callback,
@@ -202,7 +217,8 @@
@NonNull WindowMagnificationPromptController promptController,
int displayId,
FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
- MagnificationLogger magnificationLogger) {
+ MagnificationLogger magnificationLogger,
+ ViewConfiguration viewConfiguration) {
super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap,
detectShortcutTrigger, trace, callback);
if (DEBUG_ALL) {
@@ -212,6 +228,15 @@
+ ", detectTwoFingerTripleTap = " + detectTwoFingerTripleTap
+ ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
}
+
+ if (Flags.fullscreenFlingGesture()) {
+ mMinimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
+ } else {
+ mMinimumVelocity = 0;
+ mMaximumVelocity = 0;
+ }
+
mFullScreenMagnificationController = fullScreenMagnificationController;
mMagnificationInfoChangedCallback =
new FullScreenMagnificationController.MagnificationInfoChangedCallback() {
@@ -299,6 +324,10 @@
@Override
void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (event.getActionMasked() == ACTION_DOWN) {
+ cancelFling();
+ }
+
handleEventWith(mCurrentState, event, rawEvent, policyFlags);
}
@@ -501,6 +530,7 @@
}
persistScaleAndTransitionTo(mViewportDraggingState);
} else if (action == ACTION_UP || action == ACTION_CANCEL) {
+ onPanningFinished(event);
// if feature flag is enabled, currently only true on watches
if (mIsSinglePanningEnabled) {
mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
@@ -578,6 +608,7 @@
Slog.i(mLogTag, "Panned content by scrollX: " + distanceX
+ " scrollY: " + distanceY);
}
+ onPan(second);
mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
if (mIsSinglePanningEnabled) {
@@ -973,7 +1004,7 @@
&& overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
- }
+ } // TODO(b/319537921): should there be an else here?
//Primary pointer is swiping, so transit to PanningScalingState
transitToPanningScalingStateAndClear();
} else if (mIsSinglePanningEnabled
@@ -982,7 +1013,7 @@
if (overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
- }
+ } // TODO(b/319537921): should there be an else here?
transitToSinglePanningStateAndClear();
} else if (!mIsTwoFingerCountReached) {
// If it is a two-finger gesture, do not transition to the
@@ -1742,6 +1773,71 @@
}
}
+ /** Call during MOVE events for a panning gesture. */
+ private void onPan(MotionEvent event) {
+ if (!Flags.fullscreenFlingGesture()) {
+ return;
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+ }
+
+ /**
+ * Call during UP events for a panning gesture, so we can detect a fling and play a physics-
+ * based fling animation.
+ */
+ private void onPanningFinished(MotionEvent event) {
+ if (!Flags.fullscreenFlingGesture()) {
+ return;
+ }
+
+ if (mVelocityTracker == null) {
+ Log.e(mLogTag, "onPanningFinished: mVelocityTracker is null");
+ return;
+ }
+ mVelocityTracker.addMovement(event);
+ mVelocityTracker.computeCurrentVelocity(/* units= */ 1000, mMaximumVelocity);
+
+ float xPixelsPerSecond = mVelocityTracker.getXVelocity();
+ float yPixelsPerSecond = mVelocityTracker.getYVelocity();
+
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+
+ if (DEBUG_PANNING_SCALING) {
+ Slog.v(
+ mLogTag,
+ "onPanningFinished: pixelsPerSecond: "
+ + xPixelsPerSecond
+ + ", "
+ + yPixelsPerSecond
+ + " mMinimumVelocity: "
+ + mMinimumVelocity);
+ }
+
+ if ((Math.abs(yPixelsPerSecond) > mMinimumVelocity)
+ || (Math.abs(xPixelsPerSecond) > mMinimumVelocity)) {
+ mFullScreenMagnificationController.startFling(
+ mDisplayId,
+ xPixelsPerSecond,
+ yPixelsPerSecond,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ }
+ }
+
+ private void cancelFling() {
+ if (!Flags.fullscreenFlingGesture()) {
+ return;
+ }
+
+ mFullScreenMagnificationController.cancelFling(
+ mDisplayId,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ }
+
final class SinglePanningState extends SimpleOnGestureListener implements State {
@@ -1756,6 +1852,8 @@
int action = event.getActionMasked();
switch (action) {
case ACTION_UP:
+ onPanningFinished(event);
+ // fall-through!
case ACTION_CANCEL:
mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
mOverscrollHandler.clearEdgeState();
@@ -1770,6 +1868,7 @@
if (mCurrentState != mSinglePanningState) {
return true;
}
+ onPan(second);
mFullScreenMagnificationController.offsetMagnifiedRegion(
mDisplayId,
distanceX,
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index cf414d1..3645c40 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -254,6 +254,14 @@
mEventInternal.ifPresent(event -> event.mIsCredentialRequest = isCredentialRequest);
}
+ /**
+ * Set webview_requested_credential
+ */
+ public void maybeSetWebviewRequestedCredential(boolean webviewRequestedCredential) {
+ mEventInternal.ifPresent(event ->
+ event.mWebviewRequestedCredential = webviewRequestedCredential);
+ }
+
public void maybeSetNoPresentationEventReason(@NotShownReason int reason) {
mEventInternal.ifPresent(event -> {
if (event.mCountShown == 0) {
@@ -578,7 +586,8 @@
+ " mDetectionPreference=" + event.mDetectionPreference
+ " mFieldClassificationRequestId=" + event.mFieldClassificationRequestId
+ " mAppPackageUid=" + mCallingAppUid
- + " mIsCredentialRequest=" + event.mIsCredentialRequest);
+ + " mIsCredentialRequest=" + event.mIsCredentialRequest
+ + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential);
}
// TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -618,7 +627,8 @@
event.mDetectionPreference,
event.mFieldClassificationRequestId,
mCallingAppUid,
- event.mIsCredentialRequest);
+ event.mIsCredentialRequest,
+ event.mWebviewRequestedCredential);
mEventInternal = Optional.empty();
}
@@ -653,6 +663,7 @@
@DetectionPreference int mDetectionPreference = DETECTION_PREFER_UNKNOWN;
int mFieldClassificationRequestId = -1;
boolean mIsCredentialRequest = false;
+ boolean mWebviewRequestedCredential = false;
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 049feee..83d9cdb 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -18,6 +18,7 @@
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
+import static android.service.autofill.AutofillService.WEBVIEW_REQUESTED_CREDENTIAL_KEY;
import static android.service.autofill.Dataset.PICK_REASON_NO_PCC;
import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_ONLY;
import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
@@ -5516,8 +5517,11 @@
mResponses.put(requestId, newResponse);
mClientState = newClientState != null ? newClientState : newResponse.getClientState();
+ boolean webviewRequestedCredman = newClientState != null && newClientState.getBoolean(
+ WEBVIEW_REQUESTED_CREDENTIAL_KEY, false);
List<Dataset> datasetList = newResponse.getDatasets();
+ mPresentationStatsEventLogger.maybeSetWebviewRequestedCredential(webviewRequestedCredman);
mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(sIdCounterForPcc.get());
mPresentationStatsEventLogger.maybeSetAvailableCount(datasetList, mCurrentViewId);
mFillResponseEventLogger.maybeSetDatasetsCountAfterPotentialPccFiltering(datasetList);
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 586aa8a..af0777c 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -16,6 +16,8 @@
package com.android.server.companion;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -25,8 +27,10 @@
import android.companion.DevicePresenceEvent;
import android.content.ComponentName;
import android.content.Context;
+import android.hardware.power.Mode;
import android.os.Handler;
import android.os.ParcelUuid;
+import android.os.PowerManagerInternal;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -79,6 +83,8 @@
private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final @NonNull CompanionServicesRegister mCompanionServicesRegister;
+ private final PowerManagerInternal mPowerManagerInternal;
+
@GuardedBy("mBoundCompanionApplications")
private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>>
mBoundCompanionApplications;
@@ -87,11 +93,13 @@
CompanionApplicationController(Context context, AssociationStore associationStore,
ObservableUuidStore observableUuidStore,
- CompanionDevicePresenceMonitor companionDevicePresenceMonitor) {
+ CompanionDevicePresenceMonitor companionDevicePresenceMonitor,
+ PowerManagerInternal powerManagerInternal) {
mContext = context;
mAssociationStore = associationStore;
mObservableUuidStore = observableUuidStore;
mDevicePresenceMonitor = companionDevicePresenceMonitor;
+ mPowerManagerInternal = powerManagerInternal;
mCompanionServicesRegister = new CompanionServicesRegister();
mBoundCompanionApplications = new AndroidPackageMap<>();
mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>();
@@ -364,9 +372,21 @@
boolean isPrimary = serviceConnector.isPrimary();
Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);
- // First: Only mark not BOUND for primary service.
- synchronized (mBoundCompanionApplications) {
- if (serviceConnector.isPrimary()) {
+ // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
+ if (isPrimary) {
+ final List<AssociationInfo> associations =
+ mAssociationStore.getAssociationsForPackage(userId, packageName);
+
+ for (AssociationInfo association : associations) {
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
+ break;
+ }
+ }
+
+ synchronized (mBoundCompanionApplications) {
mBoundCompanionApplications.removePackage(userId, packageName);
}
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 5019428..0054bc8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -20,7 +20,6 @@
import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
-import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
@@ -270,7 +269,8 @@
mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
mCompanionAppController = new CompanionApplicationController(
- context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor);
+ context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
+ mPowerManagerInternal);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
@@ -1128,7 +1128,9 @@
mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId);
- if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) {
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile);
mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, true);
}
}
@@ -1146,7 +1148,9 @@
mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
- if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) {
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
}
}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 797a2e6..a341b4a 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -1676,7 +1676,7 @@
}
byte[] certificateDigest = null;
try {
- certificateDigest = new Signature(certificateDigestStr).toByteArray();
+ certificateDigest = new Signature(certificateDigestStr.replace(":", "")).toByteArray();
} catch (IllegalArgumentException e) {
Slog.w(TAG, "<" + elementName + "> with invalid sha256-cert-digest in "
+ permFile + " at " + parser.getPositionDescription());
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index d110349..3adea7a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2499,7 +2499,7 @@
app.info.dataDir, null, app.info.packageName,
/*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
- false, false, bindOverrideSysprops,
+ false, false, false,
new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
} else {
regularZygote = true;
@@ -4986,19 +4986,7 @@
}
void dispatchProcessStarted(ProcessRecord app, int pid) {
- int i = mProcessObservers.beginBroadcast();
- while (i > 0) {
- i--;
- final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
- if (observer != null) {
- try {
- observer.onProcessStarted(pid, app.uid, app.info.uid,
- app.info.packageName, app.processName);
- } catch (RemoteException e) {
- }
- }
- }
- mProcessObservers.finishBroadcast();
+ // TODO(b/323959187) Add the implementation.
}
void dispatchProcessDied(int pid, int uid) {
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index e90910a..bd3c8e0 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -155,6 +155,16 @@
{ "exclude-annotation": "androidx.test.filters.FlakyTest" },
{ "exclude-annotation": "org.junit.Ignore" }
]
+ },
+ {
+ "file_patterns": ["Broadcast.*"],
+ "name": "CtsContentTestCases",
+ "options": [
+ { "include-filter": "android.content.cts.BroadcastReceiverTest" },
+ { "exclude-annotation": "androidx.test.filters.LargeTest" },
+ { "exclude-annotation": "androidx.test.filters.FlakyTest" },
+ { "exclude-annotation": "org.junit.Ignore" }
+ ]
}
]
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8aa038f..3bd1e1a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2515,6 +2515,7 @@
if (DEBUG) {
Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
}
+ mSettings.putSelectedDefaultDeviceInputMethod(null);
return defaultDeviceMethodId;
}
@@ -3179,6 +3180,26 @@
}
}
}
+
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ String ime = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+ String defaultDeviceIme = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
+ + " device input method for user " + mSettings.getUserId()
+ + " - restoring " + defaultDeviceIme);
+ }
+ SecureSettingsWrapper.putString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
+ mSettings.getUserId());
+ SecureSettingsWrapper.putString(
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ }
+ }
+
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -5339,7 +5360,7 @@
InputMethodInfoUtils.getMostApplicableDefaultIME(
mSettings.getEnabledInputMethodList());
mSettings.putSelectedDefaultDeviceInputMethod(
- newDefaultIme == null ? "" : newDefaultIme.getId());
+ newDefaultIme == null ? null : newDefaultIme.getId());
}
// Previous state was enabled.
return true;
@@ -5382,6 +5403,10 @@
@GuardedBy("ImfLock.class")
private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
+ mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
+ mDisplayIdToShowIme = INVALID_DISPLAY;
+ mSettings.putSelectedDefaultDeviceInputMethod(null);
+
InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme);
int lastSubtypeId = NOT_A_SUBTYPE_ID;
// newDefaultIme is empty when there is no candidate for the selected IME.
@@ -6565,6 +6590,7 @@
// Reset selected IME.
settings.putSelectedInputMethod(nextIme);
+ settings.putSelectedDefaultDeviceInputMethod(null);
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
}
out.println("Reset current and enabled IMEs for user #" + userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index a51002b..e444db1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -36,7 +36,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.function.Predicate;
/**
@@ -88,12 +87,6 @@
mMethodMap = methodMap;
mMethodList = methodMap.values();
mUserId = userId;
- String ime = getSelectedInputMethod();
- String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
- if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
- putSelectedInputMethod(defaultDeviceIme);
- putSelectedDefaultDeviceInputMethod(null);
- }
}
@AnyThread
diff --git a/services/core/java/com/android/server/location/altitude/AltitudeService.java b/services/core/java/com/android/server/location/altitude/AltitudeService.java
index 97bf354..289d4a2 100644
--- a/services/core/java/com/android/server/location/altitude/AltitudeService.java
+++ b/services/core/java/com/android/server/location/altitude/AltitudeService.java
@@ -71,31 +71,13 @@
@Override
public GetGeoidHeightResponse getGeoidHeight(GetGeoidHeightRequest request)
throws RemoteException {
- Location location = new Location("");
- location.setLatitude(request.latitudeDegrees);
- location.setLongitude(request.longitudeDegrees);
- location.setAltitude(0.0);
- location.setVerticalAccuracyMeters(0.0f);
-
- GetGeoidHeightResponse response = new GetGeoidHeightResponse();
try {
- mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+ return mAltitudeConverter.getGeoidHeight(mContext, request);
} catch (IOException e) {
+ GetGeoidHeightResponse response = new GetGeoidHeightResponse();
response.success = false;
return response;
}
- // The geoid height for a location with zero WGS84 altitude is equal in value to the
- // negative of the corresponding MSL altitude.
- response.geoidHeightMeters = -location.getMslAltitudeMeters();
- // The geoid height error for a location with zero vertical accuracy is equal in value to
- // the corresponding MSL altitude accuracy.
- response.geoidHeightErrorMeters = location.getMslAltitudeAccuracyMeters();
- // The expiration distance and additional error are currently set to constants used by
- // health services.
- response.expirationDistanceMeters = 10000.0;
- response.additionalGeoidHeightErrorMeters = 0.707f;
- response.success = true;
- return response;
}
@Override
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index dff02bf..e349fa3 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static android.app.Notification.COLOR_DEFAULT;
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_GROUP_SUMMARY;
@@ -23,15 +24,24 @@
import static android.app.Notification.FLAG_ONGOING_EVENT;
import android.annotation.NonNull;
+import android.app.Notification;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* NotificationManagerService helper for auto-grouping notifications.
@@ -41,6 +51,8 @@
protected static final String AUTOGROUP_KEY = "ranker_group";
+ protected static final int FLAG_INVALID = -1;
+
// Flags that all autogroup summaries have
protected static final int BASE_FLAGS =
FLAG_AUTOGROUP_SUMMARY | FLAG_GROUP_SUMMARY | FLAG_LOCAL_ONLY;
@@ -51,17 +63,22 @@
private final Callback mCallback;
private final int mAutoGroupAtCount;
+ private final Context mContext;
+ private final PackageManager mPackageManager;
// Only contains notifications that are not explicitly grouped by the app (aka no group or
// sort key).
// userId|packageName -> (keys of notifications that aren't in an explicit app group -> flags)
@GuardedBy("mUngroupedNotifications")
- private final ArrayMap<String, ArrayMap<String, Integer>> mUngroupedNotifications
+ private final ArrayMap<String, ArrayMap<String, NotificationAttributes>> mUngroupedNotifications
= new ArrayMap<>();
- public GroupHelper(int autoGroupAtCount, Callback callback) {
+ public GroupHelper(Context context, PackageManager packageManager, int autoGroupAtCount,
+ Callback callback) {
mAutoGroupAtCount = autoGroupAtCount;
mCallback = callback;
+ mContext = context;
+ mPackageManager = packageManager;
}
private String generatePackageKey(int userId, String pkg) {
@@ -70,15 +87,16 @@
@VisibleForTesting
@GuardedBy("mUngroupedNotifications")
- protected int getAutogroupSummaryFlags(@NonNull final ArrayMap<String, Integer> children) {
+ protected int getAutogroupSummaryFlags(
+ @NonNull final ArrayMap<String, NotificationAttributes> children) {
boolean allChildrenHasFlag = children.size() > 0;
int anyChildFlagSet = 0;
for (int i = 0; i < children.size(); i++) {
- if (!hasAnyFlag(children.valueAt(i), ALL_CHILDREN_FLAG)) {
+ if (!hasAnyFlag(children.valueAt(i).flags, ALL_CHILDREN_FLAG)) {
allChildrenHasFlag = false;
}
- if (hasAnyFlag(children.valueAt(i), ANY_CHILDREN_FLAGS)) {
- anyChildFlagSet |= (children.valueAt(i) & ANY_CHILDREN_FLAGS);
+ if (hasAnyFlag(children.valueAt(i).flags, ANY_CHILDREN_FLAGS)) {
+ anyChildFlagSet |= (children.valueAt(i).flags & ANY_CHILDREN_FLAGS);
}
}
return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet;
@@ -95,7 +113,6 @@
} else {
maybeUngroup(sbn, false, sbn.getUserId());
}
-
} catch (Exception e) {
Slog.e(TAG, "Failure processing new notification", e);
}
@@ -121,25 +138,47 @@
private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
int flags = 0;
List<String> notificationsToGroup = new ArrayList<>();
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
synchronized (mUngroupedNotifications) {
String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
- final ArrayMap<String, Integer> children =
+ final ArrayMap<String, NotificationAttributes> children =
mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
- children.put(sbn.getKey(), sbn.getNotification().flags);
+ NotificationAttributes attr = new NotificationAttributes(sbn.getNotification().flags,
+ sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+ children.put(sbn.getKey(), attr);
mUngroupedNotifications.put(key, children);
if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
flags = getAutogroupSummaryFlags(children);
notificationsToGroup.addAll(children.keySet());
+ childrenAttr.addAll(children.values());
}
}
if (notificationsToGroup.size() > 0) {
if (autogroupSummaryExists) {
- mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), flags);
+ NotificationAttributes attr = new NotificationAttributes(flags,
+ sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+ if (Flags.autogroupSummaryIconUpdate()) {
+ attr = updateAutobundledSummaryIcon(sbn.getPackageName(), childrenAttr, attr);
+ }
+
+ mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), attr);
} else {
- mCallback.addAutoGroupSummary(
- sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), flags);
+ Icon summaryIcon = sbn.getNotification().getSmallIcon();
+ int summaryIconColor = sbn.getNotification().color;
+ if (Flags.autogroupSummaryIconUpdate()) {
+ // Calculate the initial summary icon and icon color
+ NotificationAttributes iconAttr = getAutobundledSummaryIconAndColor(
+ sbn.getPackageName(), childrenAttr);
+ summaryIcon = iconAttr.icon;
+ summaryIconColor = iconAttr.iconColor;
+ }
+
+ NotificationAttributes attr = new NotificationAttributes(flags, summaryIcon,
+ summaryIconColor);
+ mCallback.addAutoGroupSummary(sbn.getUserId(), sbn.getPackageName(), sbn.getKey(),
+ attr);
}
for (String key : notificationsToGroup) {
mCallback.addAutoGroup(key);
@@ -154,16 +193,17 @@
* (b) if we need to remove our autogroup overlay for this notification
* (c) we need to remove the autogroup summary
*
- * And updates the internal state of un-app-grouped notifications and their flags
+ * And updates the internal state of un-app-grouped notifications and their flags.
*/
private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
boolean removeSummary = false;
- int summaryFlags = 0;
+ int summaryFlags = FLAG_INVALID;
boolean updateSummaryFlags = false;
boolean removeAutogroupOverlay = false;
+ List<NotificationAttributes> childrenAttrs = new ArrayList<>();
synchronized (mUngroupedNotifications) {
String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
- final ArrayMap<String, Integer> children =
+ final ArrayMap<String, NotificationAttributes> children =
mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
if (children.size() == 0) {
return;
@@ -173,7 +213,7 @@
if (children.containsKey(sbn.getKey())) {
// if this notification was contributing flags that aren't covered by other
// children to the summary, reevaluate flags for the summary
- int flags = children.remove(sbn.getKey());
+ int flags = children.remove(sbn.getKey()).flags;
// this
if (hasAnyFlag(flags, ANY_CHILDREN_FLAGS)) {
updateSummaryFlags = true;
@@ -188,14 +228,29 @@
// If there are no more children left to autogroup, remove the summary
if (children.size() == 0) {
removeSummary = true;
+ } else {
+ childrenAttrs.addAll(children.values());
}
}
}
+
if (removeSummary) {
mCallback.removeAutoGroupSummary(userId, sbn.getPackageName());
} else {
- if (updateSummaryFlags) {
- mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), summaryFlags);
+ NotificationAttributes attr = new NotificationAttributes(summaryFlags,
+ sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+ boolean iconUpdated = false;
+ if (Flags.autogroupSummaryIconUpdate()) {
+ NotificationAttributes newAttr = updateAutobundledSummaryIcon(sbn.getPackageName(),
+ childrenAttrs, attr);
+ if (!newAttr.equals(attr)) {
+ iconUpdated = true;
+ attr = newAttr;
+ }
+ }
+
+ if (updateSummaryFlags || iconUpdated) {
+ mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), attr);
}
}
if (removeAutogroupOverlay) {
@@ -207,17 +262,139 @@
int getNotGroupedByAppCount(int userId, String pkg) {
synchronized (mUngroupedNotifications) {
String key = generatePackageKey(userId, pkg);
- final ArrayMap<String, Integer> children =
+ final ArrayMap<String, NotificationAttributes> children =
mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
return children.size();
}
}
+ NotificationAttributes getAutobundledSummaryIconAndColor(@NonNull String packageName,
+ @NonNull List<NotificationAttributes> childrenAttr) {
+ Icon newIcon = null;
+ boolean childrenHaveSameIcon = true;
+ int newColor = Notification.COLOR_INVALID;
+ boolean childrenHaveSameColor = true;
+
+ // Both the icon drawable and the icon background color are updated according to this rule:
+ // - if all child icons are identical => use the common icon
+ // - if child icons are different: use the monochromatic app icon, if exists.
+ // Otherwise fall back to a generic icon representing a stack.
+ for (NotificationAttributes state: childrenAttr) {
+ // Check for icon
+ if (newIcon == null) {
+ newIcon = state.icon;
+ } else {
+ if (!newIcon.sameAs(state.icon)) {
+ childrenHaveSameIcon = false;
+ }
+ }
+ // Check for color
+ if (newColor == Notification.COLOR_INVALID) {
+ newColor = state.iconColor;
+ } else {
+ if (newColor != state.iconColor) {
+ childrenHaveSameColor = false;
+ }
+ }
+ }
+ if (!childrenHaveSameIcon) {
+ newIcon = getMonochromeAppIcon(packageName);
+ }
+ if (!childrenHaveSameColor) {
+ newColor = COLOR_DEFAULT;
+ }
+
+ return new NotificationAttributes(0, newIcon, newColor);
+ }
+
+ NotificationAttributes updateAutobundledSummaryIcon(@NonNull String packageName,
+ @NonNull List<NotificationAttributes> childrenAttr,
+ @NonNull NotificationAttributes oldAttr) {
+ NotificationAttributes newAttr = getAutobundledSummaryIconAndColor(packageName,
+ childrenAttr);
+ Icon newIcon = newAttr.icon;
+ int newColor = newAttr.iconColor;
+ if (newAttr.icon == null) {
+ newIcon = oldAttr.icon;
+ }
+ if (newAttr.iconColor == Notification.COLOR_INVALID) {
+ newColor = oldAttr.iconColor;
+ }
+
+ return new NotificationAttributes(oldAttr.flags, newIcon, newColor);
+ }
+
+ /**
+ * Get the monochrome app icon for an app from the adaptive launcher icon
+ * or a fallback generic icon for autogroup summaries.
+ *
+ * @param pkg packageName of the app
+ * @return a monochrome app icon or a fallback generic icon
+ */
+ @NonNull
+ Icon getMonochromeAppIcon(@NonNull final String pkg) {
+ Icon monochromeIcon = null;
+ final int fallbackIconResId = R.drawable.ic_notification_summary_auto;
+ try {
+ final Drawable appIcon = mPackageManager.getApplicationIcon(pkg);
+ if (appIcon instanceof AdaptiveIconDrawable) {
+ if (((AdaptiveIconDrawable) appIcon).getMonochrome() != null) {
+ monochromeIcon = Icon.createWithResourceAdaptiveDrawable(pkg,
+ ((AdaptiveIconDrawable) appIcon).getSourceDrawableResId(), true,
+ -2.0f * AdaptiveIconDrawable.getExtraInsetFraction());
+ }
+ }
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Failed to getApplicationIcon() in getMonochromeAppIcon()", e);
+ }
+ if (monochromeIcon != null) {
+ return monochromeIcon;
+ } else {
+ return Icon.createWithResource(mContext, fallbackIconResId);
+ }
+ }
+
+ protected static class NotificationAttributes {
+ public final int flags;
+ public final int iconColor;
+ public final Icon icon;
+
+ public NotificationAttributes(int flags, Icon icon, int iconColor) {
+ this.flags = flags;
+ this.icon = icon;
+ this.iconColor = iconColor;
+ }
+
+ public NotificationAttributes(@NonNull NotificationAttributes attr) {
+ this.flags = attr.flags;
+ this.icon = attr.icon;
+ this.iconColor = attr.iconColor;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof NotificationAttributes that)) {
+ return false;
+ }
+ return flags == that.flags && iconColor == that.iconColor && icon.sameAs(that.icon);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(flags, iconColor, icon);
+ }
+ }
+
protected interface Callback {
void addAutoGroup(String key);
void removeAutoGroup(String key);
- void addAutoGroupSummary(int userId, String pkg, String triggeringKey, int flags);
+
+ void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
+ NotificationAttributes summaryAttr);
void removeAutoGroupSummary(int user, String pkg);
- void updateAutogroupSummary(int userId, String pkg, int flags);
+ void updateAutogroupSummary(int userId, String pkg, NotificationAttributes summaryAttr);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 638382e..6fa737d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -227,6 +227,7 @@
import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
@@ -341,6 +342,7 @@
import com.android.server.job.JobSchedulerInternal;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
import com.android.server.notification.ManagedServices.UserProfiles;
import com.android.server.notification.toast.CustomToastRecord;
@@ -996,17 +998,19 @@
}
/**
- * This method will update the flags of the summary.
+ * This method will update the flags and/or the icon of the summary.
* It will set it to FLAG_ONGOING_EVENT if any of its group members
- * has the same flag. It will delete the flag otherwise
+ * has the same flag. It will delete the flag otherwise.
+ * It will update the summary notification icon if the group children's
+ * icons are different.
* @param userId user id of the autogroup summary
* @param pkg package of the autogroup summary
- * @param flags the new flags for this summary
+ * @param summaryAttr the new flags and/or icon & color for this summary
* @param isAppForeground true if the app is currently in the foreground.
*/
@GuardedBy("mNotificationLock")
- protected void updateAutobundledSummaryFlags(int userId, String pkg, int flags,
- boolean isAppForeground) {
+ protected void updateAutobundledSummaryLocked(int userId, String pkg,
+ NotificationAttributes summaryAttr, boolean isAppForeground) {
ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
if (summaries == null) {
return;
@@ -1020,8 +1024,16 @@
return;
}
int oldFlags = summary.getSbn().getNotification().flags;
- if (oldFlags != flags) {
- summary.getSbn().getNotification().flags = flags;
+
+ boolean iconUpdated =
+ !summaryAttr.icon.sameAs(summary.getSbn().getNotification().getSmallIcon())
+ || summaryAttr.iconColor != summary.getSbn().getNotification().color;
+
+ if (oldFlags != summaryAttr.flags || iconUpdated) {
+ summary.getSbn().getNotification().flags =
+ summaryAttr.flags != GroupHelper.FLAG_INVALID ? summaryAttr.flags : oldFlags;
+ summary.getSbn().getNotification().setSmallIcon(summaryAttr.icon);
+ summary.getSbn().getNotification().color = summaryAttr.iconColor;
mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
mPostNotificationTrackerFactory.newTracker(null)));
}
@@ -2873,7 +2885,8 @@
private GroupHelper getGroupHelper() {
mAutoGroupAtCount =
getContext().getResources().getInteger(R.integer.config_autoGroupAtCount);
- return new GroupHelper(mAutoGroupAtCount, new GroupHelper.Callback() {
+ return new GroupHelper(getContext(), getContext().getPackageManager(),
+ mAutoGroupAtCount, new GroupHelper.Callback() {
@Override
public void addAutoGroup(String key) {
synchronized (mNotificationLock) {
@@ -2890,8 +2903,9 @@
@Override
public void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
- int flags) {
- NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, flags);
+ NotificationAttributes summaryAttr) {
+ NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey,
+ summaryAttr.flags, summaryAttr.icon, summaryAttr.iconColor);
if (r != null) {
final boolean isAppForeground =
mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
@@ -2908,11 +2922,12 @@
}
@Override
- public void updateAutogroupSummary(int userId, String pkg, int flags) {
+ public void updateAutogroupSummary(int userId, String pkg,
+ NotificationAttributes summaryAttr) {
boolean isAppForeground = pkg != null
&& mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
synchronized (mNotificationLock) {
- updateAutobundledSummaryFlags(userId, pkg, flags, isAppForeground);
+ updateAutobundledSummaryLocked(userId, pkg, summaryAttr, isAppForeground);
}
}
});
@@ -6529,7 +6544,7 @@
// Creates a 'fake' summary for a package that has exceeded the solo-notification limit.
NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
- int flagsToSet) {
+ int flagsToSet, Icon summaryIcon, int summaryIconColor) {
NotificationRecord summaryRecord = null;
boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
synchronized (mNotificationLock) {
@@ -6555,14 +6570,15 @@
final Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
final String channelId = notificationRecord.getChannel().getId();
+
final Notification summaryNotification =
- new Notification.Builder(getContext(), channelId)
- .setSmallIcon(adjustedSbn.getNotification().getSmallIcon())
+ new Notification.Builder(getContext(), channelId)
+ .setSmallIcon(summaryIcon)
.setGroupSummary(true)
.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
.setGroup(GroupHelper.AUTOGROUP_KEY)
.setFlag(flagsToSet, true)
- .setColor(adjustedSbn.getNotification().color)
+ .setColor(summaryIconColor)
.build();
summaryNotification.extras.putAll(extras);
Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index ebaf516..4fb0c22 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -42,6 +42,7 @@
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.PackageLite;
+import android.content.pm.verify.domain.DomainSet;
import android.net.Uri;
import android.os.Build;
import android.os.Process;
@@ -155,6 +156,9 @@
@NonNull
private final ArrayList<String> mWarnings = new ArrayList<>();
+ @Nullable
+ private DomainSet mPreVerifiedDomains;
+
// New install
InstallRequest(InstallingSession params) {
mUserId = params.getUser().getIdentifier();
@@ -172,6 +176,7 @@
mIsInstallInherit = params.mIsInherit;
mSessionId = params.mSessionId;
mRequireUserAction = params.mRequireUserAction;
+ mPreVerifiedDomains = params.mPreVerifiedDomains;
}
// Install existing package as user
@@ -875,6 +880,11 @@
}
}
+ @Nullable
+ public DomainSet getPreVerifiedDomains() {
+ return mPreVerifiedDomains;
+ }
+
public void addWarning(@NonNull String warning) {
mWarnings.add(warning);
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index e970d2c..4cbd3ad 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -40,6 +40,7 @@
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.PackageLite;
+import android.content.pm.verify.domain.DomainSet;
import android.os.Environment;
import android.os.Trace;
import android.os.UserHandle;
@@ -98,6 +99,8 @@
final int mSessionId;
final int mRequireUserAction;
final boolean mApplicationEnabledSettingPersistent;
+ @Nullable
+ final DomainSet mPreVerifiedDomains;
// For move install
InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -130,12 +133,13 @@
mSessionId = -1;
mRequireUserAction = USER_ACTION_UNSPECIFIED;
mApplicationEnabledSettingPersistent = false;
+ mPreVerifiedDomains = null;
}
InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, InstallSource installSource,
UserHandle user, SigningDetails signingDetails, int installerUid,
- PackageLite packageLite, PackageManagerService pm) {
+ PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm) {
mPm = pm;
mUser = user;
mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
@@ -163,6 +167,7 @@
mSessionId = sessionId;
mRequireUserAction = sessionParams.requireUserAction;
mApplicationEnabledSettingPersistent = sessionParams.applicationEnabledSettingPersistent;
+ mPreVerifiedDomains = preVerifiedDomains;
}
@Override
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 43328fc..984a629 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1676,6 +1676,8 @@
private IntentSender buildAppMarketIntentSenderForUser(@NonNull UserHandle user) {
Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET);
+ appMarketIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
return buildIntentSenderForUser(appMarketIntent, user);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
index d40a715..4b98e34 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
@@ -19,6 +19,7 @@
import android.content.pm.PackageInstaller.PreapprovalDetails;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.verify.domain.DomainSet;
import com.android.internal.util.IndentingPrintWriter;
@@ -76,6 +77,7 @@
private final boolean mSessionFailed;
private final int mSessionErrorCode;
private final String mSessionErrorMessage;
+ private final String mPreVerifiedDomains;
PackageInstallerHistoricalSession(int sessionId, int userId, int originalInstallerUid,
String originalInstallerPackageName, InstallSource installSource, int installerUid,
@@ -86,7 +88,7 @@
String finalMessage, SessionParams params, int parentSessionId,
int[] childSessionIds, boolean sessionApplied, boolean sessionFailed,
boolean sessionReady, int sessionErrorCode, String sessionErrorMessage,
- PreapprovalDetails preapprovalDetails) {
+ PreapprovalDetails preapprovalDetails, DomainSet preVerifiedDomains) {
this.sessionId = sessionId;
this.userId = userId;
this.mOriginalInstallerUid = originalInstallerUid;
@@ -128,6 +130,11 @@
} else {
this.mPreapprovalDetails = null;
}
+ if (preVerifiedDomains != null) {
+ this.mPreVerifiedDomains = String.join(",", preVerifiedDomains.getDomains());
+ } else {
+ this.mPreVerifiedDomains = null;
+ }
}
void dump(IndentingPrintWriter pw) {
@@ -170,6 +177,7 @@
pw.printPair("mSessionErrorCode", mSessionErrorCode);
pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
+ pw.printPair("mPreVerifiedDomains", mPreVerifiedDomains);
pw.println();
pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index abea56b..6e4f199 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1026,7 +1026,7 @@
mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
- false, false, false, PackageManager.INSTALL_UNKNOWN, "");
+ false, false, false, PackageManager.INSTALL_UNKNOWN, "", null);
synchronized (mSessions) {
mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 9767338..c860b5a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -113,6 +113,7 @@
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.pm.verify.domain.DomainSet;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.Configuration;
@@ -241,6 +242,8 @@
"whitelisted-restricted-permission";
private static final String TAG_AUTO_REVOKE_PERMISSIONS_MODE =
"auto-revoke-permissions-mode";
+
+ static final String TAG_PRE_VERIFIED_DOMAINS = "preVerifiedDomains";
private static final String ATTR_SESSION_ID = "sessionId";
private static final String ATTR_USER_ID = "userId";
private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
@@ -298,6 +301,7 @@
private static final String ATTR_CHECKSUM_VALUE = "checksumValue";
private static final String ATTR_APPLICATION_ENABLED_SETTING_PERSISTENT =
"applicationEnabledSettingPersistent";
+ private static final String ATTR_DOMAIN = "domain";
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
@@ -365,6 +369,25 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
private static final long THROW_EXCEPTION_COMMIT_WITH_IMMUTABLE_PENDING_INTENT = 240618202L;
+ /**
+ * Configurable maximum number of pre-verified domains allowed to be added to the session.
+ * Flag type: {@code long}
+ * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+ */
+ private static final String PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT =
+ "pre_verified_domains_count_limit";
+ /**
+ * Configurable maximum string length of each pre-verified domain.
+ * Flag type: {@code long}
+ * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+ */
+ private static final String PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT =
+ "pre_verified_domain_length_limit";
+ /** Default max number of pre-verified domains */
+ private static final long DEFAULT_PRE_VERIFIED_DOMAINS_COUNT_LIMIT = 1000;
+ /** Default max string length of each pre-verified domain */
+ private static final long DEFAULT_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT = 256;
+
// TODO: enforce INSTALL_ALLOW_TEST
// TODO: enforce INSTALL_ALLOW_DOWNGRADE
@@ -506,6 +529,9 @@
@GuardedBy("mLock")
private int mUserActionRequirement;
+ @GuardedBy("mLock")
+ private DomainSet mPreVerifiedDomains;
+
static class FileEntry {
private final int mIndex;
private final InstallationFile mFile;
@@ -1106,7 +1132,7 @@
boolean prepared, boolean committed, boolean destroyed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int sessionErrorCode,
- String sessionErrorMessage) {
+ String sessionErrorMessage, DomainSet preVerifiedDomains) {
mCallback = callback;
mContext = context;
mPm = pm;
@@ -1168,6 +1194,7 @@
mSessionErrorMessage =
sessionErrorMessage != null ? sessionErrorMessage : "";
mStagedSession = params.isStaged ? new StagedSession() : null;
+ mPreVerifiedDomains = preVerifiedDomains;
if (isDataLoaderInstallation()) {
if (isApexSession()) {
@@ -1215,7 +1242,7 @@
mStageDirInUse, mDestroyed, mFds.size(), mBridges.size(), mFinalStatus,
mFinalMessage, params, mParentSessionId, getChildSessionIdsLocked(),
mSessionApplied, mSessionFailed, mSessionReady, mSessionErrorCode,
- mSessionErrorMessage, mPreapprovalDetails);
+ mSessionErrorMessage, mPreapprovalDetails, mPreVerifiedDomains);
}
}
@@ -3152,7 +3179,7 @@
synchronized (mLock) {
return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
- user, mSigningDetails, mInstallerUid, mPackageLite, mPm);
+ user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm);
}
}
@@ -5041,6 +5068,82 @@
return (params.installFlags & PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0;
}
+ @Override
+ public void setPreVerifiedDomains(@NonNull DomainSet preVerifiedDomains) {
+ // First check permissions
+ final boolean exemptFromPermissionChecks =
+ (mInstallerUid == Process.ROOT_UID) || (mInstallerUid == Process.SHELL_UID);
+ if (!exemptFromPermissionChecks) {
+ final Computer snapshot = mPm.snapshotComputer();
+ if (PackageManager.PERMISSION_GRANTED != snapshot.checkUidPermission(
+ Manifest.permission.ACCESS_INSTANT_APPS, mInstallerUid)) {
+ throw new SecurityException("You need android.permission.ACCESS_INSTANT_APPS "
+ + "permission to set pre-verified domains.");
+ }
+ ComponentName instantAppInstallerComponent = snapshot.getInstantAppInstallerComponent();
+ if (instantAppInstallerComponent == null) {
+ // Shouldn't happen
+ throw new IllegalStateException("Instant app installer is not available. "
+ + "Only the instant app installer can call this API.");
+ }
+ if (!instantAppInstallerComponent.getPackageName().equals(getInstallerPackageName())) {
+ throw new SecurityException("Only the instant app installer can call this API.");
+ }
+ }
+ // Then check size limits
+ final long preVerifiedDomainsCountLimit = getPreVerifiedDomainsCountLimit();
+ if (preVerifiedDomains.getDomains().size() > preVerifiedDomainsCountLimit) {
+ throw new IllegalArgumentException(
+ "The number of pre-verified domains have exceeded the maximum of "
+ + preVerifiedDomainsCountLimit);
+ }
+ final long preVerifiedDomainLengthLimit = getPreVerifiedDomainLengthLimit();
+ for (String domain : preVerifiedDomains.getDomains()) {
+ if (domain.length() > preVerifiedDomainLengthLimit) {
+ throw new IllegalArgumentException(
+ "Pre-verified domain: [" + domain + " ] exceeds maximum length allowed: "
+ + preVerifiedDomainLengthLimit);
+ }
+ }
+ // Okay to proceed
+ synchronized (mLock) {
+ mPreVerifiedDomains = preVerifiedDomains;
+ }
+ }
+
+ private static long getPreVerifiedDomainsCountLimit() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT,
+ DEFAULT_PRE_VERIFIED_DOMAINS_COUNT_LIMIT);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private static long getPreVerifiedDomainLengthLimit() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT,
+ DEFAULT_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ @Nullable
+ public DomainSet getPreVerifiedDomains() {
+ assertCallerIsOwnerOrRoot();
+ synchronized (mLock) {
+ assertPreparedAndNotCommittedOrDestroyedLocked("getPreVerifiedDomains");
+ return mPreVerifiedDomains;
+ }
+ }
+
+
void setSessionReady() {
synchronized (mLock) {
// Do not allow destroyed/failed session to change state
@@ -5267,6 +5370,9 @@
pw.printPair("mSessionErrorCode", mSessionErrorCode);
pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
+ if (mPreVerifiedDomains != null) {
+ pw.printPair("mPreVerifiedDomains", mPreVerifiedDomains);
+ }
pw.println();
pw.decreaseIndent();
@@ -5563,7 +5669,13 @@
writeByteArrayAttribute(out, ATTR_SIGNATURE, signature);
out.endTag(null, TAG_SESSION_CHECKSUM_SIGNATURE);
}
-
+ if (mPreVerifiedDomains != null) {
+ for (String domain : mPreVerifiedDomains.getDomains()) {
+ out.startTag(null, TAG_PRE_VERIFIED_DOMAINS);
+ writeStringAttribute(out, ATTR_DOMAIN, domain);
+ out.endTag(null, TAG_PRE_VERIFIED_DOMAINS);
+ }
+ }
}
out.endTag(null, TAG_SESSION);
@@ -5690,6 +5802,7 @@
List<InstallationFile> files = new ArrayList<>();
ArrayMap<String, List<Checksum>> checksums = new ArrayMap<>();
ArrayMap<String, byte[]> signatures = new ArrayMap<>();
+ ArraySet<String> preVerifiedDomainSet = new ArraySet<>();
int outerDepth = in.getDepth();
int type;
while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -5743,6 +5856,9 @@
final byte[] signature = readByteArrayAttribute(in, ATTR_SIGNATURE);
signatures.put(fileName1, signature);
break;
+ case TAG_PRE_VERIFIED_DOMAINS:
+ preVerifiedDomainSet.add(readStringAttribute(in, ATTR_DOMAIN));
+ break;
}
}
@@ -5786,6 +5902,9 @@
}
}
+ DomainSet preVerifiedDomains =
+ preVerifiedDomainSet.isEmpty() ? null : new DomainSet(preVerifiedDomainSet);
+
InstallSource installSource = InstallSource.create(installInitiatingPackageName,
installOriginatingPackageName, installerPackageName, installPackageUid,
updateOwnerPackageName, installerAttributionTag, params.packageSource);
@@ -5794,6 +5913,6 @@
installerUid, installSource, params, createdMillis, committedMillis, stageDir,
stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
- sessionErrorCode, sessionErrorMessage);
+ sessionErrorCode, sessionErrorMessage, preVerifiedDomains);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 81f9d1b..e329f09 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -107,6 +107,7 @@
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.IntArray;
import android.util.Pair;
import android.util.PrintWriterPrinter;
@@ -270,6 +271,10 @@
return runGetInstallLocation();
case "install-add-session":
return runInstallAddSession();
+ case "install-set-pre-verified-domains":
+ return runInstallSetPreVerifiedDomains();
+ case "install-get-pre-verified-domains":
+ return runInstallGetPreVerifiedDomains();
case "move-package":
return runMovePackage();
case "move-primary-storage":
@@ -1808,6 +1813,41 @@
true /*logSuccess*/);
}
+ private int runInstallSetPreVerifiedDomains() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final int sessionId = Integer.parseInt(getNextArg());
+ final String preVerifiedDomainsStr = getNextArg();
+ final String[] preVerifiedDomains = preVerifiedDomainsStr.split(",");
+ PackageInstaller.Session session = null;
+ try {
+ session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(sessionId));
+ session.setPreVerifiedDomains(new ArraySet<>(preVerifiedDomains));
+ } finally {
+ IoUtils.closeQuietly(session);
+ }
+ return 0;
+ }
+
+ private int runInstallGetPreVerifiedDomains() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final int sessionId = Integer.parseInt(getNextArg());
+ PackageInstaller.Session session = null;
+ try {
+ session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(sessionId));
+ Set<String> preVerifiedDomains = session.getPreVerifiedDomains();
+ if (preVerifiedDomains.isEmpty()) {
+ pw.println("The session doesn't have any pre-verified domains specified.");
+ } else {
+ pw.println(String.join(",", preVerifiedDomains));
+ }
+ } finally {
+ IoUtils.closeQuietly(session);
+ }
+ return 0;
+ }
+
private int runInstallRemove() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
@@ -4934,6 +4974,13 @@
pw.println(" install-add-session MULTI_PACKAGE_SESSION_ID CHILD_SESSION_IDs");
pw.println(" Add one or more session IDs to a multi-package session.");
pw.println("");
+ pw.println(" install-set-pre-verified-domains SESSION_ID PRE_VERIFIED_DOMAIN... ");
+ pw.println(" Specify a comma separated list of pre-verified domains for a session.");
+ pw.println("");
+ pw.println(" install-get-pre-verified-domains SESSION_ID");
+ pw.println(" List all the pre-verified domains that are specified in a session.");
+ pw.println(" The result list is comma separated.");
+ pw.println("");
pw.println(" install-commit SESSION_ID");
pw.println(" Commit the given active install session, installing the app.");
pw.println("");
diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
index cc26c9b..9159851 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -538,9 +538,10 @@
} else {
if (fileInfo.mUserId != userId) {
// This should be impossible: private app files are always user-specific and
- // can't be accessed from different users.
- throw new IllegalArgumentException("Cannot change userId for '" + path
- + "' from " + fileInfo.mUserId + " to " + userId);
+ // can't be accessed from different users. But it does very occasionally happen
+ // (b/323665257). Ignore such cases - we shouldn't record data from a different
+ // user.
+ return false;
}
// Changing file type (i.e. loading the same file in different ways is possible if
// unlikely. We allow it but ignore it.
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 6f75439..35717af 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -691,16 +691,26 @@
TextUtils.formatSimple("Actual total duration (%d) should be greater than 0",
workDuration.getActualTotalDurationNanos()));
}
- if (workDuration.getActualCpuDurationNanos() <= 0) {
+ if (workDuration.getActualCpuDurationNanos() < 0) {
throw new IllegalArgumentException(
- TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0",
+ TextUtils.formatSimple(
+ "Actual CPU duration (%d) should be greater than or equal to 0",
workDuration.getActualCpuDurationNanos()));
}
if (workDuration.getActualGpuDurationNanos() < 0) {
throw new IllegalArgumentException(
- TextUtils.formatSimple("Actual GPU duration (%d) should be non negative",
+ TextUtils.formatSimple(
+ "Actual GPU duration (%d) should greater than or equal to 0",
workDuration.getActualGpuDurationNanos()));
}
+ if (workDuration.getActualCpuDurationNanos()
+ + workDuration.getActualGpuDurationNanos() <= 0) {
+ throw new IllegalArgumentException(
+ TextUtils.formatSimple(
+ "The actual CPU duration (%d) and the actual GPU duration (%d)"
+ + " should not both be 0", workDuration.getActualCpuDurationNanos(),
+ workDuration.getActualGpuDurationNanos()));
+ }
}
private void onProcStateChanged(boolean updateAllowed) {
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 6546646..b2e01c5 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -21,3 +21,10 @@
bug: "311793616"
is_fixed_read_only: true
}
+
+flag {
+ name: "streamlined_connectivity_battery_stats"
+ namespace: "backstage_power"
+ description: "Feature flag for streamlined connectivity battery stats"
+ bug: "323970018"
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b3983e7..57448cb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9275,7 +9275,8 @@
return !fromWin.isFocused();
}
- private void moveFocusToActivity(@NonNull ActivityRecord activity) {
+ @VisibleForTesting
+ void moveFocusToActivity(@NonNull ActivityRecord activity) {
moveDisplayToTopInternal(activity.getDisplayId());
handleTaskFocusChange(activity.getTask(), activity);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f5806c0..68dade0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3020,8 +3020,10 @@
return false;
}
if (doAnimation) {
- mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
- if (!isAnimating(TRANSITION | PARENTS)) {
+ // If a hide animation is applied, then let onAnimationFinished
+ // -> checkPolicyVisibilityChange hide the window. Otherwise make doAnimation false
+ // to commit invisible immediately.
+ if (!mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false /* isEntrance */)) {
doAnimation = false;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 09c4f7c..6428591 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -499,10 +499,6 @@
}
void applyEnterAnimationLocked() {
- if (mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow()) {
- // It's unnecessary to play enter animation below starting window.
- return;
- }
final int transit;
if (mEnterAnimationPending) {
mEnterAnimationPending = false;
@@ -513,8 +509,10 @@
// We don't apply animation for application main window here since this window type
// should be controlled by ActivityRecord in general. Wallpaper is also excluded because
- // WallpaperController should handle it.
- if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper) {
+ // WallpaperController should handle it. Also skip play enter animation for the window
+ // below starting window.
+ if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper
+ && !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) {
applyAnimationLocked(transit, true);
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 3dcf42d..b6f7eb3 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -103,14 +103,16 @@
flattenedPrimaryProviders.add(cn.flattenToString());
}
+ final boolean isShowAllOptionsRequested = false;
mPendingIntent = mCredentialManagerUi.createPendingIntent(
RequestInfo.newCreateRequestInfo(
mRequestId, mClientRequest,
mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
- /*defaultProviderId=*/flattenedPrimaryProviders),
- providerDataList, /*isRequestForAllOptions=*/ false);
+ /*defaultProviderId=*/flattenedPrimaryProviders,
+ isShowAllOptionsRequested),
+ providerDataList, /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
mClientCallback.onPendingIntent(mPendingIntent);
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 1a9a0e6..9e362b3 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -111,13 +111,15 @@
}
cancelExistingPendingIntent();
+ final boolean isShowAllOptionsRequested = true;
mPendingIntent = mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
- Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+ Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+ isShowAllOptionsRequested),
/*providerDataList=*/ null,
- /*isRequestForAllOptions=*/ true);
+ /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
for (ProviderData providerData : providerDataList) {
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index b33f531..4068d7b 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -102,15 +102,17 @@
Binder.withCleanCallingIdentity(() -> {
try {
cancelExistingPendingIntent();
+ final boolean isShowAllOptionsRequested = false;
mPendingIntent = mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext,
mClientAppInfo.getPackageName(),
Manifest.permission
- .CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+ .CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+ isShowAllOptionsRequested),
providerDataList,
- /*isRequestForAllOptions=*/ false);
+ /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
mClientCallback.onPendingIntent(mPendingIntent);
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index 30af567..6b313fd 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -187,12 +187,14 @@
}
}
if (!providerDataList.isEmpty()) {
+ final boolean isShowAllOptionsRequested = false;
return mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
- Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
- providerDataList, /*isRequestForAllOptions=*/ false);
+ Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+ isShowAllOptionsRequested),
+ providerDataList, /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
} else {
return null;
}
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 50e426c..68038fa 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -49,7 +49,6 @@
import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
import com.android.server.wm.ActivityTaskManagerInternal;
-import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@@ -314,7 +313,7 @@
Log.w(LOG_TAG, "Couldn't get ArtManagerLocal");
return;
}
- aml.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+ aml.setBatchDexoptStartCallback(Runnable::run,
(snapshot, reason, defaultPackages, builder, passedSignal) -> {
traceOnDex2oatStart();
});
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index 811b086..7aa2ff5 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -22,7 +22,9 @@
import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED
import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainSet
import android.os.Parcel
+import android.os.Process
import android.platform.test.annotations.Presubmit
import android.util.AtomicFile
import android.util.Slog
@@ -173,7 +175,7 @@
/* stagingManager */ null,
/* sessionId */ sessionId,
/* userId */ 456,
- /* installerUid */ -1,
+ /* installerUid */ Process.myUid(),
/* installSource */ installSource,
/* sessionParams */ params,
/* createdMillis */ 0L,
@@ -183,8 +185,8 @@
/* files */ null,
/* checksums */ null,
/* prepared */ true,
- /* committed */ true,
- /* destroyed */ staged,
+ /* committed */ false,
+ /* destroyed */ false,
/* sealed */ false, // Setting to true would trigger some PM logic.
/* childSessionIds */ childSessionIds.toIntArray(),
/* parentSessionId */ parentSessionId,
@@ -192,7 +194,8 @@
/* isFailed */ false,
/* isApplied */ false,
/* stagedSessionErrorCode */ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
- /* stagedSessionErrorMessage */ "some error"
+ /* stagedSessionErrorMessage */ "some error",
+ /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar"))
)
}
@@ -332,6 +335,7 @@
assertThat(expected.parentSessionId).isEqualTo(actual.parentSessionId)
assertThat(expected.childSessionIds).asList()
.containsExactlyElementsIn(actual.childSessionIds.toList())
+ assertThat(expected.preVerifiedDomains).isEqualTo(actual.preVerifiedDomains)
}
private fun assertInstallSourcesEquivalent(expected: InstallSource, actual: InstallSource) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index e7aaed4..75409d9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -236,10 +236,6 @@
}).when(mAms).registerUidObserver(any(), anyInt(),
eq(ActivityManager.PROCESS_STATE_TOP), any());
- mConstants.TIMEOUT = 200;
- mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
- mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
-
final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
public void addBroadcastToHistoryLocked(BroadcastRecord original) {
// Ignored
@@ -259,6 +255,12 @@
mBroadcastQueues[0] = mQueue;
mQueue.start(mContext.getContentResolver());
+
+ // Set the constants after invoking BroadcastQueue.start() to ensure they don't
+ // get overridden by the defaults.
+ mConstants.TIMEOUT = 200;
+ mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
+ mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
}
@After
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index d2547a3..6f9b8df 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -755,7 +755,8 @@
/* isFailed */ false,
/* isApplied */false,
/* stagedSessionErrorCode */ PackageManager.INSTALL_UNKNOWN,
- /* stagedSessionErrorMessage */ "no error");
+ /* stagedSessionErrorMessage */ "no error",
+ /* preVerifiedDomains */ null);
StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
doReturn(packageName).when(stagedSession).getPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 52726ca..b224773 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -42,6 +42,7 @@
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import android.accessibilityservice.MagnificationConfig;
+import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -52,11 +53,15 @@
import android.hardware.display.DisplayManagerInternal;
import android.os.Looper;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.view.DisplayInfo;
import android.view.MagnificationSpec;
import android.view.accessibility.MagnificationAnimationCallback;
+import android.widget.Scroller;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -65,6 +70,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.Flags;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
@@ -74,6 +80,7 @@
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -104,6 +111,9 @@
static final int INVALID_DISPLAY = 2;
private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
final FullScreenMagnificationController.ControllerContext mMockControllerCtx =
mock(FullScreenMagnificationController.ControllerContext.class);
final Context mMockContext = mock(Context.class);
@@ -118,6 +128,7 @@
private MagnificationScaleProvider mScaleProvider;
private MockContentResolver mResolver;
private final MagnificationThumbnail mMockThumbnail = mock(MagnificationThumbnail.class);
+ private final Scroller mMockScroller = mock(Scroller.class);
private final ArgumentCaptor<MagnificationConfig> mConfigCaptor = ArgumentCaptor.forClass(
MagnificationConfig.class);
@@ -126,6 +137,8 @@
ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
ValueAnimator.AnimatorListener mStateListener;
+ private final TimeAnimator mMockTimeAnimator = mock(TimeAnimator.class);
+
FullScreenMagnificationController mFullScreenMagnificationController;
public DisplayManagerInternal mDisplayManagerInternalMock = mock(DisplayManagerInternal.class);
@@ -134,7 +147,8 @@
@Before
public void setUp() {
- Looper looper = InstrumentationRegistry.getContext().getMainLooper();
+ Context realContext = InstrumentationRegistry.getContext();
+ Looper looper = realContext.getMainLooper();
// Pretending ID of the Thread associated with looper as main thread ID in controller
when(mMockContext.getMainLooper()).thenReturn(looper);
when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
@@ -168,7 +182,9 @@
mRequestObserver,
mScaleProvider,
() -> mMockThumbnail,
- ConcurrentUtils.DIRECT_EXECUTOR);
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ () -> mMockScroller,
+ () -> mMockTimeAnimator);
}
@After
@@ -428,7 +444,7 @@
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
mStateListener.onAnimationEnd(mMockValueAnimator);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
- verify(mAnimationCallback).onResult(true);
+ verify(mAnimationCallback).onResult(eq(true), any());
}
@Test
@@ -451,7 +467,7 @@
mMessageCapturingHandler.sendAllMessages();
verify(mMockValueAnimator, never()).start();
- verify(mAnimationCallback).onResult(true);
+ verify(mAnimationCallback).onResult(eq(true), any());
}
@Test
@@ -653,6 +669,85 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testStartFling_whileMagnifying_flings() throws InterruptedException {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ startFling_whileMagnifying_flings(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void startFling_whileMagnifying_flings(int displayId) throws InterruptedException {
+ register(displayId);
+ PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ float scale = 2.0f;
+ // First zoom in
+ assertTrue(mFullScreenMagnificationController
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1));
+ mMessageCapturingHandler.sendAllMessages();
+
+ PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+ mFullScreenMagnificationController.startFling(displayId,
+ /* xPixelsPerSecond= */ 400f,
+ /* yPixelsPerSecond= */ 100f,
+ SERVICE_ID_1
+ );
+ mMessageCapturingHandler.sendAllMessages();
+
+ verify(mMockTimeAnimator).start();
+ verify(mMockScroller).fling(
+ /* startX= */ eq((int) newOffsets.x / 2),
+ /* startY= */ eq((int) newOffsets.y / 2),
+ /* velocityX= */ eq(400),
+ /* velocityY= */ eq(100),
+ /* minX= */ anyInt(),
+ /* minY= */ anyInt(),
+ /* maxX= */ anyInt(),
+ /* maxY= */ anyInt()
+ );
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testStopFling_whileMagnifyingAndFlinging_stops() throws InterruptedException {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ stopFling_whileMagnifyingAndFlinging_stops(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void stopFling_whileMagnifyingAndFlinging_stops(int displayId)
+ throws InterruptedException {
+ register(displayId);
+ PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ float scale = 2.0f;
+ PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
+ // First zoom in
+ assertTrue(mFullScreenMagnificationController
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1));
+ mMessageCapturingHandler.sendAllMessages();
+
+ mFullScreenMagnificationController.startFling(displayId,
+ /* xPixelsPerSecond= */ 400f,
+ /* yPixelsPerSecond= */ 100f,
+ SERVICE_ID_1
+ );
+ mMessageCapturingHandler.sendAllMessages();
+
+ when(mMockTimeAnimator.isRunning()).thenReturn(true);
+
+ mFullScreenMagnificationController.cancelFling(displayId, SERVICE_ID_1);
+ mMessageCapturingHandler.sendAllMessages();
+
+ verify(mMockTimeAnimator).cancel();
+ // Can't verify forceFinished() because it's final
+// verify(mMockScroller).forceFinished(eq(true));
+ }
+
+ @Test
public void testGetIdOfLastServiceToChange_returnsCorrectValue() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
getIdOfLastServiceToChange_returnsCorrectValue(i);
@@ -736,7 +831,7 @@
verify(mRequestObserver, never()).onFullScreenMagnificationChanged(eq(displayId),
any(Region.class), any(MagnificationConfig.class));
- verify(mAnimationCallback).onResult(true);
+ verify(mAnimationCallback).onResult(eq(true), any());
}
@Test
@@ -772,7 +867,7 @@
mMessageCapturingHandler.sendAllMessages();
// Verify expected actions.
- verify(mAnimationCallback).onResult(false);
+ verify(mAnimationCallback).onResult(eq(false), any());
verify(mMockValueAnimator).start();
verify(mMockValueAnimator).cancel();
@@ -782,7 +877,7 @@
mStateListener.onAnimationEnd(mMockValueAnimator);
checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
- verify(lastAnimationCallback).onResult(true);
+ verify(lastAnimationCallback).onResult(eq(true), any());
}
@Test
@@ -1379,6 +1474,8 @@
private void resetMockWindowManager() {
Mockito.reset(mMockWindowManager);
Mockito.reset(mMockThumbnail);
+ Mockito.reset(mMockScroller);
+ Mockito.reset(mMockTimeAnimator);
initMockWindowManager();
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 71d64cf..8c0d44c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -31,6 +31,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
+import static org.mockito.AdditionalMatchers.gt;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
@@ -40,10 +41,13 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.pm.PackageManager;
@@ -65,6 +69,7 @@
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import android.widget.Scroller;
import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
@@ -79,6 +84,8 @@
import com.android.server.testutils.TestHandler;
import com.android.server.wm.WindowManagerInternal;
+import com.google.common.truth.Truth;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
@@ -186,6 +193,8 @@
@Rule
public final TestableContext mContext = new TestableContext(getInstrumentation().getContext());
+ private final Scroller mMockScroller = spy(new Scroller(mContext));
+
private OffsettableClock mClock;
private FullScreenMagnificationGestureHandler mMgh;
private TestHandler mHandler;
@@ -218,18 +227,21 @@
Settings.Secure.putFloatForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
UserHandle.USER_SYSTEM);
- mFullScreenMagnificationController = new FullScreenMagnificationController(
- mockController,
- new Object(),
- mMagnificationInfoChangedCallback,
- new MagnificationScaleProvider(mContext),
- () -> null,
- ConcurrentUtils.DIRECT_EXECUTOR) {
- @Override
- public boolean magnificationRegionContains(int displayId, float x, float y) {
- return true;
- }
- };
+ mFullScreenMagnificationController =
+ new FullScreenMagnificationController(
+ mockController,
+ new Object(),
+ mMagnificationInfoChangedCallback,
+ new MagnificationScaleProvider(mContext),
+ () -> null,
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ () -> mMockScroller,
+ TimeAnimator::new) {
+ @Override
+ public boolean magnificationRegionContains(int displayId, float x, float y) {
+ return true;
+ }
+ };
doAnswer((Answer<Void>) invocationOnMock -> {
Object[] args = invocationOnMock.getArguments();
@@ -263,11 +275,20 @@
@NonNull
private FullScreenMagnificationGestureHandler newInstance(boolean detectSingleFingerTripleTap,
boolean detectTwoFingerTripleTap, boolean detectShortcutTrigger) {
- FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler(
- mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback,
- detectSingleFingerTripleTap, detectTwoFingerTripleTap, detectShortcutTrigger,
- mWindowMagnificationPromptController, DISPLAY_0,
- mMockFullScreenMagnificationVibrationHelper, mMockMagnificationLogger);
+ FullScreenMagnificationGestureHandler h =
+ new FullScreenMagnificationGestureHandler(
+ mContext,
+ mFullScreenMagnificationController,
+ mMockTraceManager,
+ mMockCallback,
+ detectSingleFingerTripleTap,
+ detectTwoFingerTripleTap,
+ detectShortcutTrigger,
+ mWindowMagnificationPromptController,
+ DISPLAY_0,
+ mMockFullScreenMagnificationVibrationHelper,
+ mMockMagnificationLogger,
+ ViewConfiguration.get(mContext));
if (isWatch()) {
h.setSinglePanningEnabled(true);
} else {
@@ -724,7 +745,7 @@
//The minimum movement to transit to panningState.
final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
pointer2.offset(sWipeMinDistance + 1, 0);
- send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
fastForward(ViewConfiguration.getTapTimeout());
assertIn(STATE_PANNING);
@@ -743,7 +764,7 @@
//The minimum movement to transit to panningState.
final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
pointer2.offset(sWipeMinDistance + 1, 0);
- send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
fastForward(ViewConfiguration.getTapTimeout());
assertIn(STATE_PANNING);
@@ -762,7 +783,7 @@
//The minimum movement to transit to panningState.
final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
pointer2.offset(sWipeMinDistance + 1, 0);
- send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
assertIn(STATE_PANNING);
returnToNormalFrom(STATE_PANNING);
@@ -780,7 +801,7 @@
//The minimum movement to transit to panningState.
final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
pointer2.offset(sWipeMinDistance + 1, 0);
- send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
assertIn(STATE_PANNING);
returnToNormalFrom(STATE_PANNING);
@@ -972,6 +993,198 @@
}
@Test
+ public void singleFinger_testScrollAfterMagnified_startsFling() {
+ assumeTrue(mMgh.mIsSinglePanningEnabled);
+ goFromStateIdleTo(STATE_ACTIVATED);
+
+ swipeAndHold();
+ fastForward(20);
+ swipe(DEFAULT_POINT, new PointF(DEFAULT_X * 2, DEFAULT_Y * 2), /* durationMs= */ 20);
+
+ verify(mMockScroller).fling(
+ /* startX= */ anyInt(),
+ /* startY= */ anyInt(),
+ // The system fling velocity is configurable and hard to test across devices, so as
+ // long as there is some fling velocity, we are happy.
+ /* velocityX= */ gt(1000),
+ /* velocityY= */ gt(1000),
+ /* minX= */ anyInt(),
+ /* minY= */ anyInt(),
+ /* maxX= */ anyInt(),
+ /* maxY= */ anyInt()
+ );
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testTwoFingerPanDiagonalAfterMagnified_doesNotFlingXY()
+ throws InterruptedException {
+ goFromStateIdleTo(STATE_ACTIVATED);
+ PointF pointer1 = DEFAULT_POINT;
+ PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[]{pointer1, pointer2}, 1));
+
+ // first move triggers the panning state
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0));
+
+ // second move actually pans
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0));
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0));
+
+ assertIn(STATE_PANNING);
+ mHandler.timeAdvance();
+ returnToNormalFrom(STATE_PANNING);
+
+ mHandler.timeAdvance();
+
+ verifyNoMoreInteractions(mMockScroller);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testTwoFingerPanDiagonalAfterMagnified_startsFlingXY()
+ throws InterruptedException {
+ goFromStateIdleTo(STATE_ACTIVATED);
+ PointF pointer1 = DEFAULT_POINT;
+ PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+
+ // first move triggers the panning state
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ // second move actually pans
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ assertIn(STATE_PANNING);
+ mHandler.timeAdvance();
+ returnToNormalFrom(STATE_PANNING);
+
+ mHandler.timeAdvance();
+
+ verify(mMockScroller).fling(
+ /* startX= */ anyInt(),
+ /* startY= */ anyInt(),
+ // The system fling velocity is configurable and hard to test across devices, so as
+ // long as there is some fling velocity, we are happy.
+ /* velocityX= */ gt(1000),
+ /* velocityY= */ gt(1000),
+ /* minX= */ anyInt(),
+ /* minY= */ anyInt(),
+ /* maxX= */ anyInt(),
+ /* maxY= */ anyInt()
+ );
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testTwoFingerPanRightAfterMagnified_startsFlingXOnly()
+ throws InterruptedException {
+ goFromStateIdleTo(STATE_ACTIVATED);
+ PointF pointer1 = DEFAULT_POINT;
+ PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+
+ // first move triggers the panning state
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ // second move actually pans
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ assertIn(STATE_PANNING);
+ mHandler.timeAdvance();
+ returnToNormalFrom(STATE_PANNING);
+
+ mHandler.timeAdvance();
+
+ verify(mMockScroller).fling(
+ /* startX= */ anyInt(),
+ /* startY= */ anyInt(),
+ // The system fling velocity is configurable and hard to test across devices, so as
+ // long as there is some fling velocity, we are happy.
+ /* velocityX= */ gt(100),
+ /* velocityY= */ eq(0),
+ /* minX= */ anyInt(),
+ /* minY= */ anyInt(),
+ /* maxX= */ anyInt(),
+ /* maxY= */ anyInt()
+ );
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testDownEvent_cancelsFling()
+ throws InterruptedException {
+ goFromStateIdleTo(STATE_ACTIVATED);
+ PointF pointer1 = DEFAULT_POINT;
+ PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+
+ // first move triggers the panning state
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ // second move actually pans
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ assertIn(STATE_PANNING);
+ mHandler.timeAdvance();
+ returnToNormalFrom(STATE_PANNING);
+
+ mHandler.timeAdvance();
+
+ send(downEvent());
+ mHandler.timeAdvance();
+
+ verify(mMockScroller).forceFinished(eq(true));
+ }
+
+ @Test
public void testShortcutTriggered_invokeShowWindowPromptAction() {
goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
@@ -1397,8 +1610,11 @@
send(upEvent());
}
- private void swipe(PointF start, PointF end) {
- swipeAndHold(start, end);
+ private void swipe(PointF start, PointF end, int durationMs) {
+ var mid = new PointF(start.x + (end.x - start.x) / 2f, start.y + (end.y - start.y) / 2f);
+ swipeAndHold(start, mid);
+ fastForward(durationMs);
+ send(moveEvent(end.x - start.x / 10f, end.y - start.y / 10f));
send(upEvent(end.x, end.y));
}
@@ -1491,9 +1707,18 @@
private MotionEvent pointerEvent(int action, float x, float y) {
- return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)}, 1);
+ return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)},
+ (action == ACTION_POINTER_UP || action == ACTION_POINTER_DOWN) ? 1 : 0);
}
+ /**
+ * Create a pointer event simulating the given pointer positions.
+ *
+ * @param action the action
+ * @param pointersPosition positions of the pointers
+ * @param changedIndex the index of the pointer associated with the ACTION_POINTER_UP or
+ * ACTION_POINTER_DOWN action. Must be 0 for all other actions.
+ */
private MotionEvent pointerEvent(int action, PointF[] pointersPosition, int changedIndex) {
final MotionEvent.PointerProperties[] PointerPropertiesArray =
new MotionEvent.PointerProperties[pointersPosition.length];
@@ -1513,12 +1738,12 @@
pointerCoordsArray[i] = pointerCoords;
}
- action += (changedIndex << ACTION_POINTER_INDEX_SHIFT);
+ var actionWithPointer = action | (changedIndex << ACTION_POINTER_INDEX_SHIFT);
- return MotionEvent.obtain(
+ var event = MotionEvent.obtain(
/* downTime */ mClock.now(),
/* eventTime */ mClock.now(),
- /* action */ action,
+ /* action */ actionWithPointer,
/* pointerCount */ pointersPosition.length,
/* pointerProperties */ PointerPropertiesArray,
/* pointerCoords */ pointerCoordsArray,
@@ -1530,6 +1755,14 @@
/* edgeFlags */ 0,
/* source */ InputDevice.SOURCE_TOUCHSCREEN,
/* flags */ 0);
+
+ Truth.assertThat(event.getActionIndex()).isEqualTo(changedIndex);
+ Truth.assertThat(event.getActionMasked()).isEqualTo(action);
+ if (action == ACTION_DOWN) {
+ Truth.assertThat(changedIndex).isEqualTo(0);
+ }
+
+ return event;
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 28d07f9..cd904eb 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -42,6 +42,7 @@
import static org.mockito.Mockito.when;
import android.accessibilityservice.MagnificationConfig;
+import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -59,6 +60,7 @@
import android.view.DisplayInfo;
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.view.accessibility.MagnificationAnimationCallback;
+import android.widget.Scroller;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
@@ -119,6 +121,8 @@
@Mock
private ValueAnimator mValueAnimator;
@Mock
+ private TimeAnimator mTimeAnimator;
+ @Mock
private MessageCapturingHandler mMessageCapturingHandler;
private FullScreenMagnificationController mScreenMagnificationController;
@@ -195,14 +199,17 @@
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
- mScreenMagnificationController = spy(new FullScreenMagnificationController(
- mControllerCtx,
- new Object(),
- mScreenMagnificationInfoChangedCallbackDelegate,
- mScaleProvider,
- () -> null,
- ConcurrentUtils.DIRECT_EXECUTOR
- ));
+ mScreenMagnificationController =
+ spy(
+ new FullScreenMagnificationController(
+ mControllerCtx,
+ new Object(),
+ mScreenMagnificationInfoChangedCallbackDelegate,
+ mScaleProvider,
+ () -> null,
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ () -> new Scroller(mContext),
+ () -> mTimeAnimator));
mScreenMagnificationController.register(TEST_DISPLAY);
mMagnificationConnectionManager = spy(
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
index e075379..c0ea157 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
@@ -106,13 +106,13 @@
}
@Test
- public void testRecord_changeUserForFile_throws() {
+ public void testRecord_changeUserForFile_ignored() {
Entry entry1 = new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1");
Entry entry2 = new Entry("owning.package1", "/path/file1", 'D', 20, "loading.package1");
PackageDynamicCodeLoading info = makePackageDcl(entry1);
- assertThrows(() -> record(info, entry2));
+ assertThat(record(info, entry2)).isFalse();
assertHasEntries(info, entry1);
}
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index ffb3bce..4ab9d3e 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -90,10 +90,12 @@
private static final long[] DURATIONS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
- private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] {
+ private static final WorkDuration[] WORK_DURATIONS_FIVE = new WorkDuration[] {
new WorkDuration(1L, 11L, 8L, 4L, 1L),
new WorkDuration(2L, 13L, 8L, 6L, 2L),
new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L),
+ new WorkDuration(2L, 13L, 0L, 6L, 2L),
+ new WorkDuration(2L, 13L, 8L, 0L, 2L),
};
@Mock private Context mContext;
@@ -609,9 +611,9 @@
.createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
a.updateTargetWorkDuration(100L);
- a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
+ a.reportActualWorkDuration2(WORK_DURATIONS_FIVE);
verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(),
- eq(WORK_DURATIONS_THREE));
+ eq(WORK_DURATIONS_FIVE));
assertThrows(IllegalArgumentException.class, () -> {
a.reportActualWorkDuration2(new WorkDuration[] {});
@@ -627,7 +629,7 @@
});
assertThrows(IllegalArgumentException.class, () -> {
- a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)});
+ a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 0L, 1L)});
});
assertThrows(IllegalArgumentException.class, () -> {
@@ -648,7 +650,7 @@
latch.await();
assertFalse(service.mUidObserver.isUidForeground(a.mUid));
- a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
+ a.reportActualWorkDuration2(WORK_DURATIONS_FIVE);
verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 03cdbbd..eddff9ab 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -716,7 +716,8 @@
String certificateDigestStr = "E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:"
+ "8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0";
- byte[] certificateDigest = new Signature(certificateDigestStr).toByteArray();
+ byte[] certificateDigest = new Signature(certificateDigestStr.replace(":", ""))
+ .toByteArray();
String contents = "<config>"
+ "<" + "enhanced-confirmation-trusted-installer" + " "
+ "package=\"" + pkgName + "\""
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 516fb4a..5eb76e3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,37 +15,56 @@
*/
package com.android.server.notification;
+import static android.app.Notification.COLOR_DEFAULT;
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_CAN_COLORIZE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_ONGOING_EVENT;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static com.android.server.notification.GroupHelper.BASE_FLAGS;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import android.annotation.SuppressLint;
import android.app.Notification;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArrayMap;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
import com.android.server.UiServiceTestCase;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -59,23 +78,37 @@
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class.
@RunWith(AndroidJUnit4.class)
public class GroupHelperTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
private @Mock GroupHelper.Callback mCallback;
+ private @Mock PackageManager mPackageManager;
private final static int AUTOGROUP_AT_COUNT = 7;
private GroupHelper mGroupHelper;
+ private @Mock Icon mSmallIcon;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mGroupHelper = new GroupHelper(AUTOGROUP_AT_COUNT, mCallback);
+ mGroupHelper = new GroupHelper(getContext(), mPackageManager, AUTOGROUP_AT_COUNT,
+ mCallback);
+
+ NotificationRecord r = mock(NotificationRecord.class);
+ StatusBarNotification sbn = getSbn("package", 0, "0", UserHandle.SYSTEM);
+ when(r.getNotification()).thenReturn(sbn.getNotification());
+ when(r.getSbn()).thenReturn(sbn);
+ when(mSmallIcon.sameAs(mSmallIcon)).thenReturn(true);
}
private StatusBarNotification getSbn(String pkg, int id, String tag,
- UserHandle user, String groupKey) {
+ UserHandle user, String groupKey, Icon smallIcon, int iconColor) {
Notification.Builder nb = new Notification.Builder(getContext(), "test_channel_id")
.setContentTitle("A")
- .setWhen(1205);
+ .setWhen(1205)
+ .setSmallIcon(smallIcon)
+ .setColor(iconColor);
if (groupKey != null) {
nb.setGroup(groupKey);
}
@@ -84,23 +117,32 @@
}
private StatusBarNotification getSbn(String pkg, int id, String tag,
+ UserHandle user, String groupKey) {
+ return getSbn(pkg, id, tag, user, groupKey, mSmallIcon, Notification.COLOR_DEFAULT);
+ }
+
+ private StatusBarNotification getSbn(String pkg, int id, String tag,
UserHandle user) {
return getSbn(pkg, id, tag, user, null);
}
+ private NotificationAttributes getNotificationAttributes(int flags) {
+ return new NotificationAttributes(flags, mSmallIcon, COLOR_DEFAULT);
+ }
+
@Test
public void testGetAutogroupSummaryFlags_noChildren() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
assertEquals(BASE_FLAGS, mGroupHelper.getAutogroupSummaryFlags(children));
}
@Test
public void testGetAutogroupSummaryFlags_oneOngoing() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", 0);
- children.put("b", FLAG_ONGOING_EVENT);
- children.put("c", FLAG_BUBBLE);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(0));
+ children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT));
+ children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -108,10 +150,10 @@
@Test
public void testGetAutogroupSummaryFlags_oneOngoingNoClear() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", 0);
- children.put("b", FLAG_ONGOING_EVENT|FLAG_NO_CLEAR);
- children.put("c", FLAG_BUBBLE);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(0));
+ children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT | FLAG_NO_CLEAR));
+ children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(FLAG_NO_CLEAR | FLAG_ONGOING_EVENT | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -119,10 +161,10 @@
@Test
public void testGetAutogroupSummaryFlags_oneOngoingBubble() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", 0);
- children.put("b", FLAG_ONGOING_EVENT | FLAG_BUBBLE);
- children.put("c", FLAG_BUBBLE);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(0));
+ children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT | FLAG_BUBBLE));
+ children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -130,11 +172,11 @@
@Test
public void testGetAutogroupSummaryFlags_multipleOngoing() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", 0);
- children.put("b", FLAG_ONGOING_EVENT);
- children.put("c", FLAG_BUBBLE);
- children.put("d", FLAG_ONGOING_EVENT);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(0));
+ children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT));
+ children.put("c", getNotificationAttributes(FLAG_BUBBLE));
+ children.put("d", getNotificationAttributes(FLAG_ONGOING_EVENT));
assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -142,10 +184,10 @@
@Test
public void testGetAutogroupSummaryFlags_oneAutoCancel() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", 0);
- children.put("b", FLAG_AUTO_CANCEL);
- children.put("c", FLAG_BUBBLE);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(0));
+ children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL));
+ children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -153,11 +195,11 @@
@Test
public void testGetAutogroupSummaryFlags_allAutoCancel() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", FLAG_AUTO_CANCEL);
- children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
- children.put("c", FLAG_AUTO_CANCEL);
- children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(FLAG_AUTO_CANCEL));
+ children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE));
+ children.put("c", getNotificationAttributes(FLAG_AUTO_CANCEL));
+ children.put("d", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE));
assertEquals(FLAG_AUTO_CANCEL | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -165,11 +207,12 @@
@Test
public void testGetAutogroupSummaryFlags_allAutoCancelOneOngoing() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", FLAG_AUTO_CANCEL);
- children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
- children.put("c", FLAG_AUTO_CANCEL);
- children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(FLAG_AUTO_CANCEL));
+ children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE));
+ children.put("c", getNotificationAttributes(FLAG_AUTO_CANCEL));
+ children.put("d", getNotificationAttributes(
+ FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT));
assertEquals(FLAG_AUTO_CANCEL| FLAG_ONGOING_EVENT | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -230,11 +273,11 @@
getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -248,11 +291,11 @@
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
- eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -266,11 +309,11 @@
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -282,11 +325,11 @@
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
- eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -301,11 +344,11 @@
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
- eq(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR));
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -329,8 +372,8 @@
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary should keep FLAG_ONGOING_EVENT if any child has it
- verify(mCallback).updateAutogroupSummary(
- anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
}
@Test
@@ -355,7 +398,8 @@
mGroupHelper.onNotificationRemoved(notifications.get(0));
// Summary is no longer ongoing
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS)));
}
@Test
@@ -378,8 +422,8 @@
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary is now ongoing
- verify(mCallback).updateAutogroupSummary(
- anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
}
@Test
@@ -403,8 +447,8 @@
mGroupHelper.onNotificationPosted(sbn, true);
// Summary is now ongoing
- verify(mCallback).updateAutogroupSummary(
- anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
}
@Test
@@ -430,7 +474,8 @@
mGroupHelper.onNotificationPosted(sbn, true);
// Summary is no longer ongoing
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS)));
}
@Test
@@ -455,7 +500,7 @@
mGroupHelper.onNotificationRemoved(notifications.get(1));
// Summary is still ongoing
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -479,7 +524,8 @@
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary should no longer be autocancelable
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS)));
}
@Test
@@ -505,8 +551,8 @@
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary should now autocancelable
- verify(mCallback).updateAutogroupSummary(
- anyInt(), anyString(), eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
}
@Test
@@ -530,7 +576,7 @@
mGroupHelper.onNotificationPosted(sbn, true);
// Summary should be still be autocancelable
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -552,7 +598,7 @@
mGroupHelper.onNotificationRemoved(notifications.get(0));
// Summary should still be autocancelable
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -565,7 +611,7 @@
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -593,7 +639,7 @@
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -622,7 +668,7 @@
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -646,8 +692,213 @@
verify(mCallback, times(1)).addAutoGroup(sbn.getKey());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
- verify(mCallback, never()).addAutoGroupSummary(
- anyInt(), anyString(), anyString(), anyInt());
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(), any());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAddSummary_sameIcon_sameColor() {
+ final String pkg = "package";
+ final Icon icon = mock(Icon.class);
+ when(icon.sameAs(icon)).thenReturn(true);
+ final int iconColor = Color.BLUE;
+ final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
+
+ // Add notifications with same icon and color
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ icon, iconColor);
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ // Check that the summary would have the same icon and color
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(attr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+ // After auto-grouping, add new notification with the same color
+ StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
+ mGroupHelper.onNotificationPosted(sbn, true);
+
+ // Check that the summary was updated
+ //NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(attr));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAddSummary_diffIcon_diffColor() {
+ final String pkg = "package";
+ final Icon initialIcon = mock(Icon.class);
+ when(initialIcon.sameAs(initialIcon)).thenReturn(true);
+ final int initialIconColor = Color.BLUE;
+
+ // Spy GroupHelper for getMonochromeAppIcon
+ final Icon monochromeIcon = mock(Icon.class);
+ when(monochromeIcon.sameAs(monochromeIcon)).thenReturn(true);
+ GroupHelper groupHelper = spy(mGroupHelper);
+ doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg));
+
+ final NotificationAttributes initialAttr = new NotificationAttributes(BASE_FLAGS,
+ initialIcon, initialIconColor);
+
+ // Add notifications with same icon and color
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ initialIcon, initialIconColor);
+ groupHelper.onNotificationPosted(sbn, false);
+ }
+ // Check that the summary would have the same icon and color
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(initialAttr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+ // After auto-grouping, add new notification with a different color
+ final Icon newIcon = mock(Icon.class);
+ final int newIconColor = Color.YELLOW;
+ StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, newIcon,
+ newIconColor);
+ groupHelper.onNotificationPosted(sbn, true);
+
+ // Summary should be updated to the default color and the icon to the monochrome icon
+ NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, monochromeIcon,
+ COLOR_DEFAULT);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutoGrouped_diffIcon_diffColor_removeChild_updateTo_sameIcon_sameColor() {
+ final String pkg = "package";
+ final Icon initialIcon = mock(Icon.class);
+ when(initialIcon.sameAs(initialIcon)).thenReturn(true);
+ final int initialIconColor = Color.BLUE;
+ final NotificationAttributes initialAttr = new NotificationAttributes(
+ GroupHelper.FLAG_INVALID, initialIcon, initialIconColor);
+
+ // Add AUTOGROUP_AT_COUNT-1 notifications with same icon and color
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ initialIcon, initialIconColor);
+ notifications.add(sbn);
+ }
+ // And an additional notification with different icon and color
+ final int lastIdx = AUTOGROUP_AT_COUNT - 1;
+ StatusBarNotification newSbn = getSbn(pkg, lastIdx,
+ String.valueOf(lastIdx), UserHandle.SYSTEM, null, mock(Icon.class),
+ Color.YELLOW);
+ notifications.add(newSbn);
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+
+ // Remove last notification (the only one with different icon and color)
+ mGroupHelper.onNotificationRemoved(notifications.get(lastIdx));
+
+ // Summary should be updated to the common icon and color
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(initialAttr));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutobundledSummaryIcon_sameIcon() {
+ final String pkg = "package";
+ final Icon icon = mock(Icon.class);
+ when(icon.sameAs(icon)).thenReturn(true);
+
+ // Create notifications with the same icon
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ childrenAttr.add(new NotificationAttributes(0, icon, COLOR_DEFAULT));
+ }
+
+ //Check that the generated summary icon is the same as the child notifications'
+ Icon summaryIcon = mGroupHelper.getAutobundledSummaryIconAndColor(pkg, childrenAttr).icon;
+ assertThat(summaryIcon).isEqualTo(icon);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutobundledSummaryIcon_diffIcon() {
+ final String pkg = "package";
+ // Spy GroupHelper for getMonochromeAppIcon
+ final Icon monochromeIcon = mock(Icon.class);
+ GroupHelper groupHelper = spy(mGroupHelper);
+ doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg));
+
+ // Create notifications with different icons
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), COLOR_DEFAULT));
+ }
+
+ // Check that the generated summary icon is the monochrome icon
+ Icon summaryIcon = groupHelper.getAutobundledSummaryIconAndColor(pkg, childrenAttr).icon;
+ assertThat(summaryIcon).isEqualTo(monochromeIcon);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutobundledSummaryIconColor_sameColor() {
+ final String pkg = "package";
+ final int iconColor = Color.BLUE;
+ // Create notifications with the same icon color
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor));
+ }
+
+ // Check that the generated summary icon color is the same as the child notifications'
+ int summaryIconColor = mGroupHelper.getAutobundledSummaryIconAndColor(pkg,
+ childrenAttr).iconColor;
+ assertThat(summaryIconColor).isEqualTo(iconColor);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutobundledSummaryIconColor_diffColor() {
+ final String pkg = "package";
+ // Create notifications with different icon colors
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), i));
+ }
+
+ // Check that the generated summary icon color is the default color
+ int summaryIconColor = mGroupHelper.getAutobundledSummaryIconAndColor(pkg,
+ childrenAttr).iconColor;
+ assertThat(summaryIconColor).isEqualTo(Notification.COLOR_DEFAULT);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testMonochromeAppIcon_adaptiveIconExists() throws Exception {
+ final String pkg = "testPackage";
+ final int monochromeIconResId = 1234;
+ AdaptiveIconDrawable adaptiveIcon = mock(AdaptiveIconDrawable.class);
+ Drawable monochromeIcon = mock(Drawable.class);
+ when(mPackageManager.getApplicationIcon(pkg)).thenReturn(adaptiveIcon);
+ when(adaptiveIcon.getMonochrome()).thenReturn(monochromeIcon);
+ when(adaptiveIcon.getSourceDrawableResId()).thenReturn(monochromeIconResId);
+ assertThat(mGroupHelper.getMonochromeAppIcon(pkg).getResId())
+ .isEqualTo(monochromeIconResId);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testMonochromeAppIcon_adaptiveIconMissing_fallback() throws Exception {
+ final String pkg = "testPackage";
+ final int fallbackIconResId = R.drawable.ic_notification_summary_auto;
+ when(mPackageManager.getApplicationIcon(pkg)).thenReturn(mock(Drawable.class));
+ assertThat(mGroupHelper.getMonochromeAppIcon(pkg).getResId())
+ .isEqualTo(fallbackIconResId);
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 8261dee..f396b24 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -279,6 +279,7 @@
import com.android.server.job.JobSchedulerInternal;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import com.android.server.notification.NotificationManagerService.NotificationListeners;
import com.android.server.notification.NotificationManagerService.PostNotificationTracker;
@@ -658,7 +659,8 @@
// NOTE: Prefer using the @EnableFlag annotation where possible. Do not add any android.app
// flags here.
mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER,
- Flags.FLAG_POLITE_NOTIFICATIONS);
+ Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE);
+
initNMS();
}
@@ -2332,8 +2334,9 @@
mService.mAutobundledSummaries.put(0, new ArrayMap<>());
mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
- mService.updateAutobundledSummaryFlags(
- 0, "pkg", GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, false);
+ mService.updateAutobundledSummaryLocked(0, "pkg",
+ new NotificationAttributes(GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT,
+ mock(Icon.class), 0), false);
waitForIdle();
assertTrue(summary.getSbn().isOngoing());
@@ -2350,7 +2353,9 @@
mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
mService.mSummaryByGroupKey.put("pkg", summary);
- mService.updateAutobundledSummaryFlags(0, "pkg", GroupHelper.BASE_FLAGS, false);
+ mService.updateAutobundledSummaryLocked(0, "pkg",
+ new NotificationAttributes(GroupHelper.BASE_FLAGS,
+ mock(Icon.class), 0), false);
waitForIdle();
assertFalse(summary.getSbn().isOngoing());
@@ -3427,8 +3432,8 @@
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true);
- NotificationRecord r = mService.createAutoGroupSummary(
- temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), 0);
+ NotificationRecord r = mService.createAutoGroupSummary(temp.getUserId(),
+ temp.getSbn().getPackageName(), temp.getKey(), 0, mock(Icon.class), 0);
assertThat(r.isImportanceFixed()).isTrue();
}
@@ -9741,8 +9746,11 @@
throws RemoteException {
IRingtonePlayer mockPlayer = mock(IRingtonePlayer.class);
when(mAudioManager.getRingtonePlayer()).thenReturn(mockPlayer);
- // Set up volume to be above 0 for the sound to actually play
+ // Set up volume to be above 0, and for AudioManager to signal playback should happen,
+ // for the sound to actually play
when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
+ when(mAudioManager.shouldNotificationSoundPlay(any(android.media.AudioAttributes.class)))
+ .thenReturn(true);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
@@ -11962,7 +11970,7 @@
// add summary
mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
nr1.getSbn().getPackageName(), nr1.getKey(),
- GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT));
+ GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, mock(Icon.class), 0));
// cancel both children
mBinderService.cancelNotificationWithTag(PKG, PKG, nr0.getSbn().getTag(),
@@ -11989,8 +11997,9 @@
// add notifications + summary for USER_SYSTEM
mService.addNotification(nr0);
mService.addNotification(nr1);
- mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
- nr1.getSbn().getPackageName(), nr1.getKey(), GroupHelper.BASE_FLAGS));
+ mService.addNotification(
+ mService.createAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(),
+ nr1.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0));
// add notifications + summary for USER_ALL
NotificationRecord nr0_all =
@@ -12000,8 +12009,10 @@
mService.addNotification(nr0_all);
mService.addNotification(nr1_all);
- mService.addNotification(mService.createAutoGroupSummary(nr0_all.getUserId(),
- nr0_all.getSbn().getPackageName(), nr0_all.getKey(), GroupHelper.BASE_FLAGS));
+ mService.addNotification(
+ mService.createAutoGroupSummary(nr0_all.getUserId(),
+ nr0_all.getSbn().getPackageName(),
+ nr0_all.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0));
// cancel both children for USER_ALL
mBinderService.cancelNotificationWithTag(PKG, PKG, nr0_all.getSbn().getTag(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index c44be7b..c29547f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -56,7 +56,6 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArraySet;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
@@ -632,8 +631,8 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG)
public void testAdjacentFocusInActivityEmbedding() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG);
Task task = createTask(mDefaultDisplay);
TaskFragment primary = createTaskFragmentWithActivity(task);
TaskFragment secondary = createTaskFragmentWithActivity(task);
@@ -645,7 +644,7 @@
doReturn(primary).when(windowState).getTaskFragment();
startBackNavigation();
- verify(mWm).moveFocusToAdjacentWindow(any(), anyInt());
+ verify(mWm).moveFocusToActivity(any());
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index a0562aa..f02dd3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -236,6 +236,26 @@
assertTrue(window.isOnScreen());
window.hide(false /* doAnimation */, false /* requestAnim */);
assertFalse(window.isOnScreen());
+
+ // Verifies that a window without animation can be hidden even if its parent is animating.
+ window.show(false /* doAnimation */, false /* requestAnim */);
+ assertTrue(window.isVisibleByPolicy());
+ window.getParent().startAnimation(mTransaction, mock(AnimationAdapter.class),
+ false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM);
+ window.mAttrs.windowAnimations = 0;
+ window.hide(true /* doAnimation */, true /* requestAnim */);
+ assertFalse(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION));
+ assertFalse(window.isVisibleByPolicy());
+ assertFalse(window.isOnScreen());
+
+ // Verifies that a window with animation can be hidden after the hide animation is finished.
+ window.show(false /* doAnimation */, false /* requestAnim */);
+ window.mAttrs.windowAnimations = android.R.style.Animation_Dialog;
+ window.hide(true /* doAnimation */, true /* requestAnim */);
+ assertTrue(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION));
+ assertTrue(window.isVisibleByPolicy());
+ window.cancelAnimation();
+ assertFalse(window.isVisibleByPolicy());
}
@Test
diff --git a/services/usage/Android.bp b/services/usage/Android.bp
index 867773d..2c1095a 100644
--- a/services/usage/Android.bp
+++ b/services/usage/Android.bp
@@ -18,5 +18,8 @@
name: "services.usage",
defaults: ["platform_service_defaults"],
srcs: [":services.usage-sources"],
- libs: ["services.core"],
+ libs: [
+ "services.core",
+ "service-art.stubs.system_server",
+ ],
}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 2445f51..44f4068 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -19,7 +19,9 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.internal.util.ArrayUtils.defeatNullable;
+import static com.android.server.pm.DexOptHelper.getArtManagerLocal;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
import static com.android.server.usage.StorageStatsManagerLocal.StorageStatsAugmenter;
import android.Manifest;
@@ -76,6 +78,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.model.ArtManagedFileStats;
+import com.android.server.pm.PackageManagerLocal.FilteredSnapshot;
import com.android.server.IoThread;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
@@ -449,7 +454,7 @@
}
if (Flags.getAppBytesByDataTypeApi()) {
computeAppStatsByDataTypes(
- stats, appInfo.sourceDir);
+ stats, appInfo.sourceDir, packageNames[i]);
}
}
} catch (NameNotFoundException e) {
@@ -592,6 +597,9 @@
res.codeBytes = stats.codeSize + stats.externalCodeSize;
res.dataBytes = stats.dataSize + stats.externalDataSize;
res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
+ res.dexoptBytes = stats.dexoptSize;
+ res.curProfBytes = stats.curProfSize;
+ res.refProfBytes = stats.refProfSize;
res.apkBytes = stats.apkSize;
res.libBytes = stats.libSize;
res.dmBytes = stats.dmSize;
@@ -946,7 +954,7 @@
}
private void computeAppStatsByDataTypes(
- PackageStats stats, String sourceDirName) {
+ PackageStats stats, String sourceDirName, String packageName) {
// Get apk, lib, dm file sizes.
File srcDir = new File(sourceDirName);
@@ -958,5 +966,24 @@
stats.apkSize += getFileBytesInDir(srcDir, ".apk");
stats.dmSize += getFileBytesInDir(srcDir, ".dm");
stats.libSize += getDirBytes(new File(sourceDirName + "/lib/"));
+
+ // Get dexopt, current profle and reference profile sizes.
+ if (SystemProperties.getBoolean("dalvik.vm.features.art_managed_file_stats", false)) {
+ ArtManagedFileStats artManagedFileStats;
+ try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
+ artManagedFileStats =
+ getArtManagerLocal().getArtManagedFileStats(snapshot, packageName);
+ }
+
+ stats.dexoptSize +=
+ artManagedFileStats
+ .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_DEXOPT_ARTIFACT);
+ stats.refProfSize +=
+ artManagedFileStats
+ .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_REF_PROFILE);
+ stats.curProfSize +=
+ artManagedFileStats
+ .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_CUR_PROFILE);
+ }
}
}
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 938a5ed..b054a57 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -262,6 +262,23 @@
"$(genDir)/aapt2_tests " +
"--gtest_output=xml:$(out) " +
">/dev/null 2>&1 ; true",
+ dist: {
+ targets: ["aapt2_run_host_unit_tests"],
+ dir: "gtest",
+ dest: "aapt2_host_unit_tests_result.xml",
+ },
+ arch: {
+ x86: {
+ dist: {
+ suffix: "_x86",
+ },
+ },
+ x86_64: {
+ dist: {
+ suffix: "_x86_64",
+ },
+ },
+ },
}
phony_rule {
diff --git a/tools/codegen/src/com/android/codegen/FileInfo.kt b/tools/codegen/src/com/android/codegen/FileInfo.kt
index a1d0389..cc3a156 100644
--- a/tools/codegen/src/com/android/codegen/FileInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FileInfo.kt
@@ -238,7 +238,7 @@
} else if (classBounds.isDataclass) {
// Insert placeholder for generated code to be inserted for the 1st time
- chunks.last = (chunks.last as Code)
+ chunks[chunks.lastIndex] = (chunks.last() as Code)
.lines
.dropLastWhile { it.isBlank() }
.run {
@@ -286,4 +286,4 @@
.let { addAll(it) }
}
}
-}
\ No newline at end of file
+}
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index 9ceb204..a40bdd7 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -137,14 +137,4 @@
cause)
}
-var <T> MutableList<T>.last
- get() = last()
- set(value) {
- if (isEmpty()) {
- add(value)
- } else {
- this[size - 1] = value
- }
- }
-
-inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init)
\ No newline at end of file
+inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init)