diff --git a/core/api/current.txt b/core/api/current.txt index 7f261d450b42a84edfb7384cd51561624779e8fe..1b5c953c21c66727a1b0d30c63460ba03fbe179f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12606,6 +12606,7 @@ package android.content.pm { method public boolean isStagedSessionApplied(); method public boolean isStagedSessionFailed(); method public boolean isStagedSessionReady(); + method @FlaggedApi("android.content.pm.archiving") public boolean isUnarchival(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionInfo> CREATOR; field public static final int INVALID_ID = -1; // 0xffffffff @@ -13296,6 +13297,7 @@ package android.content.pm { method @NonNull public java.util.List<android.content.pm.VersionedPackage> getDependentPackages(); method @IntRange(from=0xffffffff) public long getLongVersion(); method public String getName(); + method @FlaggedApi("android.content.pm.sdk_lib_independence") @NonNull public java.util.List<android.content.pm.VersionedPackage> getOptionalDependentPackages(); method public int getType(); method @Deprecated @IntRange(from=0xffffffff) public int getVersion(); method public void writeToParcel(android.os.Parcel, int); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index e7803fbf011da9e2bb6b0d165cb19cadfcdaf7e1..d395b8cf936efe54482695d5bd64eb6ef008d62d 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -136,6 +136,7 @@ package android.content { package android.content.pm { public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { + method @FlaggedApi("android.content.pm.sdk_lib_independence") @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getOptionalSharedLibraryInfos(); method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraryInfos(); field public static final int HIDDEN_API_ENFORCEMENT_DEFAULT = -1; // 0xffffffff field public static final int HIDDEN_API_ENFORCEMENT_DISABLED = 0; // 0x0 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 225b31ce37ed9798633524cf60c6d047a6abb51e..e92564b5d7c2f9f8cd851429ce32602601f5e483 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -12930,6 +12930,7 @@ package android.service.voice { field public static final int CONFIDENCE_LEVEL_LOW = 1; // 0x1 field public static final int CONFIDENCE_LEVEL_MEDIUM = 2; // 0x2 field public static final int CONFIDENCE_LEVEL_NONE = 0; // 0x0 + field @FlaggedApi("android.service.voice.flags.allow_hotword_bump_egress") public static final int CONFIDENCE_LEVEL_VERY_HIGH = 4; // 0x4 field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordRejectedResult> CREATOR; } @@ -13701,6 +13702,7 @@ package android.telephony { method @NonNull public java.util.List<java.lang.Boolean> areCarrierIdentifiersAllowed(@NonNull java.util.List<android.service.carrier.CarrierIdentifier>); method public int describeContents(); method @NonNull public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_restriction_status") public int getCarrierRestrictionStatus(); method public int getDefaultCarrierRestriction(); method @NonNull public java.util.List<android.service.carrier.CarrierIdentifier> getExcludedCarriers(); method public int getMultiSimPolicy(); diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index dec1ee52712d133ab2c483fe195b49589d1be23d..cef11bb42c3f262e024c5baf69135711ef5a0d5f 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -1909,6 +1909,8 @@ SamShouldBeLast: android.view.accessibility.AccessibilityManager#addAccessibilit SAM-compatible parameters (such as parameter 1, "listener", in android.view.accessibility.AccessibilityManager.addAccessibilityStateChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.view.accessibility.AccessibilityManager#addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler): SAM-compatible parameters (such as parameter 1, "listener", in android.view.accessibility.AccessibilityManager.addTouchExplorationStateChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.view.inputmethod.InputMethodInfo#dump(android.util.Printer, String): + SAM-compatible parameters (such as parameter 1, "pw", in android.view.inputmethod.InputMethodInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams): SAM-compatible parameters (such as parameter 2, "filePathCallback", in android.webkit.WebChromeClient.onShowFileChooser) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 87c86df6140d5dd77dfb624eaf0609256672da8b..287d2bd9e6a7353e5c77b0f0fe75d25173951f58 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2909,7 +2909,7 @@ public class ApplicationPackageManager extends PackageManager { try { return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras, launcherExtras, dialogInfo, flags, mContext.getOpPackageName(), - getUserId()); + UserHandle.myUserId() /* suspendingUserId */, getUserId() /* targetUserId */); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 16a80e93326a4aae682e9e49beccefa678557d27..3713380485ea0fa19c210192f6a222c3f2a9c96b 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import static android.os.Build.VERSION_CODES.DONUT; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1065,10 +1066,24 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving * the structure. * + * NOTE: the list also contains the result of {@link #getOptionalSharedLibraryInfos}. + * * {@hide} */ + @Nullable public List<SharedLibraryInfo> sharedLibraryInfos; + /** + * List of all shared libraries this application is optionally linked against. + * This field is only set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES + * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving + * the structure. + * + * @hide + */ + @Nullable + public List<SharedLibraryInfo> optionalSharedLibraryInfos; + /** * Full path to the default directory assigned to the package for its * persistent data. @@ -1937,6 +1952,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { seInfoUser = orig.seInfoUser; sharedLibraryFiles = orig.sharedLibraryFiles; sharedLibraryInfos = orig.sharedLibraryInfos; + optionalSharedLibraryInfos = orig.optionalSharedLibraryInfos; dataDir = orig.dataDir; deviceProtectedDataDir = orig.deviceProtectedDataDir; credentialProtectedDataDir = orig.credentialProtectedDataDir; @@ -2029,6 +2045,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeString8(seInfoUser); dest.writeString8Array(sharedLibraryFiles); dest.writeTypedList(sharedLibraryInfos); + dest.writeTypedList(optionalSharedLibraryInfos); dest.writeString8(dataDir); dest.writeString8(deviceProtectedDataDir); dest.writeString8(credentialProtectedDataDir); @@ -2129,6 +2146,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { seInfoUser = source.readString8(); sharedLibraryFiles = source.createString8Array(); sharedLibraryInfos = source.createTypedArrayList(SharedLibraryInfo.CREATOR); + optionalSharedLibraryInfos = source.createTypedArrayList(SharedLibraryInfo.CREATOR); dataDir = source.readString8(); deviceProtectedDataDir = source.readString8(); credentialProtectedDataDir = source.readString8(); @@ -2760,6 +2778,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * list will only be set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving the structure. * + * NOTE: the list also contains the result of {@link #getOptionalSharedLibraryInfos}. + * * @hide */ @NonNull @@ -2771,6 +2791,23 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return sharedLibraryInfos; } + /** + * List of all shared libraries this application is optionally linked against. This + * list will only be set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES + * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving the structure. + * + * @hide + */ + @NonNull + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @FlaggedApi(Flags.FLAG_SDK_LIB_INDEPENDENCE) + public List<SharedLibraryInfo> getOptionalSharedLibraryInfos() { + if (optionalSharedLibraryInfos == null) { + return Collections.EMPTY_LIST; + } + return optionalSharedLibraryInfos; + } + /** * Gets the trusted host certificate digests of apps that are allowed to embed activities of * this application. The digests are computed using the SHA-256 digest algorithm. diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 6f7299aa8e311b63e2d6e74d76c930792aa821b4..a97de6368b8c81666d82782cf08771a5b01f1786 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -65,6 +65,8 @@ interface ILauncherApps { in UserHandle user); LauncherUserInfo getLauncherUserInfo(in UserHandle user); List<String> getPreInstalledSystemPackages(in UserHandle user); + IntentSender getAppMarketActivityIntent(String callingPackage, String packageName, + in UserHandle user); void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage, String callingFeatureId, in ComponentName component, in Rect sourceBounds, in Bundle opts, in UserHandle user); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 98623de810c48ef67f990c17f907e229779a4f6f..6dc8d4738c87720210284eb2ad11a863fd8c603e 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -300,7 +300,8 @@ interface IPackageManager { String[] setPackagesSuspendedAsUser(in String[] packageNames, boolean suspended, in PersistableBundle appExtras, in PersistableBundle launcherExtras, - in SuspendDialogInfo dialogInfo, int flags, String callingPackage, int userId); + in SuspendDialogInfo dialogInfo, int flags, String suspendingPackage, + int suspendingUserId, int targetUserId); String[] getUnsuspendablePackagesForUser(in String[] packageNames, int userId); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index ccc8f0956865d7bf8176695d924cf56124b4654e..1d2b1aff46bc549a87cae7bb5fb48072be9313b0 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -799,6 +799,55 @@ public class LauncherApps { } } + + /** + * Returns an intent sender which can be used to start the App Market activity (Installer + * Activity). + * This method is primarily used to get an intent sender which starts App Market activity for + * another profile, if the caller is not otherwise allowed to start activity in that profile. + * + * <p>When packageName is set, intent sender to start the App Market Activity which installed + * the package in calling user will be returned, but for the profile passed. + * + * <p>When packageName is not set, intent sender to launch the default App Market Activity for + * the profile will be returned. In case there are multiple App Market Activities available for + * the profile, IntentPicker will be started, allowing user to choose the preferred activity. + * + * <p>The method will fall back to the behaviour of not having the packageName set, in case: + * <ul> + * <li>No activity for the packageName is found in calling user-space.</li> + * <li>The App Market Activity which installed the package in calling user-space is not + * present.</li> + * <li>The App Market Activity which installed the package in calling user-space is not + * present in the profile passed.</li> + * </ul> + * </p> + * + * + * + * @param packageName the package for which intent sender to launch App Market Activity is + * required. + * @param user the profile for which intent sender to launch App Market Activity is required. + * @return {@link IntentSender} object which launches the App Market Activity, null in case + * there is no such activity. + * @hide + */ + @Nullable + @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + public IntentSender getAppMarketActivityIntent(@Nullable String packageName, + @NonNull UserHandle user) { + if (DEBUG) { + Log.i(TAG, "getAppMarketActivityIntent for package: " + packageName + + " user: " + user); + } + try { + return mService.getAppMarketActivityIntent(mContext.getPackageName(), + packageName, user); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + /** * Returns the list of the system packages that are installed at user creation. * diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index e395127dfaf3141b1c5c17db2a273638e8989476..0e131b413d0ce9bd0650a320979cff2dad6361b1 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -4381,6 +4381,17 @@ public class PackageInstaller { return pendingUserActionReason; } + /** + * Returns true if the session is an unarchival. + * + * @see PackageInstaller#requestUnarchive + */ + @FlaggedApi(Flags.FLAG_ARCHIVING) + public boolean isUnarchival() { + return (installFlags & PackageManager.INSTALL_UNARCHIVE) != 0; + } + + @Override public int describeContents() { return 0; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index a5d16f2f6be193a6c2fa2a821a744bf07bd0f02f..a8638708824b62629191ce777a83f118503b7993 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1517,6 +1517,7 @@ public abstract class PackageManager { INSTALL_REQUEST_UPDATE_OWNERSHIP, INSTALL_IGNORE_DEXOPT_PROFILE, INSTALL_UNARCHIVE_DRAFT, + INSTALL_UNARCHIVE, }) @Retention(RetentionPolicy.SOURCE) public @interface InstallFlags {} @@ -1764,12 +1765,21 @@ public abstract class PackageManager { * If set, then the session is a draft session created for an upcoming unarchival by its * installer. * - * @see PackageInstaller#requestUnarchive(String) + * @see PackageInstaller#requestUnarchive * * @hide */ public static final int INSTALL_UNARCHIVE_DRAFT = 1 << 29; + /** + * If set, then the {@link PackageInstaller.Session} is an unarchival. + * + * @see PackageInstaller#requestUnarchive + * + * @hide + */ + public static final int INSTALL_UNARCHIVE = 1 << 30; + /** * Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is * a development-only feature and should not be used on end user devices. @@ -9990,6 +10000,9 @@ public abstract class PackageManager { * device administrators or apps holding {@link android.Manifest.permission#MANAGE_USERS} or * {@link android.Manifest.permission#SUSPEND_APPS}. * + * <p> + * <strong>Note:</strong>This API doesn't support cross user suspension and should only be used + * for testing. * @param suspendedPackage The package that has been suspended. * @return Name of the package that suspended the given package. Returns {@code null} if the * given package is not currently suspended and the platform package name - i.e. diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java index 25ba72551b0448b2170a95e350de94004eb867fe..5acebf54a1591f9a0e4fe81a4bcc75f16905390d 100644 --- a/core/java/android/content/pm/SharedLibraryInfo.java +++ b/core/java/android/content/pm/SharedLibraryInfo.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -23,6 +24,7 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; +import android.util.Pair; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -99,8 +101,41 @@ public final class SharedLibraryInfo implements Parcelable { private final boolean mIsNative; private final VersionedPackage mDeclaringPackage; private final List<VersionedPackage> mDependentPackages; + + private final List<VersionedPackage> mOptionalDependentPackages; private List<SharedLibraryInfo> mDependencies; + /** + * Creates a new instance. + * + * @param codePaths For a non {@link #TYPE_BUILTIN builtin} library, the locations of + * jars of + * this shared library. Null for builtin library. + * @param name The lib name. + * @param version The lib version if not builtin. + * @param type The lib type. + * @param declaringPackage The package that declares the library. + * @param dependentPackages The packages that depend on the library. + * @param isNative indicate if this shared lib is a native lib or not (i.e. java) + * @hide + */ + public SharedLibraryInfo(String path, String packageName, List<String> codePaths, + String name, long version, int type, + VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages, + List<SharedLibraryInfo> dependencies, boolean isNative) { + mPath = path; + mPackageName = packageName; + mCodePaths = codePaths; + mName = name; + mVersion = version; + mType = type; + mDeclaringPackage = declaringPackage; + mDependentPackages = dependentPackages; + mDependencies = dependencies; + mIsNative = isNative; + mOptionalDependentPackages = null; + } + /** * Creates a new instance. * @@ -110,15 +145,17 @@ public final class SharedLibraryInfo implements Parcelable { * @param version The lib version if not builtin. * @param type The lib type. * @param declaringPackage The package that declares the library. - * @param dependentPackages The packages that depend on the library. * @param isNative indicate if this shared lib is a native lib or not (i.e. java) + * @param allDependentPackages All packages that depend on the library (including the optional + * sdk libraries). * * @hide */ public SharedLibraryInfo(String path, String packageName, List<String> codePaths, String name, long version, int type, - VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages, - List<SharedLibraryInfo> dependencies, boolean isNative) { + VersionedPackage declaringPackage, + List<SharedLibraryInfo> dependencies, boolean isNative, + Pair<List<VersionedPackage>, List<Boolean>> allDependentPackages) { mPath = path; mPackageName = packageName; mCodePaths = codePaths; @@ -126,9 +163,28 @@ public final class SharedLibraryInfo implements Parcelable { mVersion = version; mType = type; mDeclaringPackage = declaringPackage; - mDependentPackages = dependentPackages; mDependencies = dependencies; mIsNative = isNative; + + var allDependents = allDependentPackages.first; + var usesLibOptional = allDependentPackages.second; + mDependentPackages = allDependents; + List<VersionedPackage> optionalDependents = null; + if (mType == SharedLibraryInfo.TYPE_SDK_PACKAGE + && Flags.sdkLibIndependence() && allDependents != null + && usesLibOptional != null + && allDependents.size() == usesLibOptional.size()) { + for (int k = 0; k < allDependents.size(); k++) { + VersionedPackage versionedPackage = allDependents.get(k); + if (usesLibOptional.get(k)) { + if (optionalDependents == null) { + optionalDependents = new ArrayList<>(); + } + optionalDependents.add(versionedPackage); + } + } + } + mOptionalDependentPackages = optionalDependents; } private SharedLibraryInfo(Parcel parcel) { @@ -148,6 +204,8 @@ public final class SharedLibraryInfo implements Parcelable { parcel.readArrayList(null, android.content.pm.VersionedPackage.class); mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR); mIsNative = parcel.readBoolean(); + mOptionalDependentPackages = parcel.readParcelableList(new ArrayList<>(), + VersionedPackage.class.getClassLoader(), VersionedPackage.class); } /** @@ -324,6 +382,8 @@ public final class SharedLibraryInfo implements Parcelable { /** * Gets the packages that depend on the library. * + * NOTE: the list also contains the result of {@link #getOptionalDependentPackages}. + * * @return The dependent packages. */ public @NonNull List<VersionedPackage> getDependentPackages() { @@ -333,6 +393,19 @@ public final class SharedLibraryInfo implements Parcelable { return mDependentPackages; } + /** + * Gets the packages that optionally depend on the library. + * + * @return The dependent packages. + */ + @FlaggedApi(Flags.FLAG_SDK_LIB_INDEPENDENCE) + public @NonNull List<VersionedPackage> getOptionalDependentPackages() { + if (mOptionalDependentPackages == null) { + return Collections.emptyList(); + } + return mOptionalDependentPackages; + } + @Override public int describeContents() { return 0; @@ -362,6 +435,7 @@ public final class SharedLibraryInfo implements Parcelable { parcel.writeList(mDependentPackages); parcel.writeTypedList(mDependencies); parcel.writeBoolean(mIsNative); + parcel.writeParcelableList(mOptionalDependentPackages, flags); } private static String typeToString(int type) { diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..73ac333cfd89998843ac13a932838e6c55f0ffd3 --- /dev/null +++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.biometrics; + +/** + * Low-level callback interface between <Biometric>Manager and <Auth>Service. Allows core system + * services (e.g. SystemUI) to register a listener for updates about the current state of biometric + * authentication. + * @hide + */ +oneway interface AuthenticationStateListener { + /** + * Defines behavior in response to authentication starting + * @param requestReason reason from [BiometricRequestConstants.RequestReason] for requesting + * authentication starting + */ + void onAuthenticationStarted(int requestReason); + + /** + * Defines behavior in response to authentication stopping + */ + void onAuthenticationStopped(); +} diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index f82f79eba8c91cd5a63ada836cc9be390b7ea4f1..d7d1d1a7c6778027512d98d8996defdd9ed057bc 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -551,6 +551,44 @@ public class BiometricManager { } } + /** + * Registers listener for changes to biometric authentication state. + * Only sends callbacks for events that occur after the callback has been registered. + * @param listener Listener for changes to biometric authentication state + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void registerAuthenticationStateListener(AuthenticationStateListener listener) { + if (mService != null) { + try { + mService.registerAuthenticationStateListener(listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "registerAuthenticationStateListener(): Service not connected"); + } + } + + /** + * Unregisters listener for changes to biometric authentication state. + * @param listener Listener for changes to biometric authentication state + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void unregisterAuthenticationStateListener(AuthenticationStateListener listener) { + if (mService != null) { + try { + mService.unregisterAuthenticationStateListener(listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "unregisterAuthenticationStateListener(): Service not connected"); + } + } + + /** * Requests all {@link Authenticators.Types#BIOMETRIC_STRONG} sensors to have their * authenticatorId invalidated for the specified user. This happens when enrollments have been diff --git a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java b/core/java/android/hardware/biometrics/BiometricRequestConstants.java similarity index 69% rename from core/java/android/hardware/biometrics/BiometricOverlayConstants.java rename to core/java/android/hardware/biometrics/BiometricRequestConstants.java index 065ae64a92ad227a58754253ed403e7159a63b1a..b036f309f7df4e114ff0666c74400dfe520adadc 100644 --- a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java +++ b/core/java/android/hardware/biometrics/BiometricRequestConstants.java @@ -22,24 +22,27 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Common constants for biometric overlays. + * Common constants for biometric requests. * @hide */ -public interface BiometricOverlayConstants { +public class BiometricRequestConstants { + + private BiometricRequestConstants() {} + /** Unknown usage. */ - int REASON_UNKNOWN = 0; + public static final int REASON_UNKNOWN = 0; /** User is about to enroll. */ - int REASON_ENROLL_FIND_SENSOR = 1; + public static final int REASON_ENROLL_FIND_SENSOR = 1; /** User is enrolling. */ - int REASON_ENROLL_ENROLLING = 2; + public static final int REASON_ENROLL_ENROLLING = 2; /** Usage from BiometricPrompt. */ - int REASON_AUTH_BP = 3; - /** Usage from Keyguard. */ - int REASON_AUTH_KEYGUARD = 4; + public static final int REASON_AUTH_BP = 3; + /** Usage from Device Entry. */ + public static final int REASON_AUTH_KEYGUARD = 4; /** Non-specific usage (from FingerprintManager). */ - int REASON_AUTH_OTHER = 5; + public static final int REASON_AUTH_OTHER = 5; /** Usage from Settings. */ - int REASON_AUTH_SETTINGS = 6; + public static final int REASON_AUTH_SETTINGS = 6; @IntDef({REASON_UNKNOWN, REASON_ENROLL_FIND_SENSOR, @@ -49,5 +52,5 @@ public interface BiometricOverlayConstants { REASON_AUTH_OTHER, REASON_AUTH_SETTINGS}) @Retention(RetentionPolicy.SOURCE) - @interface ShowReason {} + public @interface RequestReason {} } diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index 5bdbe2b573b2f474a285bed42778f6ad0aa22d0c..8514f98fbf0dd03d986a2b36fe81294fb7ea486e 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -16,6 +16,7 @@ package android.hardware.biometrics; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; @@ -66,6 +67,12 @@ interface IAuthService { // Register callback for when keyguard biometric eligibility changes. void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); + // Register listener for changes to authentication state. + void registerAuthenticationStateListener(AuthenticationStateListener listener); + + // Unregister listener for changes to authentication state. + void unregisterAuthenticationStateListener(AuthenticationStateListener listener); + // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the // specified user. This happens when enrollments have been added on devices with multiple // biometric sensors. diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 935157a48a4505a28d050569da1e5e59512cf3a3..fe7de83337842673623ad74b8867b2b8ff6b1d18 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -983,6 +983,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** * @hide */ diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 0100660669e9361c8c670d2f28255432058fc9fc..f594c00b0e4771f4405b07dc23d4b7fd6d9b34a6 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -15,6 +15,7 @@ */ package android.hardware.fingerprint; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IBiometricStateListener; @@ -203,6 +204,14 @@ interface IFingerprintService { @EnforcePermission("USE_BIOMETRIC_INTERNAL") void setSidefpsController(in ISidefpsController controller); + // Registers AuthenticationStateListener. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void registerAuthenticationStateListener(AuthenticationStateListener listener); + + // Unregisters AuthenticationStateListener. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void unregisterAuthenticationStateListener(AuthenticationStateListener listener); + // Registers BiometricStateListener. @EnforcePermission("USE_BIOMETRIC_INTERNAL") void registerBiometricStateListener(IBiometricStateListener listener); diff --git a/core/java/android/service/voice/HotwordRejectedResult.java b/core/java/android/service/voice/HotwordRejectedResult.java index 26c1ca428c3b228c9e2ac0c5b2e9aeec19f80fc7..eb1ac67719eddd905d414c69af16102d0de2a507 100644 --- a/core/java/android/service/voice/HotwordRejectedResult.java +++ b/core/java/android/service/voice/HotwordRejectedResult.java @@ -16,9 +16,11 @@ package android.service.voice; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.SystemApi; import android.os.Parcelable; +import android.service.voice.flags.Flags; import com.android.internal.util.DataClass; @@ -53,12 +55,17 @@ public final class HotwordRejectedResult implements Parcelable { /** High confidence in hotword detector result. */ public static final int CONFIDENCE_LEVEL_HIGH = 3; + /** Very high confidence in hotword detector result. **/ + @FlaggedApi(Flags.FLAG_ALLOW_HOTWORD_BUMP_EGRESS) + public static final int CONFIDENCE_LEVEL_VERY_HIGH = 4; + /** @hide */ @IntDef(prefix = {"CONFIDENCE_LEVEL_"}, value = { CONFIDENCE_LEVEL_NONE, CONFIDENCE_LEVEL_LOW, CONFIDENCE_LEVEL_MEDIUM, - CONFIDENCE_LEVEL_HIGH + CONFIDENCE_LEVEL_HIGH, + CONFIDENCE_LEVEL_VERY_HIGH }) @Retention(RetentionPolicy.SOURCE) @interface HotwordConfidenceLevelValue { @@ -91,9 +98,10 @@ public final class HotwordRejectedResult implements Parcelable { CONFIDENCE_LEVEL_NONE, CONFIDENCE_LEVEL_LOW, CONFIDENCE_LEVEL_MEDIUM, - CONFIDENCE_LEVEL_HIGH + CONFIDENCE_LEVEL_HIGH, + CONFIDENCE_LEVEL_VERY_HIGH }) - @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @Retention(RetentionPolicy.SOURCE) @DataClass.Generated.Member public @interface ConfidenceLevel {} @@ -109,6 +117,8 @@ public final class HotwordRejectedResult implements Parcelable { return "CONFIDENCE_LEVEL_MEDIUM"; case CONFIDENCE_LEVEL_HIGH: return "CONFIDENCE_LEVEL_HIGH"; + case CONFIDENCE_LEVEL_VERY_HIGH: + return "CONFIDENCE_LEVEL_VERY_HIGH"; default: return Integer.toHexString(value); } } @@ -259,10 +269,10 @@ public final class HotwordRejectedResult implements Parcelable { } @DataClass.Generated( - time = 1621961370106L, + time = 1701990933632L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/voice/HotwordRejectedResult.java", - inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_HIGH\nprivate final @android.service.voice.HotwordRejectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate static int defaultConfidenceLevel()\nclass HotwordRejectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final @android.annotation.FlaggedApi int CONFIDENCE_LEVEL_VERY_HIGH\nprivate final @android.service.voice.HotwordRejectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate static int defaultConfidenceLevel()\nclass HotwordRejectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/voice/flags/flags.aconfig b/core/java/android/service/voice/flags/flags.aconfig index c414ef8a6826edf7896d8300df3389bc9c07c98e..b596666bf607fd0dabb612d2dc1de7159dbd26fc 100644 --- a/core/java/android/service/voice/flags/flags.aconfig +++ b/core/java/android/service/voice/flags/flags.aconfig @@ -6,3 +6,10 @@ flag { description: "This flag allows the hotword detection service to egress training data to the default assistant." bug: "296074924" } + +flag { + name: "allow_hotword_bump_egress" + namespace: "machine_learning" + description: "This flag allows hotword detection service to egress reason code for hotword bump." + bug: "290951024" +} diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index c7e180732fd48b91c767cc1a86f8af61b49c99f1..e3d9c605ff63f44417ff60c3032b2b16967dfe0c 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -591,6 +591,13 @@ public interface WindowManager extends ViewManager { */ int TRANSIT_FLAG_KEYGUARD_UNOCCLUDING = (1 << 13); // 0x2000 + /** + * Transition flag: Indicates that there is a physical display switch + * TODO(b/316112906) remove after defer_display_updates flag roll out + * @hide + */ + int TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH = (1 << 14); // 0x4000 + /** * @hide */ @@ -608,7 +615,8 @@ public interface WindowManager extends ViewManager { TRANSIT_FLAG_INVISIBLE, TRANSIT_FLAG_KEYGUARD_APPEARING, TRANSIT_FLAG_KEYGUARD_OCCLUDING, - TRANSIT_FLAG_KEYGUARD_UNOCCLUDING + TRANSIT_FLAG_KEYGUARD_UNOCCLUDING, + TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH, }) @Retention(RetentionPolicy.SOURCE) @interface TransitionFlags {} diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java index efc1455ecd456f235acdfa536de37bc2899f4e5b..f1aa330f562c5de386f5649be3509df593d98da0 100644 --- a/core/java/com/android/internal/app/SuspendedAppActivity.java +++ b/core/java/com/android/internal/app/SuspendedAppActivity.java @@ -39,6 +39,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.SuspendDialogInfo; +import android.content.pm.UserPackage; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -308,7 +309,8 @@ public class SuspendedAppActivity extends AlertActivity try { final String[] errored = ipm.setPackagesSuspendedAsUser( new String[]{mSuspendedPackage}, false, null, null, null, 0, - mSuspendingPackage, mUserId); + mSuspendingPackage, mUserId /* suspendingUserId */, + mUserId /* targetUserId */); if (ArrayUtils.contains(errored, mSuspendedPackage)) { Slog.e(TAG, "Could not unsuspend " + mSuspendedPackage); break; @@ -350,17 +352,18 @@ public class SuspendedAppActivity extends AlertActivity } public static Intent createSuspendedAppInterceptIntent(String suspendedPackage, - String suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options, + UserPackage suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options, IntentSender onUnsuspend, int userId) { - return new Intent() + Intent intent = new Intent() .setClassName("android", SuspendedAppActivity.class.getName()) .putExtra(EXTRA_SUSPENDED_PACKAGE, suspendedPackage) .putExtra(EXTRA_DIALOG_INFO, dialogInfo) - .putExtra(EXTRA_SUSPENDING_PACKAGE, suspendingPackage) + .putExtra(EXTRA_SUSPENDING_PACKAGE, suspendingPackage.packageName) .putExtra(EXTRA_UNSUSPEND_INTENT, onUnsuspend) .putExtra(EXTRA_ACTIVITY_OPTIONS, options) .putExtra(Intent.EXTRA_USER_ID, userId) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + return intent; } } diff --git a/core/java/com/android/internal/net/TEST_MAPPING b/core/java/com/android/internal/net/TEST_MAPPING index 971ad36eecba8b73ab8a2ac983228b9dd4f47df9..f935946c95c729a27beac84cfc8decd2bf17e6ed 100644 --- a/core/java/com/android/internal/net/TEST_MAPPING +++ b/core/java/com/android/internal/net/TEST_MAPPING @@ -1,5 +1,5 @@ { - "postsubmit": [ + "presubmit": [ { "name": "FrameworksNetTests", "options": [ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index c98090638010355476917ae8f7ff7e51e2e26b52..8d8dc10951a67121639cc2f2629503bb1227401e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -48,6 +48,11 @@ public interface BackAnimation { int keyAction, @BackEvent.SwipeEdge int swipeEdge); + /** + * Called when the input pointers are pilfered. + */ + void onPilferPointers(); + /** * Sets whether the back gesture is past the trigger threshold or not. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index cf858dcb0637f5dcd8d192f8d6e6455e93c893a6..d8c691b01b61f494a6032c41b003803c273b5a1e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -111,6 +111,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont /** Tracks if we should start the back gesture on the next motion move event */ private boolean mShouldStartOnNextMoveEvent = false; + private boolean mOnBackStartDispatched = false; private final FlingAnimationUtils mFlingAnimationUtils; @@ -303,6 +304,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont /* swipeEdge = */ swipeEdge)); } + @Override + public void onPilferPointers() { + BackAnimationController.this.onPilferPointers(); + } + @Override public void setTriggerBack(boolean triggerBack) { mShellExecutor.execute(() -> BackAnimationController.this.setTriggerBack(triggerBack)); @@ -384,6 +390,16 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont return null; } + @VisibleForTesting + void onPilferPointers() { + mCurrentTracker.updateStartLocation(); + // Dispatch onBackStarted, only to app callbacks. + // System callbacks will receive onBackStarted when the remote animation starts. + if (!shouldDispatchToAnimator()) { + tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null)); + } + } + /** * Called when a new motion event needs to be transferred to this * {@link BackAnimationController} @@ -483,12 +499,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback(); // App is handling back animation. Cancel system animation latency tracking. cancelLatencyTracking(); - dispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null)); + tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null)); } } private void onMove() { - if (!mBackGestureStarted || mBackNavigationInfo == null || mActiveCallback == null) { + if (!mBackGestureStarted + || mBackNavigationInfo == null + || mActiveCallback == null + || !mOnBackStartDispatched) { return; } // Skip dispatching if the move corresponds to the queued instead of the current gesture @@ -524,13 +543,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont && mBackNavigationInfo.isPrepareRemoteAnimation(); } - private void dispatchOnBackStarted(IOnBackInvokedCallback callback, + private void tryDispatchOnBackStarted(IOnBackInvokedCallback callback, BackMotionEvent backEvent) { - if (callback == null) { + if (callback == null || mOnBackStartDispatched) { return; } try { callback.onBackStarted(backEvent); + mOnBackStartDispatched = true; } catch (RemoteException e) { Log.e(TAG, "dispatchOnBackStarted error: ", e); } @@ -828,6 +848,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont void finishBackNavigation(boolean triggerBack) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()"); mActiveCallback = null; + mShouldStartOnNextMoveEvent = false; + mOnBackStartDispatched = false; mShellBackAnimationRegistry.resetDefaultCrossActivity(); cancelLatencyTracking(); if (mBackNavigationInfo != null) { @@ -909,7 +931,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont ::onBackAnimationFinished)); if (apps.length >= 1) { - dispatchOnBackStarted( + mCurrentTracker.updateStartLocation(); + tryDispatchOnBackStarted( mActiveCallback, mCurrentTracker.createStartEvent(apps[0])); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java index 19eb928d4e30afa0cb07ef08d901dd2d73382b1e..4bd56d460818a198b4941813fa0e3bb8e10cb98c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java @@ -104,6 +104,15 @@ class TouchTracker { mStartThresholdX = mInitTouchX; } + /** Update the start location used to compute the progress + * to the latest touch location. + */ + void updateStartLocation() { + mInitTouchX = mLatestTouchX; + mInitTouchY = mLatestTouchY; + mStartThresholdX = mInitTouchX; + } + void reset() { mInitTouchX = 0; mInitTouchY = 0; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index f5c01d063707fdfd5fd6abe04f5d6e4b7f2c9602..4c477373c32cd5036112d3e7d4f008c24a5bde5d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -769,7 +769,6 @@ public class PipAnimationController { getSurfaceTransactionHelper().crop(tx, leash, destBounds); } if (mContentOverlay != null) { - mContentOverlay.onAnimationEnd(tx, destBounds); clearContentOverlay(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java index a2bd47c5285ed3715bcede1e899b0e643e06b5a6..e11e8596a7feabcd98ba64e9a93113adb114ae53 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java @@ -67,15 +67,6 @@ public abstract class PipContentOverlay { public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction); - /** - * Callback when reaches the end of animation on the internal {@link #mLeash}. - * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly - * call apply on this transaction, it should be applied on the caller side. - * @param destinationBounds {@link Rect} of the final bounds. - */ - public abstract void onAnimationEnd(SurfaceControl.Transaction atomicTx, - Rect destinationBounds); - /** A {@link PipContentOverlay} uses solid color. */ public static final class PipColorOverlay extends PipContentOverlay { private static final String TAG = PipColorOverlay.class.getSimpleName(); @@ -107,11 +98,6 @@ public abstract class PipContentOverlay { atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); } - @Override - public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { - // Do nothing. Color overlay should be fully opaque by now, ready for fade out. - } - private float[] getContentOverlayColor(Context context) { final TypedArray ta = context.obtainStyledAttributes(new int[] { android.R.attr.colorBackground }); @@ -164,11 +150,6 @@ public abstract class PipContentOverlay { Rect currentBounds, float fraction) { // Do nothing. Keep the snapshot till animation ends. } - - @Override - public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { - // Do nothing. Snapshot overlay should be fully opaque by now, ready for fade out. - } } /** A {@link PipContentOverlay} shows app icon on solid color background. */ @@ -254,11 +235,6 @@ public abstract class PipContentOverlay { .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); } - @Override - public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { - // Do nothing. Icon overlay should be fully opaque by now, ready for fade out. - } - @Override public void detach(SurfaceControl.Transaction tx) { super.detach(tx); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 743b1ea197bc60f2f46999ffec94e186eaf63b85..3635165d76ced5c24dd880024cd7221feeb19563 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -329,15 +329,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private @Surface.Rotation int mCurrentRotation; /** - * An optional overlay used to mask content changing between an app in/out of PiP, only set if - * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true, only in gesture nav. - */ - @Nullable - SurfaceControl mSwipePipToHomeOverlay; - - /** - * An optional overlay used to mask content changing between an app in/out of PiP, only set if - * {@link PipTransitionState#getInSwipePipToHomeTransition()} is false. + * An optional overlay used to mask content changing between an app in/out of PiP. */ @Nullable SurfaceControl mPipOverlay; @@ -480,7 +472,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } mPipBoundsState.setBounds(destinationBounds); - mSwipePipToHomeOverlay = overlay; + mPipOverlay = overlay; if (ENABLE_SHELL_TRANSITIONS && overlay != null) { // With Shell transition, the overlay was attached to the remote transition leash, which // will be removed when the current transition is finished, so we need to reparent it @@ -892,7 +884,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } final Rect destinationBounds = mPipBoundsState.getBounds(); - final SurfaceControl swipeToHomeOverlay = mSwipePipToHomeOverlay; + final SurfaceControl swipeToHomeOverlay = mPipOverlay; final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); mSurfaceTransactionHelper .resetScale(tx, mLeash, destinationBounds) @@ -911,7 +903,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } }, tx); mPipTransitionState.setInSwipePipToHomeTransition(false); - mSwipePipToHomeOverlay = null; + mPipOverlay = null; } private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable, @@ -1126,9 +1118,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } clearWaitForFixedRotation(); - if (mSwipePipToHomeOverlay != null) { - removeContentOverlay(mSwipePipToHomeOverlay, null /* callback */); - mSwipePipToHomeOverlay = null; + if (mPipOverlay != null) { + removeContentOverlay(mPipOverlay, null /* callback */); + mPipOverlay = null; } resetShadowRadius(); mPipTransitionState.setInSwipePipToHomeTransition(false); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 0f3c16220dee9d87285322cb9c705ea64cc89194..f5f15d81ea44597a8a7046ae48a5980e4733865c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -465,7 +465,7 @@ public class PipTransition extends PipTransitionController { mSurfaceTransactionHelper.crop(tx, leash, destinationBounds) .resetScale(tx, leash, destinationBounds) .round(tx, leash, true /* applyCornerRadius */); - if (mPipOrganizer.mSwipePipToHomeOverlay != null && !mInitBounds.isEmpty()) { + if (mPipOrganizer.mPipOverlay != null && !mInitBounds.isEmpty()) { // Resetting the scale for pinned task while re-adjusting its crop, // also scales the overlay. So we need to update the overlay leash too. Rect overlayBounds = new Rect(destinationBounds); @@ -476,7 +476,7 @@ public class PipTransition extends PipTransitionController { (destinationBounds.width() - overlaySize) / 2, (destinationBounds.height() - overlaySize) / 2); mSurfaceTransactionHelper.resetScale(tx, - mPipOrganizer.mSwipePipToHomeOverlay, overlayBounds); + mPipOrganizer.mPipOverlay, overlayBounds); } } mInitBounds.setEmpty(); @@ -615,9 +615,9 @@ public class PipTransition extends PipTransitionController { } } // if overlay is present remove it immediately, as exit transition came before it faded out - if (mPipOrganizer.mSwipePipToHomeOverlay != null) { - startTransaction.remove(mPipOrganizer.mSwipePipToHomeOverlay); - clearSwipePipToHomeOverlay(); + if (mPipOrganizer.mPipOverlay != null) { + startTransaction.remove(mPipOrganizer.mPipOverlay); + clearPipOverlay(); } if (pipChange == null) { ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, @@ -1077,7 +1077,7 @@ public class PipTransition extends PipTransitionController { if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) { mInitBounds.set(appBounds); } - final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; + final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mPipOverlay; if (swipePipToHomeOverlay != null) { // Launcher fade in the overlay on top of the fullscreen Task. It is possible we // reparent the PIP activity to a new PIP task (in case there are other activities @@ -1106,7 +1106,7 @@ public class PipTransition extends PipTransitionController { sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); if (swipePipToHomeOverlay != null) { mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay, - this::clearSwipePipToHomeOverlay /* callback */, false /* withStartDelay */); + this::clearPipOverlay /* callback */, false /* withStartDelay */); } mPipTransitionState.setInSwipePipToHomeTransition(false); } @@ -1250,8 +1250,8 @@ public class PipTransition extends PipTransitionController { mPipMenuController.updateMenuBounds(destinationBounds); } - private void clearSwipePipToHomeOverlay() { - mPipOrganizer.mSwipePipToHomeOverlay = null; + private void clearPipOverlay() { + mPipOrganizer.mPipOverlay = null; } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 0367ba160605d0860efe100e38b109ce678c3e44..d023cea6d19d807cfde8bcbe2da6a1b307754165 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -995,16 +995,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { t.show(mOpeningTasks.get(i).mTaskSurface); } for (int i = 0; i < mPausingTasks.size(); ++i) { - if (!sendUserLeaveHint && mPausingTasks.get(i).isLeaf()) { - // This means recents is not *actually* finishing, so of course we gotta - // do special stuff in WMCore to accommodate. - wct.setDoNotPip(mPausingTasks.get(i).mToken); - } - // Since we will reparent out of the leashes, pre-emptively hide the child - // surface to match the leash. Otherwise, there will be a flicker before the - // visibility gets committed in Core when using split-screen (in splitscreen, - // the leaf-tasks are not "independent" so aren't hidden by normal setup). - t.hide(mPausingTasks.get(i).mTaskSurface); + cleanUpPausingOrClosingTask(mPausingTasks.get(i), wct, t, sendUserLeaveHint); + } + for (int i = 0; i < mClosingTasks.size(); ++i) { + cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint); } if (mPipTransaction != null && sendUserLeaveHint) { SurfaceControl pipLeash = null; @@ -1053,6 +1047,20 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } + private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, + SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) { + if (!sendUserLeaveHint && task.isLeaf()) { + // This means recents is not *actually* finishing, so of course we gotta + // do special stuff in WMCore to accommodate. + wct.setDoNotPip(task.mToken); + } + // Since we will reparent out of the leashes, pre-emptively hide the child + // surface to match the leash. Otherwise, there will be a flicker before the + // visibility gets committed in Core when using split-screen (in splitscreen, + // the leaf-tasks are not "independent" so aren't hidden by normal setup). + finishTransaction.hide(task.mTaskSurface); + } + @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) { } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 77427d999aafa359dcc38d211509ee302f920e70..96e57e71f05ceebc1d0986947cb77420aa86759a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -223,6 +223,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private boolean mExitSplitScreenOnHide; private boolean mIsDividerRemoteAnimating; private boolean mIsDropEntering; + private boolean mSkipEvictingMainStageChildren; private boolean mIsExiting; private boolean mIsRootTranslucent; @VisibleForTesting @@ -468,6 +469,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } // Due to drag already pip task entering split by this method so need to reset flag here. mIsDropEntering = false; + mSkipEvictingMainStageChildren = false; return true; } @@ -572,6 +574,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } + // Don't evict the main stage children as this can race and happen after the activity is + // started into that stage + if (!isSplitScreenVisible()) { + mSkipEvictingMainStageChildren = true; + // Starting the split task without evicting children will bring the single root task + // container forward, so ensure that we hide the divider before we start animate it + setDividerVisibility(false, null); + } + // If split screen is not activated, we're expecting to open a pair of apps to split. final int extraTransitType = mMainStage.isActive() ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN; @@ -600,6 +611,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } + // Don't evict the main stage children as this can race and happen after the activity is + // started into that stage + if (!isSplitScreenVisible()) { + mSkipEvictingMainStageChildren = true; + // Starting the split task without evicting children will bring the single root task + // container forward, so ensure that we hide the divider before we start animate it + setDividerVisibility(false, null); + } + // If split screen is not activated, we're expecting to open a pair of apps to split. final int extraTransitType = mMainStage.isActive() ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN; @@ -1618,7 +1638,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Ensure to evict old splitting tasks because the new split pair might be composed by // one of the splitting tasks, evicting the task when finishing entering transition // won't guarantee to put the task to the indicated new position. - if (!mIsDropEntering) { + if (!mSkipEvictingMainStageChildren) { mMainStage.evictAllChildren(wct); } mMainStage.reparentTopTask(wct); @@ -1680,6 +1700,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, finishT.show(mRootTaskLeash); setSplitsVisible(true); mIsDropEntering = false; + mSkipEvictingMainStageChildren = false; mSplitRequest = null; updateRecentTasksSplitPair(); if (!mLogger.hasStartedSession()) { @@ -1929,6 +1950,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mIsDropEntering) { updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); mIsDropEntering = false; + mSkipEvictingMainStageChildren = false; } else { mShowDecorImmediately = true; mSplitLayout.flingDividerToCenter(); @@ -2123,6 +2145,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mIsDropEntering) { updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); mIsDropEntering = false; + mSkipEvictingMainStageChildren = false; } else { mShowDecorImmediately = true; mSplitLayout.flingDividerToCenter(); @@ -3245,6 +3268,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) { if (!isSplitScreenVisible()) { mIsDropEntering = true; + mSkipEvictingMainStageChildren = true; } if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) { // If split running background, exit split first. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index e03f82526bdb4ac508db0b515ffac347bb591083..34c015f05c681a0d361f5e1b5f3c076606a4f7a9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -330,6 +330,11 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { continue; } if (isHide) { + if (pending.mType == TRANSIT_TO_BACK) { + // TO_BACK is only used when setting the task view visibility immediately, + // so in that case we can also hide the surface immediately + startTransaction.hide(chg.getLeash()); + } tv.prepareHideAnimation(finishTransaction); } else { tv.prepareCloseAnimation(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index 20ff79f7318e1fa589e9b6be9e8ed4579f0f373d..98d343b6676082335e6183e6cdde452fbd6e0362 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -18,6 +18,7 @@ package com.android.wm.shell.unfold; import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; @@ -245,23 +246,29 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene public boolean shouldPlayUnfoldAnimation(@NonNull TransitionInfo transitionInfo) { // Unfold animation won't play when animations are disabled if (!ValueAnimator.areAnimatorsEnabled()) return false; + // Only handle transitions that are marked as physical display switch + // See PhysicalDisplaySwitchTransitionLauncher for the conditions + if ((transitionInfo.getFlags() & TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH) == 0) return false; for (int i = 0; i < transitionInfo.getChanges().size(); i++) { final TransitionInfo.Change change = transitionInfo.getChanges().get(i); - if ((change.getFlags() & TransitionInfo.FLAG_IS_DISPLAY) != 0) { - if (change.getEndAbsBounds() == null || change.getStartAbsBounds() == null) { - continue; - } + // We are interested only in display container changes + if ((change.getFlags() & TransitionInfo.FLAG_IS_DISPLAY) == 0) { + continue; + } - // Handle only unfolding, currently we don't have an animation when folding - final int afterArea = - change.getEndAbsBounds().width() * change.getEndAbsBounds().height(); - final int beforeArea = change.getStartAbsBounds().width() - * change.getStartAbsBounds().height(); + // Handle only unfolding, currently we don't have an animation when folding + if (change.getEndAbsBounds() == null || change.getStartAbsBounds() == null) { + continue; + } - if (afterArea > beforeArea) { - return true; - } + final int afterArea = + change.getEndAbsBounds().width() * change.getEndAbsBounds().height(); + final int beforeArea = change.getStartAbsBounds().width() + * change.getStartAbsBounds().height(); + + if (afterArea > beforeArea) { + return true; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 0395a9b78b2ba02045970ae793b19b850b8f8ba5..771876f7ce5dbd1270ba011991e014b5994120be 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -182,8 +182,7 @@ public class BackAnimationControllerTest extends ShellTestCase { } private void triggerBackGesture() { - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - doMotionEvent(MotionEvent.ACTION_MOVE, 0); + doStartEvents(0, 0); mController.setTriggerBack(true); } @@ -244,10 +243,7 @@ public class BackAnimationControllerTest extends ShellTestCase { /* enableAnimation = */ true, /* isAnimationCallback = */ false); - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - - // Check that back start and progress is dispatched when first move. - doMotionEvent(MotionEvent.ACTION_MOVE, 100); + doStartEvents(0, 100); simulateRemoteAnimationStart(); @@ -270,10 +266,8 @@ public class BackAnimationControllerTest extends ShellTestCase { /* enableAnimation = */ true, /* isAnimationCallback = */ true); - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - // Check that back start and progress is dispatched when first move. - doMotionEvent(MotionEvent.ACTION_MOVE, 100, 3000); + doStartEvents(0, 100); simulateRemoteAnimationStart(); @@ -359,8 +353,7 @@ public class BackAnimationControllerTest extends ShellTestCase { .injectInputEvent(any(KeyEvent.class), any(Integer.class)); // Verify that we start accepting gestures again once transition finishes. - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - doMotionEvent(MotionEvent.ACTION_MOVE, 100); + doStartEvents(0, 100); simulateRemoteAnimationStart(); verify(mAnimatorCallback).onBackStarted(any()); @@ -399,8 +392,7 @@ public class BackAnimationControllerTest extends ShellTestCase { .injectInputEvent(any(KeyEvent.class), any(Integer.class)); // Verify that we start accepting gestures again once transition finishes. - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - doMotionEvent(MotionEvent.ACTION_MOVE, 100); + doStartEvents(0, 100); simulateRemoteAnimationStart(); verify(mAnimatorCallback).onBackStarted(any()); @@ -427,8 +419,7 @@ public class BackAnimationControllerTest extends ShellTestCase { mShellExecutor.flushAll(); reset(mAnimatorCallback); - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - doMotionEvent(MotionEvent.ACTION_MOVE, 100); + doStartEvents(0, 100); simulateRemoteAnimationStart(); verify(mAnimatorCallback).onBackStarted(any()); } @@ -441,9 +432,7 @@ public class BackAnimationControllerTest extends ShellTestCase { /* enableAnimation = */ true, /* isAnimationCallback = */ false); - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - // Check that back start and progress is dispatched when first move. - doMotionEvent(MotionEvent.ACTION_MOVE, 100); + doStartEvents(0, 100); simulateRemoteAnimationStart(); verify(mAnimatorCallback).onBackStarted(any()); @@ -563,10 +552,8 @@ public class BackAnimationControllerTest extends ShellTestCase { /* enableAnimation = */ true, /* isAnimationCallback = */ false); - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - // Check that back start and progress is dispatched when first move. - doMotionEvent(MotionEvent.ACTION_MOVE, 100); + doStartEvents(0, 100); simulateRemoteAnimationStart(); @@ -593,6 +580,15 @@ public class BackAnimationControllerTest extends ShellTestCase { /* swipeEdge */ BackEvent.EDGE_LEFT); } + /** + * Simulate event sequence that starts a back navigation. + */ + private void doStartEvents(int startX, int moveX) { + doMotionEvent(MotionEvent.ACTION_DOWN, startX); + mController.onPilferPointers(); + doMotionEvent(MotionEvent.ACTION_MOVE, moveX); + } + private void simulateRemoteAnimationStart() throws RemoteException { RemoteAnimationTarget animationTarget = createAnimationTarget(); RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget}; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java index fc1fe1cd0acc9a44ef63d4477132b1e396aebcfe..c5e229feaba7ded4d602bd0e7659d43490f08dea 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java @@ -18,12 +18,11 @@ package com.android.wm.shell.unfold; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH; import static android.view.WindowManager.TRANSIT_NONE; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -40,22 +39,17 @@ import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import com.android.window.flags.FakeFeatureFlagsImpl; -import com.android.window.flags.FeatureFlags; -import com.android.window.flags.Flags; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.sysui.ShellInit; -import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.transition.TransitionInfoBuilder; +import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import java.util.ArrayList; import java.util.List; @@ -195,6 +189,22 @@ public class UnfoldTransitionHandlerTest { assertThat(animationStarted).isTrue(); } + @Test + public void startAnimation_differentTransitionFromRequestWithResize_doesNotStartAnimation() { + mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo()); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + + boolean animationStarted = mUnfoldTransitionHandler.startAnimation( + mTransition, + createDisplayResizeTransitionInfo(), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + + assertThat(animationStarted).isFalse(); + } + @Test public void startAnimation_differentTransitionFromRequestWithoutUnfold_doesNotStart() { mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo()); @@ -403,24 +413,18 @@ public class UnfoldTransitionHandlerTest { } } - static class TestCase { - private final boolean mShouldHandleMixedUnfold; - - public TestCase(boolean shouldHandleMixedUnfold) { - mShouldHandleMixedUnfold = shouldHandleMixedUnfold; - } - - public boolean mixedUnfoldFlagEnabled() { - return mShouldHandleMixedUnfold; - } - - @Override - public String toString() { - return "shouldHandleMixedUnfold flag = " + mShouldHandleMixedUnfold; - } + private TransitionInfo createUnfoldTransitionInfo() { + TransitionInfo transitionInfo = new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0); + TransitionInfo.Change change = new TransitionInfo.Change(null, mock(SurfaceControl.class)); + change.setStartAbsBounds(new Rect(0, 0, 10, 10)); + change.setEndAbsBounds(new Rect(0, 0, 100, 100)); + change.setFlags(TransitionInfo.FLAG_IS_DISPLAY); + transitionInfo.addChange(change); + transitionInfo.setFlags(TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH); + return transitionInfo; } - private TransitionInfo createUnfoldTransitionInfo() { + private TransitionInfo createDisplayResizeTransitionInfo() { TransitionInfo transitionInfo = new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0); TransitionInfo.Change change = new TransitionInfo.Change(null, mock(SurfaceControl.class)); change.setStartAbsBounds(new Rect(0, 0, 10, 10)); diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 901ea46ba38bb92b5d3cfb7744e649617db015ae..a8ffd2b4dd8fc283a8f8a7460bf2e6ac3ed6c866 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -110,6 +110,10 @@ interface ITvInputManager { void pauseRecording(in IBinder sessionToken, in Bundle params, int userId); void resumeRecording(in IBinder sessionToken, in Bundle params, int userId); + // For playback control + void startPlayback(in IBinder sessionToken, int userId); + void stopPlayback(in IBinder sessionToken, int mode, int userId); + // For broadcast info void requestBroadcastInfo(in IBinder sessionToken, in BroadcastInfoRequest request, int userId); void removeBroadcastInfo(in IBinder sessionToken, int id, int userId); diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index 5246f5c4dc1ef40256ecb75b29f047c980a7235a..e37ee6e342e54a558585f92bd7e3b0d4f33d77c9 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -63,6 +63,9 @@ oneway interface ITvInputSession { void timeShiftSetMode(int mode); void timeShiftEnablePositionTracking(boolean enable); + void startPlayback(); + void stopPlayback(int mode); + // For the recording session void startRecording(in Uri programUri, in Bundle params); void stopRecording(); diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index d749b91e388949da73746b588efd131f12bb9dd2..ae3ee6535c7179db5ab8ff2bbc8ff0b2c0c02770 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -79,6 +79,8 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_TIME_SHIFT_SET_MODE = 30; private static final int DO_SET_TV_MESSAGE_ENABLED = 31; private static final int DO_NOTIFY_TV_MESSAGE = 32; + private static final int DO_STOP_PLAYBACK = 33; + private static final int DO_START_PLAYBACK = 34; private final boolean mIsRecordingSession; private final HandlerCaller mCaller; @@ -286,6 +288,14 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2); break; } + case DO_STOP_PLAYBACK: { + mTvInputSessionImpl.stopPlayback(msg.arg1); + break; + } + case DO_START_PLAYBACK: { + mTvInputSessionImpl.startPlayback(); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -483,6 +493,17 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand enabled)); } + @Override + public void stopPlayback(int mode) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_STOP_PLAYBACK, mode)); + } + + @Override + public void startPlayback() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_START_PLAYBACK)); + } + + private final class TvInputEventReceiver extends InputEventReceiver { TvInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 631ab9a2693de129730f9516243ee6aefa6db7ef..c685a5adb08b9f25120ba40d4bace53e04038516 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -339,6 +339,14 @@ public final class TvInputManager { */ public static final int VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN = VIDEO_UNAVAILABLE_REASON_END; + /** + * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and + * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because + * it has been stopped by stopPlayback. + * @hide + */ + public static final int VIDEO_UNAVAILABLE_REASON_STOPPED = 19; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({TIME_SHIFT_STATUS_UNKNOWN, TIME_SHIFT_STATUS_UNSUPPORTED, @@ -3302,6 +3310,30 @@ public final class TvInputManager { } } + void stopPlayback(int mode) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.stopPlayback(mToken, mode, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + void startPlayback() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.startPlayback(mToken, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Sends TV messages to the service for testing purposes */ diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 720d9a6291de60623243db5f8cabd4e1255bf210..55fa51755177927186d4c7fedd5e589ba6f896cb 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -34,6 +34,7 @@ import android.graphics.Rect; import android.hardware.hdmi.HdmiDeviceInfo; import android.media.AudioPresentation; import android.media.PlaybackParams; +import android.media.tv.interactive.TvInteractiveAppService; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; @@ -1530,6 +1531,32 @@ public abstract class TvInputService extends Service { @NonNull Bundle data) { } + /** + * Called when the application requests playback of the Audio, Video, and CC streams to be + * stopped, but the metadata should continue to be filtered. + * + * <p>The metadata that will continue to be filtered includes the PSI + * (Program specific information) and SI (Service Information), part of ISO/IEC 13818-1. + * + * <p> Note that this is different form {@link #timeShiftPause()} as should release the + * stream, making it impossible to resume from this position again. + * @param mode + * @hide + */ + public void onStopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) { + } + + /** + * Starts playback of the Audio, Video, and CC streams. + * + * <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be + * used after stopping playback. This is used to restart playback from the current position + * in the live broadcast. + * @hide + */ + public void onStartPlayback() { + } + /** * Called when the application requests to play a given recorded TV program. * @@ -1992,6 +2019,20 @@ public abstract class TvInputService extends Service { } } + /** + * Calls {@link #onStopPlayback(int)}. + */ + void stopPlayback(int mode) { + onStopPlayback(mode); + } + + /** + * Calls {@link #onStartPlayback()}. + */ + void startPlayback() { + onStartPlayback(); + } + /** * Calls {@link #onTimeShiftPlay(Uri)}. */ diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 196b5c3112c0c43f81f3ba6261de02eb4b8c9cea..233f966755433c206621e283ef90d6fd840ef0c1 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -37,6 +37,7 @@ import android.media.PlaybackParams; import android.media.tv.TvInputManager.Session; import android.media.tv.TvInputManager.Session.FinishedInputEventCallback; import android.media.tv.TvInputManager.SessionCallback; +import android.media.tv.interactive.TvInteractiveAppService; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -643,6 +644,35 @@ public class TvView extends ViewGroup { } } + /** + * Stops playback of the Audio, Video, and CC streams, but continue filtering the metadata. + * + * <p>The metadata that will continue to be filtered includes the PSI + * (Program specific information) and SI (Service Information), part of ISO/IEC 13818-1. + * + * <p> Note that this is different form {@link #timeShiftPause()} as this completely drops + * the stream, making it impossible to resume from this position again. + * @hide + */ + public void stopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) { + if (mSession != null) { + mSession.stopPlayback(mode); + } + } + + /** + * Starts playback of the Audio, Video, and CC streams. + * + * <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be + * used after stopping playback. This is used to restart playback from the current position + * in the live broadcast. + * @hide + */ + public void startPlayback() { + if (mSession != null) { + mSession.startPlayback(); + } + } /** * Sends TV messages to the session for testing purposes diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 0abf285bd19cb55ce1969b01bc3944db63e57a75..66282dc209ed2a50659fc02b8bd5798f22ef1106 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -527,6 +527,15 @@ public class CompanionDeviceActivity extends FragmentActivity implements final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile)); + // No need to show permission consent dialog if it is a isSkipPrompt(true) + // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt. + if (mRequest.isSkipPrompt()) { + Log.d(TAG, "Skipping the permission consent dialog."); + mSingleDeviceSpinner.setVisibility(View.GONE); + onUserSelectedDevice(mSelectedDevice); + return; + } + updatePermissionUi(); mProfileIcon.setImageDrawable(profileIcon); @@ -598,6 +607,14 @@ public class CompanionDeviceActivity extends FragmentActivity implements Log.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString()); + // No need to show permission consent dialog if it is a isSkipPrompt(true) + // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt. + if (mRequest.isSkipPrompt()) { + Log.d(TAG, "Skipping the permission consent dialog."); + onUserSelectedDevice(mSelectedDevice); + return; + } + updatePermissionUi(); mSummary.setVisibility(View.VISIBLE); @@ -615,14 +632,6 @@ public class CompanionDeviceActivity extends FragmentActivity implements this, PROFILE_TITLES.get(deviceProfile), mAppLabel, remoteDeviceName); final Spanned summary; - // No need to show permission consent dialog if it is a isSkipPrompt(true) - // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt. - if (mRequest.isSkipPrompt()) { - mSingleDeviceSpinner.setVisibility(View.GONE); - onUserSelectedDevice(mSelectedDevice); - return; - } - if (deviceProfile == null && mRequest.isSingleDevice()) { summary = getHtmlFromResources(this, summaryResourceId, remoteDeviceName); mConstraintList.setVisibility(View.GONE); diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt index f216abb66a649ab68705068d6f285272287e9456..66bd6f502274342b4dc9238f04b8e77669470cc7 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt @@ -28,7 +28,14 @@ import androidx.compose.material.icons.outlined.Shield import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.common.SettingsEntryBuilder @@ -41,14 +48,14 @@ import com.android.settingslib.spa.gallery.R import com.android.settingslib.spa.widget.card.CardButton import com.android.settingslib.spa.widget.card.CardModel import com.android.settingslib.spa.widget.card.SettingsCard -import com.android.settingslib.spa.widget.card.SettingsCollapsibleCard import com.android.settingslib.spa.widget.card.SettingsCardContent +import com.android.settingslib.spa.widget.card.SettingsCollapsibleCard import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.scaffold.RegularScaffold object CardPageProvider : SettingsPageProvider { - override val name = "CardPage" + override val name = "Card" override fun getTitle(arguments: Bundle?) = TITLE @@ -79,10 +86,13 @@ object CardPageProvider : SettingsPageProvider { @Composable private fun SettingsCardWithoutIcon() { + var isVisible by rememberSaveable { mutableStateOf(true) } SettingsCard( CardModel( title = stringResource(R.string.sample_title), text = stringResource(R.string.sample_text), + isVisible = { isVisible }, + onDismiss = { isVisible = false }, buttons = listOf( CardButton(text = "Action") {}, ), @@ -92,21 +102,23 @@ object CardPageProvider : SettingsPageProvider { @Composable fun SampleSettingsCollapsibleCard() { - SettingsCollapsibleCard( - title = "More alerts", - imageVector = Icons.Outlined.Error, - models = listOf( + val context = LocalContext.current + var isVisible0 by rememberSaveable { mutableStateOf(true) } + val cards = remember { + mutableStateListOf( CardModel( - title = stringResource(R.string.sample_title), - text = stringResource(R.string.sample_text), + title = context.getString(R.string.sample_title), + text = context.getString(R.string.sample_text), imageVector = Icons.Outlined.PowerOff, + isVisible = { isVisible0 }, + onDismiss = { isVisible0 = false }, buttons = listOf( CardButton(text = "Action") {}, ) ), CardModel( - title = stringResource(R.string.sample_title), - text = stringResource(R.string.sample_text), + title = context.getString(R.string.sample_title), + text = context.getString(R.string.sample_text), imageVector = Icons.Outlined.Shield, buttons = listOf( CardButton(text = "Action") {}, @@ -114,6 +126,11 @@ object CardPageProvider : SettingsPageProvider { ) ) ) + } + SettingsCollapsibleCard( + title = "More alerts", + imageVector = Icons.Outlined.Error, + models = cards.toList() ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index c143390f269cd0993954b5686567ea12045168d7..993cb4ac85e50622736f8000ffb8379ae6bcc547 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -37,6 +37,7 @@ object SettingsDimension { val itemPaddingAround = 8.dp val itemDividerHeight = 32.dp + val iconSmall = 16.dp val iconLarge = 48.dp /** The size when app icon is displayed in list. */ diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt index c113f4332fc38d339e98d3bfdf90892276c3795a..b18a1bc01388ba50fdb1830e4da6fe84292b652b 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt @@ -28,5 +28,14 @@ data class CardModel( val title: String, val text: String, val imageVector: ImageVector? = null, + val isVisible: () -> Boolean = { true }, + + /** + * A dismiss button will be displayed if this is not null. + * + * And this callback will be called when user clicks the button. + */ + val onDismiss: (() -> Unit)? = null, + val buttons: List<CardButton> = emptyList(), ) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt index 43792787c5aa9a09ce32b811a819bbffa89e1c4e..7eec8888f025964f3b4a0239c0da63a666250b28 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt @@ -16,28 +16,35 @@ package com.android.settingslib.spa.widget.card +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.android.settingslib.spa.debug.UiModePreviews import com.android.settingslib.spa.framework.theme.SettingsDimension @@ -87,19 +94,30 @@ fun SettingsCard(model: CardModel) { @Composable internal fun SettingsCardImpl(model: CardModel) { - SettingsCardContent { - Column( - modifier = Modifier.padding(SettingsDimension.itemPaddingStart), - verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround) - ) { - CardIcon(model.imageVector) - SettingsTitle(model.title) - SettingsBody(model.text) - Buttons(model.buttons) + AnimatedVisibility(visible = model.isVisible()) { + SettingsCardContent { + Column( + modifier = Modifier.padding(SettingsDimension.itemPaddingStart), + verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround) + ) { + CardHeader(model.imageVector, model.onDismiss) + SettingsTitle(model.title) + SettingsBody(model.text) + Buttons(model.buttons) + } } } } +@Composable +fun CardHeader(imageVector: ImageVector?, onDismiss: (() -> Unit)? = null) { + Row(Modifier.fillMaxWidth()) { + CardIcon(imageVector) + Spacer(modifier = Modifier.weight(1f)) + DismissButton(onDismiss) + } +} + @Composable private fun CardIcon(imageVector: ImageVector?) { if (imageVector != null) { @@ -112,6 +130,28 @@ private fun CardIcon(imageVector: ImageVector?) { } } +@Composable +private fun DismissButton(onDismiss: (() -> Unit)?) { + if (onDismiss == null) return + Surface( + shape = CircleShape, + color = MaterialTheme.colorScheme.secondaryContainer, + ) { + IconButton( + onClick = onDismiss, + modifier = Modifier.size(SettingsDimension.itemIconSize) + ) { + Icon( + imageVector = Icons.Outlined.Close, + contentDescription = stringResource( + androidx.compose.material3.R.string.m3c_snackbar_dismiss + ), + modifier = Modifier.size(SettingsDimension.iconSmall), + ) + } + } +} + @Composable private fun Buttons(buttons: List<CardButton>) { if (buttons.isNotEmpty()) { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt index bf192a18679e3eaadf0b8d6245316c68b47e23d0..6e36490beac7278b88c23a14b44ed6c21ff58dce 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt @@ -58,7 +58,7 @@ fun SettingsCollapsibleCard( var expanded by rememberSaveable { mutableStateOf(false) } SettingsCard { SettingsCardContent { - Header(title, imageVector, models.size, expanded) { expanded = it } + Header(title, imageVector, models.count { it.isVisible() }, expanded) { expanded = it } } AnimatedVisibility(expanded) { Column { diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt index fd3ae49d93c850da812be2207763c9dd6525323e..beb9433cdbf0c1282fa90090d05fb3f4df45c538 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt @@ -16,10 +16,18 @@ package com.android.settingslib.spa.widget.card +import android.content.Context +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.isNotDisplayed import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -31,6 +39,8 @@ class SettingsCardTest { @get:Rule val composeTestRule = createComposeRule() + private val context: Context = ApplicationProvider.getApplicationContext() + @Test fun settingsCard_titleDisplayed() { composeTestRule.setContent { @@ -96,6 +106,27 @@ class SettingsCardTest { assertThat(buttonClicked).isTrue() } + @Test + fun settingsCard_dismiss() { + composeTestRule.setContent { + var isVisible by remember { mutableStateOf(true) } + SettingsCard( + CardModel( + title = TITLE, + text = "", + isVisible = { isVisible }, + onDismiss = { isVisible = false }, + ) + ) + } + + composeTestRule.onNodeWithContentDescription( + context.getString(androidx.compose.material3.R.string.m3c_snackbar_dismiss) + ).performClick() + + composeTestRule.onNodeWithText(TEXT).isNotDisplayed() + } + private companion object { const val TITLE = "Title" const val TEXT = "Text" diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCardTest.kt index efe1c701b1f6d83a67fbc7dd38735e26274f1503..aba9d7be1e91eb161d6bd4fa33b211dd048680c9 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCardTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCardTest.kt @@ -16,12 +16,20 @@ package com.android.settingslib.spa.widget.card +import android.content.Context import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Error +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.isNotDisplayed import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Rule import org.junit.Test @@ -32,6 +40,8 @@ class SettingsCollapsibleCardTest { @get:Rule val composeTestRule = createComposeRule() + private val context: Context = ApplicationProvider.getApplicationContext() + @Test fun settingsCollapsibleCard_titleDisplayed() { setContent() @@ -62,8 +72,22 @@ class SettingsCollapsibleCardTest { composeTestRule.onNodeWithText(CARD_TEXT).assertIsDisplayed() } + @Test + fun settingsCollapsibleCard_dismiss() { + setContent() + composeTestRule.onNodeWithText(TITLE).performClick() + + composeTestRule.onNodeWithContentDescription( + context.getString(androidx.compose.material3.R.string.m3c_snackbar_dismiss) + ).performClick() + + composeTestRule.onNodeWithText(CARD_TEXT).isNotDisplayed() + composeTestRule.onNodeWithText("0").assertIsDisplayed() + } + private fun setContent() { composeTestRule.setContent { + var isVisible by rememberSaveable { mutableStateOf(true) } SettingsCollapsibleCard( title = TITLE, imageVector = Icons.Outlined.Error, @@ -71,6 +95,8 @@ class SettingsCollapsibleCardTest { CardModel( title = "", text = CARD_TEXT, + isVisible = { isVisible }, + onDismiss = { isVisible = false }, ) ), ) diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 2cb44ec39a23e6156e26afcab1c376b95800f892..49ac0f864ed724aa71419fb02f1a5aba2d393dbd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -28,7 +28,9 @@ import android.bluetooth.BluetoothLeBroadcast; import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; +import android.bluetooth.BluetoothLeBroadcastSettings; import android.bluetooth.BluetoothLeBroadcastSubgroup; +import android.bluetooth.BluetoothLeBroadcastSubgroupSettings; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; import android.content.ContentResolver; @@ -42,10 +44,13 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.settingslib.R; +import com.google.common.collect.ImmutableList; + import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -376,6 +381,77 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { (mBroadcastCode != null && mBroadcastCode.length > 0) ? mBroadcastCode : null); } + /** + * Start the private Broadcast for personal audio sharing or qr code sharing. + * + * <p>The broadcast will use random string for both broadcast name and subgroup program info; + * The broadcast will use random string for broadcast code; The broadcast will only have one + * subgroup due to system limitation; The subgroup language will be null. + * + * <p>If the system started the LE Broadcast, then the system calls the corresponding callback + * {@link BluetoothLeBroadcast.Callback}. + */ + public void startPrivateBroadcast(int quality) { + mNewAppSourceName = "Sharing audio"; + if (mServiceBroadcast == null) { + Log.d(TAG, "The BluetoothLeBroadcast is null when starting the private broadcast."); + return; + } + if (mServiceBroadcast.getAllBroadcastMetadata().size() + >= mServiceBroadcast.getMaximumNumberOfBroadcasts()) { + Log.d(TAG, "Skip starting the broadcast due to number limit."); + return; + } + String programInfo = getProgramInfo(); + if (DEBUG) { + Log.d(TAG, "startBroadcast: language = null ,programInfo = " + programInfo); + } + // Current broadcast framework only support one subgroup + BluetoothLeBroadcastSubgroupSettings subgroupSettings = + buildBroadcastSubgroupSettings(/* language= */ null, programInfo, quality); + BluetoothLeBroadcastSettings settings = + buildBroadcastSettings( + true, // TODO: set to false after framework fix + TextUtils.isEmpty(programInfo) ? null : programInfo, + (mBroadcastCode != null && mBroadcastCode.length > 0) + ? mBroadcastCode + : null, + ImmutableList.of(subgroupSettings)); + mServiceBroadcast.startBroadcast(settings); + } + + private BluetoothLeBroadcastSettings buildBroadcastSettings( + boolean isPublic, + @Nullable String broadcastName, + @Nullable byte[] broadcastCode, + List<BluetoothLeBroadcastSubgroupSettings> subgroupSettingsList) { + BluetoothLeBroadcastSettings.Builder builder = + new BluetoothLeBroadcastSettings.Builder() + .setPublicBroadcast(isPublic) + .setBroadcastName(broadcastName) + .setBroadcastCode(broadcastCode); + for (BluetoothLeBroadcastSubgroupSettings subgroupSettings : subgroupSettingsList) { + builder.addSubgroupSettings(subgroupSettings); + } + return builder.build(); + } + + private BluetoothLeBroadcastSubgroupSettings buildBroadcastSubgroupSettings( + @Nullable String language, @Nullable String programInfo, int quality) { + BluetoothLeAudioContentMetadata metadata = + new BluetoothLeAudioContentMetadata.Builder() + .setLanguage(language) + .setProgramInfo(programInfo) + .build(); + // Current broadcast framework only support one subgroup, thus we still maintain the latest + // metadata to keep legacy UI working. + mBluetoothLeAudioContentMetadata = metadata; + return new BluetoothLeBroadcastSubgroupSettings.Builder() + .setPreferredQuality(quality) + .setContentMetadata(mBluetoothLeAudioContentMetadata) + .build(); + } + public String getProgramInfo() { return mProgramInfo; } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index a26b311b1f8f78dacb0ed334fe7d9153203c3482..facb244a548952fb4f974ca83ffcac3c0af591cd 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -60,6 +60,13 @@ flag { bug: "308623704" } +flag { + name: "refactor_get_current_user" + namespace: "systemui" + description: "KeyguardUpdateMonitor.getCurrentUser() was providing outdated results." + bug: "305984787" +} + flag { name: "notification_throttle_hun" namespace: "systemui" 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 e538e093e60f567e17e2b069ce2c5799fa42e725..2944bd9f9a8e5c069a1862092498eb23f30d40c7 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 @@ -174,8 +174,8 @@ private fun <T> computeValue( lerp: (T, T, Float) -> T, canOverflow: Boolean, ): T { - val state = layoutImpl.state.transitionState - if (state !is TransitionState.Transition || !layoutImpl.isTransitionReady(state)) { + val transition = layoutImpl.state.currentTransition + if (transition == null || !layoutImpl.isTransitionReady(transition)) { return sharedValue.value } @@ -191,10 +191,11 @@ private fun <T> computeValue( return value as Element.SharedValue<T> } - val fromValue = sceneValue(state.fromScene) - val toValue = sceneValue(state.toScene) + val fromValue = sceneValue(transition.fromScene) + val toValue = sceneValue(transition.toScene) return if (fromValue != null && toValue != null) { - val progress = if (canOverflow) state.progress else state.progress.coerceIn(0f, 1f) + val progress = + if (canOverflow) transition.progress else transition.progress.coerceIn(0f, 1f) lerp(fromValue.value, toValue.value, progress) } else if (fromValue != null) { fromValue.value diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index de69c37d4630021257206400c96ec0cb73a3e766..ba6d00e3b7f5890aa02f8a73125d531da81f1baf 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -28,11 +28,11 @@ import kotlinx.coroutines.launch * the currently running transition, if there is one. */ internal fun CoroutineScope.animateToScene( - layoutImpl: SceneTransitionLayoutImpl, + layoutState: SceneTransitionLayoutStateImpl, target: SceneKey, ) { - val state = layoutImpl.state.transitionState - if (state.currentScene == target) { + val transitionState = layoutState.transitionState + if (transitionState.currentScene == target) { // This can happen in 3 different situations, for which there isn't anything else to do: // 1. There is no ongoing transition and [target] is already the current scene. // 2. The user is swiping to [target] from another scene and released their pointer such @@ -44,44 +44,47 @@ internal fun CoroutineScope.animateToScene( return } - when (state) { - is TransitionState.Idle -> animate(layoutImpl, target) + when (transitionState) { + is TransitionState.Idle -> animate(layoutState, target) is TransitionState.Transition -> { // A transition is currently running: first check whether `transition.toScene` or // `transition.fromScene` is the same as our target scene, in which case the transition // can be accelerated or reversed to end up in the target state. - if (state.toScene == target) { + if (transitionState.toScene == target) { // The user is currently swiping to [target] but didn't release their pointer yet: // animate the progress to `1`. - check(state.fromScene == state.currentScene) - val progress = state.progress + check(transitionState.fromScene == transitionState.currentScene) + val progress = transitionState.progress if ((1f - progress).absoluteValue < ProgressVisibilityThreshold) { - // The transition is already finished (progress ~= 1): no need to animate. - layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene) + // The transition is already finished (progress ~= 1): no need to animate. We + // finish the current transition early to make sure that the current state + // change is committed. + layoutState.finishTransition(transitionState, transitionState.currentScene) } else { // The transition is in progress: start the canned animation at the same // progress as it was in. // TODO(b/290184746): Also take the current velocity into account. - animate(layoutImpl, target, startProgress = progress) + animate(layoutState, target, startProgress = progress) } return } - if (state.fromScene == target) { + if (transitionState.fromScene == target) { // There is a transition from [target] to another scene: simply animate the same // transition progress to `0`. - check(state.toScene == state.currentScene) - val progress = state.progress + check(transitionState.toScene == transitionState.currentScene) + val progress = transitionState.progress if (progress.absoluteValue < ProgressVisibilityThreshold) { - // The transition is at progress ~= 0: no need to animate. - layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene) + // The transition is at progress ~= 0: no need to animate.We finish the current + // transition early to make sure that the current state change is committed. + layoutState.finishTransition(transitionState, transitionState.currentScene) } else { // TODO(b/290184746): Also take the current velocity into account. - animate(layoutImpl, target, startProgress = progress, reversed = true) + animate(layoutState, target, startProgress = progress, reversed = true) } return @@ -89,27 +92,22 @@ internal fun CoroutineScope.animateToScene( // Generic interruption; the current transition is neither from or to [target]. // TODO(b/290930950): Better handle interruptions here. - animate(layoutImpl, target) + animate(layoutState, target) } } } private fun CoroutineScope.animate( - layoutImpl: SceneTransitionLayoutImpl, + layoutState: SceneTransitionLayoutStateImpl, target: SceneKey, startProgress: Float = 0f, reversed: Boolean = false, ) { - val fromScene = layoutImpl.state.transitionState.currentScene + val fromScene = layoutState.transitionState.currentScene val isUserInput = - (layoutImpl.state.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput + (layoutState.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput ?: false - val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec - val visibilityThreshold = - (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold - val animatable = Animatable(startProgress, visibilityThreshold = visibilityThreshold) - val targetProgress = if (reversed) 0f else 1f val transition = if (reversed) { @@ -119,7 +117,6 @@ private fun CoroutineScope.animate( currentScene = target, isInitiatedByUserInput = isUserInput, isUserInputOngoing = false, - animatable = animatable, ) } else { OneOffTransition( @@ -128,21 +125,27 @@ private fun CoroutineScope.animate( currentScene = target, isInitiatedByUserInput = isUserInput, isUserInputOngoing = false, - animatable = animatable, ) } - // Change the current layout state to use this new transition. - layoutImpl.state.transitionState = transition + // Change the current layout state to start this new transition. This will compute the + // TransformationSpec associated to this transition, which we need to initialize the Animatable + // that will actually animate it. + layoutState.startTransition(transition) + + // The transformation now contains the spec that we should use to instantiate the Animatable. + val animationSpec = layoutState.transformationSpec.progressSpec + val visibilityThreshold = + (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold + val animatable = + Animatable(startProgress, visibilityThreshold = visibilityThreshold).also { + transition.animatable = it + } // Animate the progress to its target value. launch { animatable.animateTo(targetProgress, animationSpec) - - // Unless some other external state change happened, the state should now be idle. - if (layoutImpl.state.transitionState == transition) { - layoutImpl.state.transitionState = TransitionState.Idle(target) - } + layoutState.finishTransition(transition, target) } } @@ -152,8 +155,16 @@ private class OneOffTransition( override val currentScene: SceneKey, override val isInitiatedByUserInput: Boolean, override val isUserInputOngoing: Boolean, - private val animatable: Animatable<Float, AnimationVector1D>, ) : TransitionState.Transition(fromScene, toScene) { + /** + * The animatable used to animate this transition. + * + * Note: This is lateinit because we need to first create this Transition object so that + * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to + * it, which is need to initialize this Animatable. + */ + lateinit var animatable: Animatable<Float, AnimationVector1D> + override val progress: Float get() = animatable.value } 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 431a8aef6d3daeb2e4d0fcb4254f39b3b74cd75c..5dc1079e8b568383131185aef57590be756784a6 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 @@ -181,15 +181,11 @@ private data class ElementModifier( } internal class ElementNode( - layoutImpl: SceneTransitionLayoutImpl, - scene: Scene, - element: Element, - sceneValues: Element.TargetValues, + private var layoutImpl: SceneTransitionLayoutImpl, + private var scene: Scene, + private var element: Element, + private var sceneValues: Element.TargetValues, ) : Modifier.Node(), DrawModifierNode { - private var layoutImpl: SceneTransitionLayoutImpl = layoutImpl - private var scene: Scene = scene - private var element: Element = element - private var sceneValues: Element.TargetValues = sceneValues override fun onAttach() { super.onAttach() @@ -283,26 +279,27 @@ private fun shouldDrawElement( scene: Scene, element: Element, ): Boolean { - val state = layoutImpl.state.transitionState + val transition = layoutImpl.state.currentTransition // Always draw the element if there is no ongoing transition or if the element is not shared. if ( - state !is TransitionState.Transition || - !layoutImpl.isTransitionReady(state) || - state.fromScene !in element.sceneValues || - state.toScene !in element.sceneValues + transition == null || + !layoutImpl.isTransitionReady(transition) || + transition.fromScene !in element.sceneValues || + transition.toScene !in element.sceneValues ) { return true } - val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key) + val sharedTransformation = + sharedElementTransformation(layoutImpl.state, transition, element.key) if (sharedTransformation?.enabled == false) { return true } return shouldDrawOrComposeSharedElement( layoutImpl, - state, + transition, scene.key, element.key, sharedTransformation, @@ -331,21 +328,21 @@ internal fun shouldDrawOrComposeSharedElement( } private fun isSharedElementEnabled( - layoutImpl: SceneTransitionLayoutImpl, + layoutState: SceneTransitionLayoutStateImpl, transition: TransitionState.Transition, element: ElementKey, ): Boolean { - return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true + return sharedElementTransformation(layoutState, transition, element)?.enabled ?: true } internal fun sharedElementTransformation( - layoutImpl: SceneTransitionLayoutImpl, + layoutState: SceneTransitionLayoutStateImpl, transition: TransitionState.Transition, element: ElementKey, ): SharedElementTransformation? { - val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene) - val sharedInFromScene = spec.transformations(element, transition.fromScene).shared - val sharedInToScene = spec.transformations(element, transition.toScene).shared + val transformationSpec = layoutState.transformationSpec + val sharedInFromScene = transformationSpec.transformations(element, transition.fromScene).shared + val sharedInToScene = transformationSpec.transformations(element, transition.toScene).shared // The sharedElement() transformation must either be null or be the same in both scenes. if (sharedInFromScene != sharedInToScene) { @@ -371,13 +368,9 @@ private fun isElementOpaque( scene: Scene, sceneValues: Element.TargetValues, ): Boolean { - val state = layoutImpl.state.transitionState - - if (state !is TransitionState.Transition) { - return true - } + val transition = layoutImpl.state.currentTransition ?: return true - if (!layoutImpl.isTransitionReady(state)) { + if (!layoutImpl.isTransitionReady(transition)) { val lastValue = sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f @@ -385,8 +378,8 @@ private fun isElementOpaque( return lastValue == 1f } - val fromScene = state.fromScene - val toScene = state.toScene + val fromScene = transition.fromScene + val toScene = transition.toScene val fromValues = element.sceneValues[fromScene] val toValues = element.sceneValues[toScene] @@ -395,14 +388,11 @@ private fun isElementOpaque( } val isSharedElement = fromValues != null && toValues != null - if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { + if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) { return true } - return layoutImpl.transitions - .transitionSpec(fromScene, toScene) - .transformations(element.key, scene.key) - .alpha == null + return layoutImpl.state.transformationSpec.transformations(element.key, scene.key).alpha == null } /** @@ -607,24 +597,22 @@ private inline fun <T> computeValue( lastValue: () -> T, lerp: (T, T, Float) -> T, ): T { - val state = layoutImpl.state.transitionState - - // There is no ongoing transition. - if (state !is TransitionState.Transition) { - // Even if this element SceneTransitionLayout is not animated, the layout itself might be - // animated (e.g. by another parent SceneTransitionLayout), in which case this element still - // need to participate in the layout phase. - return currentValue() - } + val transition = + layoutImpl.state.currentTransition + // There is no ongoing transition. Even if this element SceneTransitionLayout is not + // animated, the layout itself might be animated (e.g. by another parent + // SceneTransitionLayout), in which case this element still need to participate in the + // layout phase. + ?: return currentValue() // A transition was started but it's not ready yet (not all elements have been composed/laid // out yet). Use the last value that was set, to make sure elements don't unexpectedly jump. - if (!layoutImpl.isTransitionReady(state)) { + if (!layoutImpl.isTransitionReady(transition)) { return lastValue() } - val fromScene = state.fromScene - val toScene = state.toScene + val fromScene = transition.fromScene + val toScene = transition.toScene val fromValues = element.sceneValues[fromScene] val toValues = element.sceneValues[toScene] @@ -638,21 +626,17 @@ private inline fun <T> computeValue( // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared // elements follow the finger direction. val isSharedElement = fromValues != null && toValues != null - if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { + if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) { val start = sceneValue(fromValues!!) val end = sceneValue(toValues!!) // Make sure we don't read progress if values are the same and we don't need to interpolate, // so we don't invalidate the phase where this is read. - return if (start == end) start else lerp(start, end, state.progress) + return if (start == end) start else lerp(start, end, transition.progress) } val transformation = - transformation( - layoutImpl.transitions - .transitionSpec(fromScene, toScene) - .transformations(element.key, scene.key) - ) + transformation(layoutImpl.state.transformationSpec.transformations(element.key, scene.key)) // If there is no transformation explicitly associated to this element value, let's use // the value given by the system (like the current position and size given by the layout // pass). @@ -675,7 +659,7 @@ private inline fun <T> computeValue( scene, element, sceneValues, - state, + transition, idleValue, ) @@ -685,7 +669,7 @@ private inline fun <T> computeValue( return targetValue } - val progress = state.progress + val progress = transition.progress // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range. val rangeProgress = transformation.range?.progress(progress) ?: progress diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt index 7029da2edb0d5e05b34217092344ccb4fb6327c7..306f27626e196b03af3d1e0c882b738ed193e5c2 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -120,17 +120,13 @@ private fun shouldComposeMovableElement( scene: SceneKey, element: Element, ): Boolean { - val transitionState = layoutImpl.state.transitionState - - // If we are idle, there is only one [scene] that is composed so we can compose our movable - // content here. - if (transitionState is TransitionState.Idle) { - check(transitionState.currentScene == scene) - return true - } - - val fromScene = (transitionState as TransitionState.Transition).fromScene - val toScene = transitionState.toScene + val transition = + layoutImpl.state.currentTransition + // If we are idle, there is only one [scene] that is composed so we can compose our + // movable content here. + ?: return true + val fromScene = transition.fromScene + val toScene = transition.toScene val fromReady = layoutImpl.isSceneReady(fromScene) val toReady = layoutImpl.isSceneReady(toScene) @@ -181,10 +177,10 @@ private fun shouldComposeMovableElement( return shouldDrawOrComposeSharedElement( layoutImpl, - transitionState, + transition, scene, element.key, - sharedElementTransformation(layoutImpl, transitionState, element.key), + sharedElementTransformation(layoutImpl.state, transition, element.key), ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt index 32025b4f1258a2ecdb7821e7dc1992c1c3226734..e78f3266d664faca0bc0b9659a36278920a42eee 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt @@ -179,7 +179,8 @@ private fun scenePriorityNestedScrollConnection( bottomOrRightBehavior: NestedScrollBehavior, ) = SceneNestedScrollHandler( - gestureHandler = layoutImpl.gestureHandler(orientation = orientation), + layoutImpl = layoutImpl, + orientation = orientation, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, ) 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 91decf4d8b7ebd1c7c67b45fc66a0d602e86f0c4..338557d0942e9bb29eb523a642572d93010aa6a6 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 @@ -41,14 +41,9 @@ internal class SceneGestureHandler( internal val orientation: Orientation, private val coroutineScope: CoroutineScope, ) { + private val layoutState = layoutImpl.state val draggable: DraggableHandler = SceneDraggableHandler(this) - internal var transitionState - get() = layoutImpl.state.transitionState - set(value) { - layoutImpl.state.transitionState = value - } - private var _swipeTransition: SwipeTransition? = null internal var swipeTransition: SwipeTransition get() = _swipeTransition ?: error("SwipeTransition needs to be initialized") @@ -57,27 +52,26 @@ internal class SceneGestureHandler( } private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) { - if (isDrivingTransition || force) transitionState = newTransition + if (isDrivingTransition || force) layoutState.startTransition(newTransition) swipeTransition = newTransition } - internal val currentScene: Scene - get() = layoutImpl.scene(transitionState.currentScene) - internal val isDrivingTransition - get() = transitionState == _swipeTransition + get() = layoutState.transitionState == _swipeTransition /** * The velocity threshold at which the intent of the user is to swipe up or down. It is the same * as SwipeableV2Defaults.VelocityThreshold. */ - internal val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() } + internal val velocityThreshold: Float + get() = with(layoutImpl.density) { 125.dp.toPx() } /** * The positional threshold at which the intent of the user is to swipe to the next scene. It is * the same as SwipeableV2Defaults.PositionalThreshold. */ - private val positionalThreshold = with(layoutImpl.density) { 56.dp.toPx() } + private val positionalThreshold + get() = with(layoutImpl.density) { 56.dp.toPx() } internal var gestureWithPriority: Any? = null @@ -98,18 +92,18 @@ internal class SceneGestureHandler( return } - val transition = transitionState - if (transition is TransitionState.Transition) { + val transitionState = layoutState.transitionState + if (transitionState is TransitionState.Transition) { // TODO(b/290184746): Better handle interruptions here if state != idle. Log.w( TAG, "start from TransitionState.Transition is not fully supported: from" + - " ${transition.fromScene} to ${transition.toScene} " + - "(progress ${transition.progress})" + " ${transitionState.fromScene} to ${transitionState.toScene} " + + "(progress ${transitionState.progress})" ) } - val fromScene = currentScene + val fromScene = layoutImpl.scene(transitionState.currentScene) setCurrentActions(fromScene, startedPosition, pointersDown) val (targetScene, distance) = @@ -364,7 +358,7 @@ internal class SceneGestureHandler( findTargetSceneAndDistanceStrict(fromScene, velocity) ?: run { // We will not animate - transitionState = TransitionState.Idle(fromScene.key) + layoutState.finishTransition(swipeTransition, idleScene = fromScene.key) return } @@ -439,14 +433,7 @@ internal class SceneGestureHandler( ) swipeTransition.finishOffsetAnimation() - - // Now that the animation is done, the state should be idle. Note that if the state - // was changed since this animation started, some external code changed it and we - // shouldn't do anything here. Note also that this job will be cancelled in the case - // where the user intercepts this swipe. - if (isDrivingTransition) { - transitionState = TransitionState.Idle(targetScene) - } + layoutState.finishTransition(swipeTransition, targetScene) } } } @@ -539,10 +526,14 @@ private class SceneDraggableHandler( } internal class SceneNestedScrollHandler( - private val gestureHandler: SceneGestureHandler, + private val layoutImpl: SceneTransitionLayoutImpl, + private val orientation: Orientation, private val topOrLeftBehavior: NestedScrollBehavior, private val bottomOrRightBehavior: NestedScrollBehavior, ) : NestedScrollHandler { + private val layoutState = layoutImpl.state + private val gestureHandler = layoutImpl.gestureHandler(orientation) + override val connection: PriorityNestedScrollConnection = nestedScrollConnection() private fun nestedScrollConnection(): PriorityNestedScrollConnection { @@ -553,7 +544,7 @@ internal class SceneNestedScrollHandler( val actionUpOrLeft = Swipe( direction = - when (gestureHandler.orientation) { + when (orientation) { Orientation.Horizontal -> SwipeDirection.Left Orientation.Vertical -> SwipeDirection.Up }, @@ -563,7 +554,7 @@ internal class SceneNestedScrollHandler( val actionDownOrRight = Swipe( direction = - when (gestureHandler.orientation) { + when (orientation) { Orientation.Horizontal -> SwipeDirection.Right Orientation.Vertical -> SwipeDirection.Down }, @@ -571,7 +562,7 @@ internal class SceneNestedScrollHandler( ) fun hasNextScene(amount: Float): Boolean { - val fromScene = gestureHandler.currentScene + val fromScene = layoutImpl.scene(layoutState.transitionState.currentScene) val nextScene = when { amount < 0f -> fromScene.userActions[actionUpOrLeft] @@ -582,7 +573,7 @@ internal class SceneNestedScrollHandler( } return PriorityNestedScrollConnection( - orientation = gestureHandler.orientation, + orientation = orientation, canStartPreScroll = { offsetAvailable, offsetBeforeStart -> canChangeScene = offsetBeforeStart == 0f @@ -590,8 +581,9 @@ internal class SceneNestedScrollHandler( canChangeScene && gestureHandler.isDrivingTransition && offsetAvailable != 0f if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false - val progress = gestureHandler.swipeTransition.progress - val threshold = gestureHandler.layoutImpl.transitionInterceptionThreshold + 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 @@ -599,9 +591,8 @@ internal class SceneNestedScrollHandler( // The progress value can go beyond this range in the case of overscroll. val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f) if (shouldSnapToIdle) { - gestureHandler.swipeTransition.cancelOffsetAnimation() - gestureHandler.transitionState = - TransitionState.Idle(gestureHandler.swipeTransition.currentScene) + swipeTransition.cancelOffsetAnimation() + layoutState.finishTransition(swipeTransition, swipeTransition.currentScene) } // Start only if we cannot consume this event diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 239971ff6be825e3c7d6c2ec9ce8286686ba7484..3608e374fdbc83ea3fdd8ca39d2ec108dd81a9a7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -19,6 +19,8 @@ package com.android.compose.animation.scene import androidx.annotation.FloatRange import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.remember @@ -27,6 +29,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.platform.LocalDensity +import kotlinx.coroutines.channels.Channel /** * [SceneTransitionLayout] is a container that automatically animates its content whenever @@ -266,24 +269,45 @@ internal fun SceneTransitionLayoutForTesting( val coroutineScope = rememberCoroutineScope() val layoutImpl = remember { SceneTransitionLayoutImpl( + state = state as SceneTransitionLayoutStateImpl, onChangeScene = onChangeScene, - builder = scenes, - transitions = transitions, - state = state, density = density, edgeDetector = edgeDetector, transitionInterceptionThreshold = transitionInterceptionThreshold, + builder = scenes, coroutineScope = coroutineScope, ) .also { onLayoutImpl?.invoke(it) } } - layoutImpl.onChangeScene = onChangeScene - layoutImpl.transitions = transitions - layoutImpl.density = density - layoutImpl.edgeDetector = edgeDetector + val targetSceneChannel = remember { Channel<SceneKey>(Channel.CONFLATED) } + SideEffect { + if (state != layoutImpl.state) { + error( + "This SceneTransitionLayout was bound to a different SceneTransitionLayoutState" + + " that was used when creating it, which is not supported" + ) + } + + layoutImpl.onChangeScene = onChangeScene + (state as SceneTransitionLayoutStateImpl).transitions = transitions + layoutImpl.density = density + layoutImpl.edgeDetector = edgeDetector + layoutImpl.updateScenes(scenes) + + state.transitions = transitions + + targetSceneChannel.trySend(currentScene) + } + + LaunchedEffect(targetSceneChannel) { + for (newKey in targetSceneChannel) { + // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame + // late. + val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey + animateToScene(layoutImpl.state, newKey) + } + } - layoutImpl.setScenes(scenes) - layoutImpl.setCurrentScene(currentScene) layoutImpl.Content(modifier) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 00e33e24c41e8e050f3baaf3102a14033bfd6992..c99c3250bbb108206d29c90f81a9b89f490d7596 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -22,13 +22,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -40,36 +35,40 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.util.fastForEach import com.android.compose.ui.util.lerp import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.Channel @Stable internal class SceneTransitionLayoutImpl( - onChangeScene: (SceneKey) -> Unit, + internal val state: SceneTransitionLayoutStateImpl, + internal var onChangeScene: (SceneKey) -> Unit, + internal var density: Density, + internal var edgeDetector: EdgeDetector, + internal var transitionInterceptionThreshold: Float, builder: SceneTransitionLayoutScope.() -> Unit, - transitions: SceneTransitions, - internal val state: SceneTransitionLayoutState, - density: Density, - edgeDetector: EdgeDetector, - transitionInterceptionThreshold: Float, coroutineScope: CoroutineScope, ) { - internal val scenes = SnapshotStateMap<SceneKey, Scene>() + internal val scenes = mutableMapOf<SceneKey, Scene>() + + /** + * The map of [Element]s. + * + * Note that this map is *mutated* directly during composition, so it is a [SnapshotStateMap] to + * make sure that mutations are reverted if composition is cancelled. + */ internal val elements = SnapshotStateMap<ElementKey, Element>() - /** The scenes that are "ready", i.e. they were composed and fully laid-out at least once. */ + /** + * The scenes that are "ready", i.e. they were composed and fully laid-out at least once. + * + * Note that this map is *read* during composition, so it is a [SnapshotStateMap] to make sure + * that we recompose when modifications are made to this map. + */ private val readyScenes = SnapshotStateMap<SceneKey, Boolean>() - internal var onChangeScene by mutableStateOf(onChangeScene) - internal var transitions by mutableStateOf(transitions) - internal var density: Density by mutableStateOf(density) - internal var edgeDetector by mutableStateOf(edgeDetector) - internal var transitionInterceptionThreshold by mutableStateOf(transitionInterceptionThreshold) - private val horizontalGestureHandler: SceneGestureHandler private val verticalGestureHandler: SceneGestureHandler init { - setScenes(builder) + updateScenes(builder) // SceneGestureHandler must wait for the scenes to be initialized, in order to access the // current scene (required for SwipeTransition). @@ -98,7 +97,7 @@ internal class SceneTransitionLayoutImpl( return scenes[key] ?: error("Scene $key is not configured") } - internal fun setScenes(builder: SceneTransitionLayoutScope.() -> Unit) { + internal fun updateScenes(builder: SceneTransitionLayoutScope.() -> Unit) { // Keep a reference of the current scenes. After processing [builder], the scenes that were // not configured will be removed. val scenesToRemove = scenes.keys.toMutableSet() @@ -140,20 +139,6 @@ internal class SceneTransitionLayoutImpl( scenesToRemove.forEach { scenes.remove(it) } } - @Composable - internal fun setCurrentScene(key: SceneKey) { - val channel = remember { Channel<SceneKey>(Channel.CONFLATED) } - SideEffect { channel.trySend(key) } - LaunchedEffect(channel) { - for (newKey in channel) { - // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame - // late. - val newKey = channel.tryReceive().getOrNull() ?: newKey - animateToScene(this@SceneTransitionLayoutImpl, newKey) - } - } - } - @Composable @OptIn(ExperimentalComposeUiApi::class) internal fun Content(modifier: Modifier) { @@ -171,14 +156,14 @@ internal class SceneTransitionLayoutImpl( val width: Int val height: Int - val state = state.transitionState - if (state !is TransitionState.Transition) { + val transition = state.currentTransition + if (transition == null) { width = placeable.width height = placeable.height } else { // Interpolate the size. - val fromSize = scene(state.fromScene).targetSize - val toSize = scene(state.toScene).targetSize + val fromSize = scene(transition.fromScene).targetSize + val toSize = scene(transition.toScene).targetSize // Optimization: make sure we don't read state.progress if fromSize == // toSize to avoid running this code every frame when the layout size does @@ -187,7 +172,7 @@ internal class SceneTransitionLayoutImpl( width = fromSize.width height = fromSize.height } else { - val size = lerp(fromSize, toSize, state.progress) + val size = lerp(fromSize, toSize, transition.progress) width = size.width.coerceAtLeast(0) height = size.height.coerceAtLeast(0) } @@ -228,13 +213,12 @@ internal class SceneTransitionLayoutImpl( scene.Content( Modifier.drawWithContent { - when (val state = state.transitionState) { - is TransitionState.Idle -> drawContent() - is TransitionState.Transition -> { - // Don't draw scenes that are not ready yet. - if (readyScenes.containsKey(key)) { - drawContent() - } + if (state.currentTransition == null) { + drawContent() + } else { + // Don't draw scenes that are not ready yet. + if (readyScenes.containsKey(key)) { + drawContent() } } } 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 623725582a9d8c7fe5462b2e2bdfbe4aeb49c2b3..d1ba582d6c2313649cf02b9bfb52dc9f090d36a2 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 @@ -23,31 +23,32 @@ import androidx.compose.runtime.setValue /** The state of a [SceneTransitionLayout]. */ @Stable -class SceneTransitionLayoutState(initialScene: SceneKey) { +sealed interface SceneTransitionLayoutState { /** * The current [TransitionState]. All values read here are backed by the Snapshot system. * * To observe those values outside of Compose/the Snapshot system, use * [SceneTransitionLayoutState.observableTransitionState] instead. */ - var transitionState: TransitionState by mutableStateOf(TransitionState.Idle(initialScene)) + val transitionState: TransitionState + + /** The current transition, or `null` if we are idle. */ + val currentTransition: TransitionState.Transition? + get() = transitionState as? TransitionState.Transition /** - * Whether we are transitioning, optionally restricting the check to the transition between - * [from] and [to]. + * Whether we are transitioning. If [from] or [to] is empty, we will also check that they match + * the scenes we are animating from and/or to. */ - fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean { - val transition = transitionState as? TransitionState.Transition ?: return false - - return (from == null || transition.fromScene == from) && - (to == null || transition.toScene == to) - } + fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */ - fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean { - return isTransitioning(from = scene, to = other) || - isTransitioning(from = other, to = scene) - } + fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean +} + +/** Create a new [SceneTransitionLayoutState] that is currently idle at scene [currentScene]. */ +fun SceneTransitionLayoutState(currentScene: SceneKey): SceneTransitionLayoutState { + return SceneTransitionLayoutStateImpl(currentScene, SceneTransitions.Empty) } @Stable @@ -93,3 +94,50 @@ sealed interface TransitionState { abstract val isUserInputOngoing: Boolean } } + +internal class SceneTransitionLayoutStateImpl( + initialScene: SceneKey, + internal var transitions: SceneTransitions, +) : SceneTransitionLayoutState { + override var transitionState: TransitionState by + mutableStateOf(TransitionState.Idle(initialScene)) + private set + + /** + * The current [transformationSpec] associated to [transitionState]. Accessing this value makes + * sense only if [transitionState] is a [TransitionState.Transition]. + */ + internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty + + override fun isTransitioning(from: SceneKey?, to: SceneKey?): Boolean { + val transition = currentTransition ?: return false + return (from == null || transition.fromScene == from) && + (to == null || transition.toScene == to) + } + + override fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean { + return isTransitioning(from = scene, to = other) || + isTransitioning(from = other, to = scene) + } + + /** Start a new [transition], instantly interrupting any ongoing transition if there was one. */ + internal fun startTransition(transition: TransitionState.Transition) { + // Compute the [TransformationSpec] when the transition starts. + transformationSpec = + transitions + .transitionSpec(transition.fromScene, transition.toScene) + .transformationSpec() + + transitionState = transition + } + + /** + * 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) { + if (transitionState == transition) { + transitionState = TransitionState.Idle(idleScene) + } + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index f91895bb0e05f40f38f0e85d8a9c2eb1e3e34f9f..3a55567d69bba04bf7a1983bdf2643fc1f020bbd 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -18,11 +18,9 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.snap -import androidx.compose.runtime.Stable import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.util.fastForEach -import androidx.compose.ui.util.fastMap import com.android.compose.animation.scene.transformation.AnchoredSize import com.android.compose.animation.scene.transformation.AnchoredTranslate import com.android.compose.animation.scene.transformation.DrawScale @@ -36,16 +34,17 @@ import com.android.compose.animation.scene.transformation.Transformation import com.android.compose.animation.scene.transformation.Translate /** The transitions configuration of a [SceneTransitionLayout]. */ -class SceneTransitions( - internal val transitionSpecs: List<TransitionSpec>, +class SceneTransitions +internal constructor( + internal val transitionSpecs: List<TransitionSpecImpl>, ) { - private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>() + private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpecImpl>>() - internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec { + internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl { return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) } } - private fun findSpec(from: SceneKey, to: SceneKey): TransitionSpec { + private fun findSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl { val spec = transition(from, to) { it.from == from && it.to == to } if (spec != null) { return spec @@ -53,7 +52,7 @@ class SceneTransitions( val reversed = transition(from, to) { it.from == to && it.to == from } if (reversed != null) { - return reversed.reverse() + return reversed.reversed() } val relaxedSpec = @@ -67,16 +66,16 @@ class SceneTransitions( return transition(from, to) { (it.from == to && it.to == null) || (it.to == from && it.from == null) } - ?.reverse() + ?.reversed() ?: defaultTransition(from, to) } private fun transition( from: SceneKey, to: SceneKey, - filter: (TransitionSpec) -> Boolean, - ): TransitionSpec? { - var match: TransitionSpec? = null + filter: (TransitionSpecImpl) -> Boolean, + ): TransitionSpecImpl? { + var match: TransitionSpecImpl? = null transitionSpecs.fastForEach { spec -> if (filter(spec)) { if (match != null) { @@ -89,28 +88,88 @@ class SceneTransitions( } private fun defaultTransition(from: SceneKey, to: SceneKey) = - TransitionSpec(from, to, emptyList(), snap()) + TransitionSpecImpl(from, to, TransformationSpec.EmptyProvider) + + companion object { + val Empty = SceneTransitions(transitionSpecs = emptyList()) + } } /** The definition of a transition between [from] and [to]. */ -@Stable -data class TransitionSpec( - val from: SceneKey?, - val to: SceneKey?, - val transformations: List<Transformation>, - val spec: AnimationSpec<Float>, -) { - // TODO(b/302300957): Make sure this cache does not infinitely grow. - private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>() +interface TransitionSpec { + /** + * The scene we are transitioning from. If `null`, this spec can be used to animate from any + * scene. + */ + val from: SceneKey? + + /** + * The scene we are transitioning to. If `null`, this spec can be used to animate from any + * scene. + */ + val to: SceneKey? + + /** + * Return a reversed version of this [TransitionSpec] for a transition going from [to] to + * [from]. + */ + fun reversed(): TransitionSpec + + /* + * The [TransformationSpec] associated to this [TransitionSpec]. + * + * Note that this is called once every a transition associated to this [TransitionSpec] is + * started. + */ + fun transformationSpec(): TransformationSpec +} + +interface TransformationSpec { + /** The [AnimationSpec] used to animate the associated transition progress. */ + val progressSpec: AnimationSpec<Float> + + /** The list of [Transformation] applied to elements during this transition. */ + val transformations: List<Transformation> + + companion object { + internal val Empty = + TransformationSpecImpl(progressSpec = snap(), transformations = emptyList()) + internal val EmptyProvider = { Empty } + } +} - internal fun reverse(): TransitionSpec { - return copy( +internal class TransitionSpecImpl( + override val from: SceneKey?, + override val to: SceneKey?, + private val transformationSpec: () -> TransformationSpecImpl, +) : TransitionSpec { + override fun reversed(): TransitionSpecImpl { + return TransitionSpecImpl( from = to, to = from, - transformations = transformations.fastMap { it.reverse() }, + transformationSpec = { + val reverse = transformationSpec.invoke() + TransformationSpecImpl( + progressSpec = reverse.progressSpec, + transformations = reverse.transformations.map { it.reversed() } + ) + } ) } + override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke() +} + +/** + * An implementation of [TransformationSpec] that allows the quick retrieval of an element + * [ElementTransformations]. + */ +internal class TransformationSpecImpl( + override val progressSpec: AnimationSpec<Float>, + override val transformations: List<Transformation>, +) : TransformationSpec { + private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>() + internal fun transformations(element: ElementKey, scene: SceneKey): ElementTransformations { return cache .getOrPut(element) { mutableMapOf() } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index 116a66673d0afdcd011b9d5894564d14d4fb4842..0d3bc7d0cd85d73faf9f3d0e1e658e4b17feeedc 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -27,7 +27,8 @@ internal fun Modifier.swipeToScene(gestureHandler: SceneGestureHandler): Modifie fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean = userActions.keys.any { it is Swipe && it.direction.orientation == orientation } - val currentScene = gestureHandler.currentScene + val layoutImpl = gestureHandler.layoutImpl + val currentScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene) val orientation = gestureHandler.orientation val canSwipe = currentScene.shouldEnableSwipes(orientation) val canOppositeSwipe = diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index 8f4a36e47212b30eee935aafcb8e9e9e924213c9..70468669297c8f274fc33ce0147e82abd2b3678e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -44,7 +44,7 @@ internal fun transitionsImpl( } private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { - val transitionSpecs = mutableListOf<TransitionSpec>() + val transitionSpecs = mutableListOf<TransitionSpecImpl>() override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec { return transition(from = null, to = to, builder) @@ -63,14 +63,15 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { to: SceneKey?, builder: TransitionBuilder.() -> Unit, ): TransitionSpec { - val impl = TransitionBuilderImpl().apply(builder) - val spec = - TransitionSpec( - from, - to, - impl.transformations, - impl.spec, + fun transformationSpec(): TransformationSpecImpl { + val impl = TransitionBuilderImpl().apply(builder) + return TransformationSpecImpl( + progressSpec = impl.spec, + transformations = impl.transformations, ) + } + + val spec = TransitionSpecImpl(from, to, ::transformationSpec) transitionSpecs.add(spec) return spec } @@ -143,7 +144,7 @@ internal class TransitionBuilderImpl : TransitionBuilder { transformations.add( if (reversed) { - transformation.reverse() + transformation.reversed() } else { transformation } 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 206935558179a34657ce27c6e651fd51bd359afe..0cd11b9914c9760425bd459c4bf85e63b4481124 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 @@ -42,7 +42,7 @@ sealed interface Transformation { * Reverse this transformation. This is called when we use Transition(from = A, to = B) when * animating from B to A and there is no Transition(from = B, to = A) defined. */ - fun reverse(): Transformation = this + fun reversed(): Transformation = this } internal class SharedElementTransformation( @@ -77,10 +77,10 @@ internal class RangedPropertyTransformation<T>( val delegate: PropertyTransformation<T>, override val range: TransformationRange, ) : PropertyTransformation<T> by delegate { - override fun reverse(): Transformation { + override fun reversed(): Transformation { return RangedPropertyTransformation( - delegate.reverse() as PropertyTransformation<T>, - range.reverse() + delegate.reversed() as PropertyTransformation<T>, + range.reversed() ) } } @@ -102,7 +102,7 @@ data class TransformationRange( } /** Reverse this range. */ - fun reverse() = TransformationRange(start = reverseBound(end), end = reverseBound(start)) + fun reversed() = TransformationRange(start = reverseBound(end), end = reverseBound(start)) /** Get the progress of this range given the global [transitionProgress]. */ fun progress(transitionProgress: Float): Float { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt index e6224df649cab2e0c792ce66343fe58dae5c55cc..d9ce5191f3d90641c0b5233edb251bf5eb01826e 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt @@ -55,8 +55,8 @@ class SceneGestureHandlerTest { ) { private var internalCurrentScene: SceneKey by mutableStateOf(SceneA) - private val layoutState: SceneTransitionLayoutState = - SceneTransitionLayoutState(internalCurrentScene) + private val layoutState = + SceneTransitionLayoutStateImpl(internalCurrentScene, EmptyTestTransitions) val mutableUserActionsA: MutableMap<UserAction, SceneKey> = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC) @@ -93,36 +93,24 @@ class SceneGestureHandlerTest { private val layoutImpl = SceneTransitionLayoutImpl( - onChangeScene = { internalCurrentScene = it }, - builder = scenesBuilder, - transitions = EmptyTestTransitions, state = layoutState, + onChangeScene = { internalCurrentScene = it }, density = Density(1f), edgeDetector = DefaultEdgeDetector, transitionInterceptionThreshold = transitionInterceptionThreshold, + builder = scenesBuilder, coroutineScope = coroutineScope, ) .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) } - val sceneGestureHandler = - SceneGestureHandler( - layoutImpl = layoutImpl, - orientation = Orientation.Vertical, - coroutineScope = coroutineScope, - ) - - val horizontalSceneGestureHandler = - SceneGestureHandler( - layoutImpl = layoutImpl, - orientation = Orientation.Horizontal, - coroutineScope = coroutineScope, - ) - + val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical) + val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal) val draggable = sceneGestureHandler.draggable fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) = SceneNestedScrollHandler( - gestureHandler = sceneGestureHandler, + layoutImpl, + orientation = sceneGestureHandler.orientation, topOrLeftBehavior = nestedScrollBehavior, bottomOrRightBehavior = nestedScrollBehavior, ) 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 eeda8d46adfa30a06b1b293e1937b0c631e34961..c5b8d9ae0d10f5f28ae089657a25161adc2ca46d 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 @@ -40,8 +40,8 @@ class SceneTransitionLayoutStateTest { @Test fun isTransitioningTo_transition() { - val state = SceneTransitionLayoutState(TestScenes.SceneA) - state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneB) + val state = SceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty) + state.startTransition(transition(from = TestScenes.SceneA, to = TestScenes.SceneB)) assertThat(state.isTransitioning()).isTrue() assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue() diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt index fa94b25028a23e7b24e59930065fc7bfe93e654f..ef729921f4cdd4e746970e3a5ea21dbe49aec78c 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt @@ -55,7 +55,7 @@ class TransitionDslTest { assertThat(transitions.transitionSpecs) .comparingElementsUsing( - Correspondence.transforming<TransitionSpec, Pair<SceneKey?, SceneKey?>>( + Correspondence.transforming<TransitionSpecImpl, Pair<SceneKey?, SceneKey?>>( { it?.from to it?.to }, "has (from, to) equal to" ) @@ -70,8 +70,8 @@ class TransitionDslTest { @Test fun defaultTransitionSpec() { val transitions = transitions { from(TestScenes.SceneA, to = TestScenes.SceneB) } - val transition = transitions.transitionSpecs.single() - assertThat(transition.spec).isInstanceOf(SpringSpec::class.java) + val transformationSpec = transitions.transitionSpecs.single().transformationSpec() + assertThat(transformationSpec.progressSpec).isInstanceOf(SpringSpec::class.java) } @Test @@ -79,9 +79,9 @@ class TransitionDslTest { val transitions = transitions { from(TestScenes.SceneA, to = TestScenes.SceneB) { spec = tween(durationMillis = 42) } } - val transition = transitions.transitionSpecs.single() - assertThat(transition.spec).isInstanceOf(TweenSpec::class.java) - assertThat((transition.spec as TweenSpec).durationMillis).isEqualTo(42) + val transformationSpec = transitions.transitionSpecs.single().transformationSpec() + assertThat(transformationSpec.progressSpec).isInstanceOf(TweenSpec::class.java) + assertThat((transformationSpec.progressSpec as TweenSpec).durationMillis).isEqualTo(42) } @Test @@ -90,9 +90,10 @@ class TransitionDslTest { from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) } } - val transition = transitions.transitionSpecs.single() - assertThat(transition.transformations.size).isEqualTo(1) - assertThat(transition.transformations.single().range).isEqualTo(null) + val transformations = + transitions.transitionSpecs.single().transformationSpec().transformations + assertThat(transformations.size).isEqualTo(1) + assertThat(transformations.single().range).isEqualTo(null) } @Test @@ -105,8 +106,9 @@ class TransitionDslTest { } } - val transition = transitions.transitionSpecs.single() - assertThat(transition.transformations) + val transformations = + transitions.transitionSpecs.single().transformationSpec().transformations + assertThat(transformations) .comparingElementsUsing(TRANSFORMATION_RANGE) .containsExactly( TransformationRange(start = 0.1f, end = 0.8f), @@ -127,8 +129,9 @@ class TransitionDslTest { } } - val transition = transitions.transitionSpecs.single() - assertThat(transition.transformations) + val transformations = + transitions.transitionSpecs.single().transformationSpec().transformations + assertThat(transformations) .comparingElementsUsing(TRANSFORMATION_RANGE) .containsExactly( TransformationRange(start = 100 / 500f, end = 300 / 500f), @@ -149,8 +152,9 @@ class TransitionDslTest { } } - val transition = transitions.transitionSpecs.single() - assertThat(transition.transformations) + val transformations = + transitions.transitionSpecs.single().transformationSpec().transformations + assertThat(transformations) .comparingElementsUsing(TRANSFORMATION_RANGE) .containsExactly( TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f), @@ -170,9 +174,13 @@ class TransitionDslTest { // Fetch the transition from B to A, which will automatically reverse the transition from A // to B we defined. - val transition = - transitions.transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA) - assertThat(transition.transformations) + val transformations = + transitions + .transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA) + .transformationSpec() + .transformations + + assertThat(transformations) .comparingElementsUsing(TRANSFORMATION_RANGE) .containsExactly( TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f), diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 695d888d94f5807e7f133f0801e26cbf4cf04641..f1701356c134ca85d73cad787d7de412df41609f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -18,7 +18,7 @@ package com.android.keyguard import android.content.res.Configuration -import android.hardware.biometrics.BiometricOverlayConstants +import android.hardware.biometrics.BiometricRequestConstants import android.media.AudioManager import android.telephony.TelephonyManager import android.testing.TestableLooper.RunWithLooper @@ -59,6 +59,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -235,6 +236,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { sceneInteractor = sceneInteractor, ) + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) underTest = KeyguardSecurityContainerController( view, @@ -763,16 +765,18 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { @Test fun sideFpsControllerShow() { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) underTest.updateSideFpsVisibility(/* isVisible= */ true) verify(sideFpsController) .show( SideFpsUiRequestSource.PRIMARY_BOUNCER, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD + BiometricRequestConstants.REASON_AUTH_KEYGUARD ) } @Test fun sideFpsControllerHide() { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) underTest.updateSideFpsVisibility(/* isVisible= */ false) verify(sideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..74f50d8c844bd44b4fae5c94edf9c8b6d9f8f512 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt @@ -0,0 +1,171 @@ +/* + * 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.accessibility.data.repository + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +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 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class ColorCorrectionRepositoryImplTest : SysuiTestCase() { + companion object { + val TEST_USER_1 = UserHandle.of(1)!! + val TEST_USER_2 = UserHandle.of(2)!! + } + + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + private val settings: FakeSettings = FakeSettings() + + private lateinit var underTest: ColorCorrectionRepository + + @Before + fun setUp() { + underTest = + ColorCorrectionRepositoryImpl( + testDispatcher, + settings, + ) + } + + @Test + fun isEnabled_initiallyGetsSettingsValue() = + scope.runTest { + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + 1, + TEST_USER_1.identifier + ) + + underTest = + ColorCorrectionRepositoryImpl( + testDispatcher, + settings, + ) + + underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope) + runCurrent() + + val actualValue: Boolean = underTest.isEnabled(TEST_USER_1).first() + Truth.assertThat(actualValue).isTrue() + } + + @Test + fun isEnabled_settingUpdated_valueUpdated() = + scope.runTest { + underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope) + + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.DISABLED, + TEST_USER_1.identifier + ) + runCurrent() + Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse() + + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.ENABLED, + TEST_USER_1.identifier + ) + runCurrent() + Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue() + + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.DISABLED, + TEST_USER_1.identifier + ) + runCurrent() + Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse() + } + + @Test + fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() = + scope.runTest { + underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope) + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.DISABLED, + TEST_USER_1.identifier + ) + underTest.isEnabled(TEST_USER_2).launchIn(backgroundScope) + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.DISABLED, + TEST_USER_2.identifier + ) + + runCurrent() + Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse() + Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse() + + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.ENABLED, + TEST_USER_1.identifier + ) + runCurrent() + Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue() + Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse() + } + + @Test + fun setEnabled() = + scope.runTest { + val success = underTest.setIsEnabled(true, TEST_USER_1) + runCurrent() + Truth.assertThat(success).isTrue() + + val actualValue = + settings.getIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + TEST_USER_1.identifier + ) + Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.ENABLED) + } + + @Test + fun setDisabled() = + scope.runTest { + val success = underTest.setIsEnabled(false, TEST_USER_1) + runCurrent() + Truth.assertThat(success).isTrue() + + val actualValue = + settings.getIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + TEST_USER_1.identifier + ) + Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.DISABLED) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index a1b801cd3d3f7f06620eb7207834ae1ed03c9256..f8321b7e7eb3d5d35d2e098d9aef065580dfe8a1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -22,9 +22,9 @@ import android.app.ActivityTaskManager import android.content.ComponentName import android.graphics.Insets import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS -import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_UNKNOWN import android.hardware.biometrics.SensorLocationInternal import android.hardware.biometrics.SensorProperties import android.hardware.display.DisplayManager @@ -65,6 +65,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -144,6 +145,7 @@ class SideFpsControllerTest : SysuiTestCase() { @Before fun setup() { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) displayRepository = FakeDisplayRepository() displayStateRepository = FakeDisplayStateRepository() keyguardBouncerRepository = FakeKeyguardBouncerRepository() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 90d36e7fd81492167789caa664f432c00401190b..a726b7c2b075930343fd6143f28bd5afef302746 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -17,12 +17,12 @@ package com.android.systemui.biometrics import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING -import android.hardware.biometrics.BiometricOverlayConstants.ShowReason +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricRequestConstants.RequestReason import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater @@ -135,7 +135,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { } private fun withReason( - @ShowReason reason: Int, + @RequestReason reason: Int, isDebuggable: Boolean = false, enableDeviceEntryUdfpsRefactor: Boolean = false, block: () -> Unit, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 97ee526b7108f4e043aed3d8fca283607e0e25f5..dddcf18c1ede5708bb874c18e65a3f191350abd0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -43,7 +43,7 @@ import static org.mockito.Mockito.when; import android.graphics.Rect; import android.hardware.biometrics.BiometricFingerprintConstants; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.SensorProperties; import android.hardware.display.DisplayManager; @@ -359,7 +359,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void dozeTimeTick() throws RemoteException { mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); mUdfpsController.dozeTimeTick(); verify(mUdfpsView).dozeTimeTick(); @@ -455,7 +455,7 @@ public class UdfpsControllerTest extends SysuiTestCase { public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException { // GIVEN overlay was showing and the udfps bouncer is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); // WHEN the overlay is hidden @@ -469,7 +469,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void showUdfpsOverlay_callsListener() throws RemoteException { mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mFingerprintManager).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN, @@ -479,7 +479,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception { mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler), anyLong()); @@ -520,7 +520,7 @@ public class UdfpsControllerTest extends SysuiTestCase { reset(mWindowManager); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_ENROLL_ENROLLING, + BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mWindowManager).addView(any(), any()); @@ -555,7 +555,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // Show the overlay. mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mWindowManager).addView(any(), any()); @@ -637,7 +637,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // Show the overlay. mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); @@ -720,7 +720,7 @@ public class UdfpsControllerTest extends SysuiTestCase { initUdfpsController(testParams.sensorProps); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received @@ -778,7 +778,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing and screen is on and fp is running mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); // WHEN fingerprint is requested because of AOD interrupt @@ -808,7 +808,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN AOD interrupt mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); @@ -886,7 +886,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN overlay is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { when(mUdfpsView.isDisplayConfigured()).thenReturn(true); @@ -917,7 +917,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN AOD interrupt mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); @@ -958,7 +958,7 @@ public class UdfpsControllerTest extends SysuiTestCase { final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the ACTION_UP event @@ -1019,7 +1019,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN screen off mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOff(); mFgExecutor.runAllReady(); @@ -1041,7 +1041,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN showing overlay mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); @@ -1126,7 +1126,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing and a11y touch exploration NOT enabled when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(a11y); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); if (a11y) { @@ -1148,7 +1148,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing and a11y touch exploration NOT enabled when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); @@ -1197,7 +1197,7 @@ public class UdfpsControllerTest extends SysuiTestCase { -1 /* pointerId */, touchData); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); @@ -1289,7 +1289,7 @@ public class UdfpsControllerTest extends SysuiTestCase { public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException { // GIVEN UDFPS overlay is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // GIVEN there's been an AoD interrupt @@ -1316,7 +1316,7 @@ public class UdfpsControllerTest extends SysuiTestCase { throws RemoteException { // GIVEN UDFPS overlay is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // GIVEN there's been an AoD interrupt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt index 90e0c19b7c65e8e9cced1b0617c29c878878e476..a3bf3f492e6e5ef3f4367e3e34198fc969005422 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.utils.os.FakeHandler @@ -105,8 +106,10 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { job.cancel() } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun shouldUpdateSideFps_show() = runTest { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) var count = 0 val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this) repository.setPrimaryShow(true) @@ -116,8 +119,10 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { job.cancel() } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun shouldUpdateSideFps_hide() = runTest { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) repository.setPrimaryShow(true) var count = 0 val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this) @@ -128,8 +133,10 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { job.cancel() } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun sideFpsShowing() = runTest { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) var sideFpsIsShowing = false val job = underTest.sideFpsShowing.onEach { sideFpsIsShowing = it }.launchIn(this) repository.setSideFpsShowing(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt index 2b7221ec192cb29b206e16d51c06c8cc27cf594c..6b7d2635ffa4e4b96560a1bf46f159419e707ed4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt @@ -304,4 +304,34 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { ) assertThat(authenticationStatus).isNull() } + + @Test + fun onBiometricRunningStateChanged_shouldUpdateIndicatorVisibility() = + testScope.runTest { + val shouldUpdateIndicatorVisibility by + collectLastValue(underTest.shouldUpdateIndicatorVisibility) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + assertThat(shouldUpdateIndicatorVisibility).isFalse() + + invokeOnCallback { + it.onBiometricRunningStateChanged(false, BiometricSourceType.FINGERPRINT) + } + assertThat(shouldUpdateIndicatorVisibility).isTrue() + } + + @Test + fun onStrongAuthStateChanged_shouldUpdateIndicatorVisibility() = + testScope.runTest { + val shouldUpdateIndicatorVisibility by + collectLastValue(underTest.shouldUpdateIndicatorVisibility) + runCurrent() + + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture()) + assertThat(shouldUpdateIndicatorVisibility).isFalse() + + invokeOnCallback { it.onStrongAuthStateChanged(0) } + assertThat(shouldUpdateIndicatorVisibility).isTrue() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..8ee6d2005350536b3b1eaa231cc07d320dc322c3 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.colorcorrection.domain + +import android.graphics.drawable.TestStubDrawable +import android.widget.Switch +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.android.systemui.qs.tiles.impl.colorcorrection.qsColorCorrectionTileConfig +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ColorCorrectionTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val colorCorrectionTileConfig = kosmos.qsColorCorrectionTileConfig + private val subtitleArray = + context.resources.getStringArray(R.array.tile_states_color_correction) + // Using lazy (versus =) to make sure we override the right context -- see b/311612168 + private val mapper by lazy { + ColorCorrectionTileMapper( + context.orCreateTestableResources + .apply { addOverride(R.drawable.ic_qs_color_correction, TestStubDrawable()) } + .resources, + context.theme + ) + } + + @Test + fun disabledModel() { + val inputModel = ColorCorrectionTileModel(false) + + val outputState = mapper.map(colorCorrectionTileConfig, inputModel) + + val expectedState = + createColorCorrectionTileState(QSTileState.ActivationState.INACTIVE, subtitleArray[1]) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel() { + val inputModel = ColorCorrectionTileModel(true) + + val outputState = mapper.map(colorCorrectionTileConfig, inputModel) + + val expectedState = + createColorCorrectionTileState(QSTileState.ActivationState.ACTIVE, subtitleArray[2]) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createColorCorrectionTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String + ): QSTileState { + val label = context.getString(R.string.quick_settings_color_correction_label) + return QSTileState( + { Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null) }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + label, + null, + QSTileState.SideViewIcon.None, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..8c612acad8875f055b781713041d9ac7026ce038 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.data.repository.FakeColorCorrectionRepository +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class ColorCorrectionTileDataInteractorTest : SysuiTestCase() { + + private val colorCorrectionRepository = FakeColorCorrectionRepository() + private val underTest: ColorCorrectionTileDataInteractor = + ColorCorrectionTileDataInteractor(colorCorrectionRepository) + + @Test + fun alwaysAvailable() = runTest { + val availability = underTest.availability(TEST_USER).toCollection(mutableListOf()) + + assertThat(availability).hasSize(1) + assertThat(availability.last()).isTrue() + } + + @Test + fun dataMatchesTheRepository() = runTest { + val dataList: List<ColorCorrectionTileModel> by + collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))) + runCurrent() + + colorCorrectionRepository.setIsEnabled(true, TEST_USER) + runCurrent() + + colorCorrectionRepository.setIsEnabled(false, TEST_USER) + runCurrent() + + assertThat(dataList).hasSize(3) + assertThat(dataList.map { it.isEnabled }).isEqualTo(listOf(false, true, false)) + } + + private companion object { + val TEST_USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..3049cc079a1c3c48e641d6aa0af630f6116a6b20 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor + +import android.os.UserHandle +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.data.repository.FakeColorCorrectionRepository +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ColorCorrectionTileUserActionInteractorTest : SysuiTestCase() { + + private val testUser = UserHandle.CURRENT + private val repository = FakeColorCorrectionRepository() + private val inputHandler = FakeQSTileIntentUserInputHandler() + + private val underTest = + ColorCorrectionUserActionInteractor( + repository, + inputHandler, + ) + + @Test + fun handleClickWhenEnabled() = runTest { + val wasEnabled = true + repository.setIsEnabled(wasEnabled, testUser) + + underTest.handleInput(QSTileInputTestKtx.click(ColorCorrectionTileModel(wasEnabled))) + + assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled) + } + + @Test + fun handleClickWhenDisabled() = runTest { + val wasEnabled = false + repository.setIsEnabled(wasEnabled, testUser) + + underTest.handleInput(QSTileInputTestKtx.click(ColorCorrectionTileModel(wasEnabled))) + + assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled) + } + + @Test + fun handleLongClickWhenDisabled() = runTest { + val enabled = false + + underTest.handleInput(QSTileInputTestKtx.longClick(ColorCorrectionTileModel(enabled))) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_COLOR_CORRECTION_SETTINGS) + } + } + + @Test + fun handleLongClickWhenEnabled() = runTest { + val enabled = true + + underTest.handleInput(QSTileInputTestKtx.longClick(ColorCorrectionTileModel(enabled))) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_COLOR_CORRECTION_SETTINGS) + } + } +} diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml index 4d952209da6fca254e0e3b609b732c90adadc4a0..fc4bf8a65643f41c78054382f389007efcde0719 100644 --- a/packages/SystemUI/res/layout/sidefps_view.xml +++ b/packages/SystemUI/res/layout/sidefps_view.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.biometrics.SideFpsLottieViewWrapper +<com.android.systemui.biometrics.SideFpsIndicatorView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/sidefps_animation" diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt index 80f70a0cd2f2eaaf12466a33089be83d7949136d..30648291366aa3b200b9569535ec980105968f2c 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt @@ -37,6 +37,10 @@ data class BiometricModalities( val hasSfps: Boolean get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType + /** If UDFPS authentication is available. */ + val hasUdfps: Boolean + get() = hasFingerprint && fingerprintProperties!!.isAnyUdfpsType + /** If fingerprint authentication is available (and [faceProperties] is non-null). */ val hasFace: Boolean get() = faceProperties != null diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 0a4378e07b45c7304067294987ee59afe55109ed..cce2018f733f759ccb6f127450ef6f23b9b39ec8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -38,7 +38,7 @@ import android.app.admin.DevicePolicyManager; import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Resources; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.media.AudioManager; import android.metrics.LogMaker; import android.os.SystemClock; @@ -74,6 +74,7 @@ import com.android.systemui.Gefingerpoken; import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate; import com.android.systemui.biometrics.SideFpsController; import com.android.systemui.biometrics.SideFpsUiRequestSource; +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor; import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.classifier.FalsingA11yDelegate; @@ -486,7 +487,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mSceneContainerFlags = sceneContainerFlags; mGlobalSettings = globalSettings; mSessionTracker = sessionTracker; - mSideFpsController = sideFpsController; + if (SideFpsControllerRefactor.isEnabled()) { + mSideFpsController = Optional.empty(); + } else { + mSideFpsController = sideFpsController; + } mFalsingA11yDelegate = falsingA11yDelegate; mTelephonyManager = telephonyManager; mViewMediatorCallback = viewMediatorCallback; @@ -569,12 +574,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mView.clearFocus(); } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** * Shows and hides the side finger print sensor animation. * * @param isVisible sets whether we show or hide the side fps animation */ public void updateSideFpsVisibility(boolean isVisible) { + SideFpsControllerRefactor.assertInLegacyMode(); if (!mSideFpsController.isPresent()) { return; } @@ -582,7 +589,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (isVisible) { mSideFpsController.get().show( SideFpsUiRequestSource.PRIMARY_BOUNCER, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD + BiometricRequestConstants.REASON_AUTH_KEYGUARD ); } else { mSideFpsController.get().hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt index ca859de73a364abdcd40c8d3a57000282bb0bdd2..24aa11e10f309762d35b721313ef253c9a120436 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,61 +16,16 @@ package com.android.systemui.accessibility -import com.android.systemui.qs.tileimpl.QSTileImpl -import com.android.systemui.qs.tiles.ColorCorrectionTile -import com.android.systemui.qs.tiles.ColorInversionTile -import com.android.systemui.qs.tiles.DreamTile -import com.android.systemui.qs.tiles.FontScalingTile -import com.android.systemui.qs.tiles.NightDisplayTile -import com.android.systemui.qs.tiles.OneHandedModeTile -import com.android.systemui.qs.tiles.ReduceBrightColorsTile +import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository +import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl +import com.android.systemui.accessibility.qs.QSAccessibilityModule import dagger.Binds import dagger.Module -import dagger.multibindings.IntoMap -import dagger.multibindings.StringKey -@Module +@Module(includes = [QSAccessibilityModule::class]) interface AccessibilityModule { - - /** Inject ColorInversionTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(ColorInversionTile.TILE_SPEC) - fun bindColorInversionTile(colorInversionTile: ColorInversionTile): QSTileImpl<*> - - /** Inject NightDisplayTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(NightDisplayTile.TILE_SPEC) - fun bindNightDisplayTile(nightDisplayTile: NightDisplayTile): QSTileImpl<*> - - /** Inject ReduceBrightColorsTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(ReduceBrightColorsTile.TILE_SPEC) - fun bindReduceBrightColorsTile(reduceBrightColorsTile: ReduceBrightColorsTile): QSTileImpl<*> - - /** Inject OneHandedModeTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(OneHandedModeTile.TILE_SPEC) - fun bindOneHandedModeTile(oneHandedModeTile: OneHandedModeTile): QSTileImpl<*> - - /** Inject ColorCorrectionTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(ColorCorrectionTile.TILE_SPEC) - fun bindColorCorrectionTile(colorCorrectionTile: ColorCorrectionTile): QSTileImpl<*> - - /** Inject DreamTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(DreamTile.TILE_SPEC) - fun bindDreamTile(dreamTile: DreamTile): QSTileImpl<*> - - /** Inject FontScalingTile into tileMap in QSModule */ @Binds - @IntoMap - @StringKey(FontScalingTile.TILE_SPEC) - fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*> + abstract fun colorCorrectionRepository( + impl: ColorCorrectionRepositoryImpl + ): ColorCorrectionRepository } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..6483ae44d5ecd5f9406449b932cc3f258a557117 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt @@ -0,0 +1,73 @@ +/* + * 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.accessibility.data.repository + +import android.os.UserHandle +import android.provider.Settings.Secure +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext + +/** Provides data related to color correction. */ +interface ColorCorrectionRepository { + /** Observable for whether color correction is enabled */ + fun isEnabled(userHandle: UserHandle): Flow<Boolean> + + /** Sets color correction enabled state. */ + suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean +} + +@SysUISingleton +class ColorCorrectionRepositoryImpl +@Inject +constructor( + @Background private val bgCoroutineContext: CoroutineContext, + private val secureSettings: SecureSettings, +) : ColorCorrectionRepository { + + companion object { + const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED + const val DISABLED = 0 + const val ENABLED = 1 + } + + override fun isEnabled(userHandle: UserHandle): Flow<Boolean> = + secureSettings + .observerFlow(userHandle.identifier, SETTING_NAME) + .onStart { emit(Unit) } + .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED } + .distinctUntilChanged() + .flowOn(bgCoroutineContext) + + override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean = + withContext(bgCoroutineContext) { + secureSettings.putIntForUser( + SETTING_NAME, + if (isEnabled) ENABLED else DISABLED, + userHandle.identifier + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..df7fdb8e6058c1e871903cc36640f1a87c997361 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt @@ -0,0 +1,124 @@ +/* + * 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.accessibility.qs + +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.ColorCorrectionTile +import com.android.systemui.qs.tiles.ColorInversionTile +import com.android.systemui.qs.tiles.DreamTile +import com.android.systemui.qs.tiles.FontScalingTile +import com.android.systemui.qs.tiles.NightDisplayTile +import com.android.systemui.qs.tiles.OneHandedModeTile +import com.android.systemui.qs.tiles.ReduceBrightColorsTile +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.ColorCorrectionTileMapper +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionTileDataInteractor +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionUserActionInteractor +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.res.R +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +interface QSAccessibilityModule { + + /** Inject ColorInversionTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(ColorInversionTile.TILE_SPEC) + fun bindColorInversionTile(colorInversionTile: ColorInversionTile): QSTileImpl<*> + + /** Inject NightDisplayTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(NightDisplayTile.TILE_SPEC) + fun bindNightDisplayTile(nightDisplayTile: NightDisplayTile): QSTileImpl<*> + + /** Inject ReduceBrightColorsTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(ReduceBrightColorsTile.TILE_SPEC) + fun bindReduceBrightColorsTile(reduceBrightColorsTile: ReduceBrightColorsTile): QSTileImpl<*> + + /** Inject OneHandedModeTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(OneHandedModeTile.TILE_SPEC) + fun bindOneHandedModeTile(oneHandedModeTile: OneHandedModeTile): QSTileImpl<*> + + /** Inject ColorCorrectionTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(ColorCorrectionTile.TILE_SPEC) + fun bindColorCorrectionTile(colorCorrectionTile: ColorCorrectionTile): QSTileImpl<*> + + /** Inject DreamTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(DreamTile.TILE_SPEC) + fun bindDreamTile(dreamTile: DreamTile): QSTileImpl<*> + + /** Inject FontScalingTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(FontScalingTile.TILE_SPEC) + fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*> + + companion object { + + const val COLOR_CORRECTION_TILE_SPEC = "color_correction" + + @Provides + @IntoMap + @StringKey(COLOR_CORRECTION_TILE_SPEC) + fun provideColorCorrectionTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(COLOR_CORRECTION_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.ic_qs_color_correction, + labelRes = R.string.quick_settings_color_correction_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** Inject ColorCorrectionTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(COLOR_CORRECTION_TILE_SPEC) + fun provideColorCorrectionTileViewModel( + factory: QSTileViewModelFactory.Static<ColorCorrectionTileModel>, + mapper: ColorCorrectionTileMapper, + stateInteractor: ColorCorrectionTileDataInteractor, + userActionInteractor: ColorCorrectionUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(COLOR_CORRECTION_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 877afce7fe6577d5aacf15fcffa7499bc76359d1..5fba761b2f0942ab2d3fe8b1f6eec5fb909d2965 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -70,6 +70,7 @@ import com.android.systemui.CoreStartable; import com.android.systemui.biometrics.domain.interactor.LogContextInteractor; import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel; @@ -88,6 +89,8 @@ import com.android.systemui.util.concurrency.Execution; import dagger.Lazy; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -101,7 +104,6 @@ import java.util.Set; import javax.inject.Inject; import javax.inject.Provider; -import kotlin.Unit; import kotlinx.coroutines.CoroutineScope; /** @@ -317,7 +319,9 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null; if (mSidefpsProps != null) { - mSideFpsController = mSidefpsControllerFactory.get(); + if (!SideFpsControllerRefactor.isEnabled()) { + mSideFpsController = mSidefpsControllerFactory.get(); + } } mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() { @@ -1194,7 +1198,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, * Whether the passed userId has enrolled SFPS. */ public boolean isSfpsEnrolled(int userId) { - if (mSideFpsController == null) { + if (mSidefpsProps == null) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 91cee9e51a93b08f7495e904a20dde8250d08bc9..ac99fc69b2b5c11113f0ac0073c102f9f35cd67d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -23,9 +23,9 @@ import android.graphics.PixelFormat import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS import android.hardware.biometrics.SensorLocationInternal import android.hardware.display.DisplayManager import android.hardware.fingerprint.FingerprintManager @@ -58,6 +58,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.keyguard.KeyguardPINView import com.android.systemui.Dumpable import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -91,7 +92,7 @@ constructor( @Main private val mainExecutor: DelayableExecutor, @Main private val handler: Handler, private val alternateBouncerInteractor: AlternateBouncerInteractor, - @Application private val scope: CoroutineScope, + @Application private val applicationScope: CoroutineScope, dumpManager: DumpManager, fpsUnlockTracker: FpsUnlockTracker ) : Dumpable { @@ -110,7 +111,7 @@ constructor( handler, sensorProps, { reason -> onOrientationChanged(reason) }, - BiometricOverlayConstants.REASON_UNKNOWN + BiometricRequestConstants.REASON_UNKNOWN ) @VisibleForTesting val orientationListener = orientationReasonListener.orientationListener @@ -169,25 +170,27 @@ constructor( } init { - fpsUnlockTracker.startTracking() - fingerprintManager?.setSidefpsController( - object : ISidefpsController.Stub() { - override fun show( - sensorId: Int, - @BiometricOverlayConstants.ShowReason reason: Int - ) = - if (reason.isReasonToAutoShow(activityTaskManager)) { - show(SideFpsUiRequestSource.AUTO_SHOW, reason) - } else { - hide(SideFpsUiRequestSource.AUTO_SHOW) - } - - override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW) - } - ) - listenForAlternateBouncerVisibility() + if (!SideFpsControllerRefactor.isEnabled) { + fpsUnlockTracker.startTracking() + fingerprintManager?.setSidefpsController( + object : ISidefpsController.Stub() { + override fun show( + sensorId: Int, + @BiometricRequestConstants.RequestReason reason: Int + ) = + if (reason.isReasonToAutoShow(activityTaskManager)) { + show(SideFpsUiRequestSource.AUTO_SHOW, reason) + } else { + hide(SideFpsUiRequestSource.AUTO_SHOW) + } + + override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW) + } + ) + listenForAlternateBouncerVisibility() - dumpManager.registerDumpable(this) + dumpManager.registerDumpable(this) + } } private fun listenForAlternateBouncerVisibility() { @@ -195,7 +198,7 @@ constructor( alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, "SideFpsController") } - scope.launch { + applicationScope.launch { alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> if (isVisible) { show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD) @@ -209,8 +212,10 @@ constructor( /** Shows the side fps overlay if not already shown. */ fun show( request: SideFpsUiRequestSource, - @BiometricOverlayConstants.ShowReason reason: Int = BiometricOverlayConstants.REASON_UNKNOWN + @BiometricRequestConstants.RequestReason + reason: Int = BiometricRequestConstants.REASON_UNKNOWN ) { + SideFpsControllerRefactor.assertInLegacyMode() if (!displayStateInteractor.isInRearDisplayMode.value) { requests.add(request) mainExecutor.execute { @@ -229,6 +234,7 @@ constructor( /** Hides the fps overlay if shown. */ fun hide(request: SideFpsUiRequestSource) { + SideFpsControllerRefactor.assertInLegacyMode() requests.remove(request) mainExecutor.execute { if (requests.isEmpty()) { @@ -239,6 +245,7 @@ constructor( /** Hide the arrow indicator. */ fun hideIndicator() { + SideFpsControllerRefactor.assertInLegacyMode() val lottieAnimationView = overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView? lottieAnimationView?.visibility = INVISIBLE @@ -246,6 +253,7 @@ constructor( /** Show the arrow indicator. */ fun showIndicator() { + SideFpsControllerRefactor.assertInLegacyMode() val lottieAnimationView = overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView? lottieAnimationView?.visibility = VISIBLE @@ -279,13 +287,13 @@ constructor( pw.println("currentRotation=${displayInfo.rotation}") } - private fun onOrientationChanged(@BiometricOverlayConstants.ShowReason reason: Int) { + private fun onOrientationChanged(@BiometricRequestConstants.RequestReason reason: Int) { if (overlayView != null) { createOverlayForDisplay(reason) } } - private fun createOverlayForDisplay(@BiometricOverlayConstants.ShowReason reason: Int) { + private fun createOverlayForDisplay(@BiometricRequestConstants.RequestReason reason: Int) { val view = layoutInflater.inflate(R.layout.sidefps_view, null, false) overlayView = view val display = context.display!! @@ -395,7 +403,7 @@ private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorProper /** Returns [True] when the device has a side fingerprint sensor. */ fun FingerprintManager?.hasSideFpsSensor(): Boolean = this?.sideFpsSensorProperties != null -@BiometricOverlayConstants.ShowReason +@BiometricRequestConstants.RequestReason private fun Int.isReasonToAutoShow(activityTaskManager: ActivityTaskManager): Boolean = when (this) { REASON_AUTH_KEYGUARD -> false @@ -434,7 +442,7 @@ private fun Display.isNaturalOrientation(): Boolean = private fun LottieAnimationView.addOverlayDynamicColor( context: Context, - @BiometricOverlayConstants.ShowReason reason: Int + @BiometricRequestConstants.RequestReason reason: Int ) { fun update() { val isKeyguard = reason == REASON_AUTH_KEYGUARD @@ -501,7 +509,7 @@ class OrientationReasonListener( handler: Handler, sensorProps: FingerprintSensorPropertiesInternal, onOrientationChanged: (reason: Int) -> Unit, - @BiometricOverlayConstants.ShowReason var reason: Int + @BiometricRequestConstants.RequestReason var reason: Int ) { val orientationListener = BiometricDisplayListener( @@ -516,7 +524,7 @@ class OrientationReasonListener( /** * The source of a request to show the side fps visual indicator. This is distinct from - * [BiometricOverlayConstants] which corrresponds with the reason fingerprint authentication is + * [BiometricRequestConstants] which corresponds with the reason fingerprint authentication is * requested. */ enum class SideFpsUiRequestSource { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsIndicatorView.kt similarity index 96% rename from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt rename to packages/SystemUI/src/com/android/systemui/biometrics/SideFpsIndicatorView.kt index e98f6db12d348ce97c175378c422705b32426c6a..d5e25ac84abacea3d7b5513f7cbf0387904b3273 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsIndicatorView.kt @@ -19,6 +19,6 @@ import android.content.Context import android.util.AttributeSet import com.android.systemui.util.wrapper.LottieViewWrapper -class SideFpsLottieViewWrapper +class SideFpsIndicatorView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index bb6ef41bdfd387a6b204874cfd83d598c6aab5b0..65668b56a9f3184ad55482fa4226e6c112d98f0f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -19,10 +19,10 @@ package com.android.systemui.biometrics; import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT; import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; -import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP; -import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD; -import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING; -import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR; +import static android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP; +import static android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD; +import static android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING; +import static android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR; import static com.android.internal.util.LatencyTracker.ACTION_UDFPS_ILLUMINATE; import static com.android.internal.util.Preconditions.checkNotNull; @@ -106,6 +106,8 @@ import com.android.systemui.util.time.SystemClock; import dagger.Lazy; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -115,8 +117,6 @@ import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Provider; -import kotlin.Unit; - import kotlinx.coroutines.ExperimentalCoroutinesApi; /** diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 452cd6a7c4df498d5a90470fc4f6203a0ba62031..dae6d08f733198a8677f735b2a38250fea12242b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -21,13 +21,13 @@ import android.annotation.UiThread import android.content.Context import android.graphics.PixelFormat import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR -import android.hardware.biometrics.BiometricOverlayConstants.ShowReason +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR +import android.hardware.biometrics.BiometricRequestConstants.RequestReason import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.os.Build import android.os.RemoteException @@ -96,7 +96,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, val requestId: Long, - @ShowReason val requestReason: Int, + @RequestReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, private val onTouch: (View, MotionEvent, Boolean) -> Boolean, private val activityLaunchAnimator: ActivityLaunchAnimator, @@ -461,7 +461,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } } -@ShowReason +@RequestReason private fun Int.isImportantForAccessibility() = this == REASON_ENROLL_FIND_SENSOR || this == REASON_ENROLL_ENROLLING || diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt index e3dbcb52325856c7d2b44973f4c5238090feb61a..88b9e1bdfd9716eefad35a8ae28718e319106df5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt @@ -18,13 +18,13 @@ package com.android.systemui.biometrics import android.content.Context import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR -import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR +import android.hardware.biometrics.BiometricRequestConstants.REASON_UNKNOWN import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.util.Log import android.view.LayoutInflater diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt index 72fcfe777e792e0a95cdedd657e077b70ac2f383..8ae6f87f4f8316066560addfc3be534edc63758d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt @@ -18,8 +18,11 @@ package com.android.systemui.biometrics.dagger import android.content.res.Resources import com.android.internal.R +import com.android.systemui.CoreStartable import com.android.systemui.biometrics.EllipseOverlapDetectorParams import com.android.systemui.biometrics.UdfpsUtils +import com.android.systemui.biometrics.data.repository.BiometricStatusRepository +import com.android.systemui.biometrics.data.repository.BiometricStatusRepositoryImpl import com.android.systemui.biometrics.data.repository.DisplayStateRepository import com.android.systemui.biometrics.data.repository.DisplayStateRepositoryImpl import com.android.systemui.biometrics.data.repository.FacePropertyRepository @@ -33,11 +36,14 @@ import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector import com.android.systemui.biometrics.udfps.EllipseOverlapDetector import com.android.systemui.biometrics.udfps.OverlapDetector +import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder import com.android.systemui.dagger.SysUISingleton import com.android.systemui.util.concurrency.ThreadFactory import dagger.Binds import dagger.Module import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import java.util.concurrent.Executor import javax.inject.Qualifier @@ -45,6 +51,11 @@ import javax.inject.Qualifier @Module interface BiometricsModule { + @Binds + @IntoMap + @ClassKey(SideFpsOverlayViewBinder::class) + fun bindsSideFpsOverlayViewBinder(viewBinder: SideFpsOverlayViewBinder): CoreStartable + @Binds @SysUISingleton fun faceSettings(impl: FaceSettingsRepositoryImpl): FaceSettingsRepository @@ -55,6 +66,10 @@ interface BiometricsModule { @SysUISingleton fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository + @Binds + @SysUISingleton + fun biometricStatusRepository(impl: BiometricStatusRepositoryImpl): BiometricStatusRepository + @Binds @SysUISingleton fun fingerprintRepository( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..ad2136af4b860698fe92407ec0f8afe3993ee60e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt @@ -0,0 +1,112 @@ +/* + * 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.biometrics.data.repository + +import android.hardware.biometrics.AuthenticationStateListener +import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.shareIn + +/** A repository for the state of biometric authentication. */ +interface BiometricStatusRepository { + /** + * The logical reason for the current fingerprint auth operation if one is on-going, otherwise + * [NotRunning]. + */ + val fingerprintAuthenticationReason: Flow<AuthenticationReason> +} + +@SysUISingleton +class BiometricStatusRepositoryImpl +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val biometricManager: BiometricManager? +) : BiometricStatusRepository { + + override val fingerprintAuthenticationReason: Flow<AuthenticationReason> = + conflatedCallbackFlow { + val updateFingerprintAuthenticateReason = { reason: AuthenticationReason -> + trySendWithFailureLogging( + reason, + TAG, + "Error sending fingerprintAuthenticateReason reason" + ) + } + + val authenticationStateListener = + object : AuthenticationStateListener.Stub() { + override fun onAuthenticationStarted(requestReason: Int) { + val authenticationReason = + when (requestReason) { + REASON_AUTH_BP -> + AuthenticationReason.BiometricPromptAuthentication + REASON_AUTH_KEYGUARD -> + AuthenticationReason.DeviceEntryAuthentication + REASON_AUTH_OTHER -> AuthenticationReason.OtherAuthentication + REASON_AUTH_SETTINGS -> + AuthenticationReason.SettingsAuthentication( + SettingsOperations.OTHER + ) + REASON_ENROLL_ENROLLING -> + AuthenticationReason.SettingsAuthentication( + SettingsOperations.ENROLL_ENROLLING + ) + REASON_ENROLL_FIND_SENSOR -> + AuthenticationReason.SettingsAuthentication( + SettingsOperations.ENROLL_FIND_SENSOR + ) + else -> AuthenticationReason.Unknown + } + updateFingerprintAuthenticateReason(authenticationReason) + } + + override fun onAuthenticationStopped() { + updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning) + } + } + + updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning) + biometricManager?.registerAuthenticationStateListener(authenticationStateListener) + awaitClose { + biometricManager?.unregisterAuthenticationStateListener( + authenticationStateListener + ) + } + } + .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1) + + companion object { + private const val TAG = "BiometricStatusRepositoryImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt index b9b2fd8875d9f3051efefe96f42c465d4e89e6cc..ec3fd9f7da359cd76b1bb32cbe60fb13aaca2636 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt @@ -15,6 +15,8 @@ */ package com.android.systemui.biometrics.domain +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl import com.android.systemui.biometrics.domain.interactor.CredentialInteractor import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor @@ -36,6 +38,12 @@ interface BiometricsDomainLayerModule { impl: PromptSelectorInteractorImpl ): PromptSelectorInteractor + @Binds + @SysUISingleton + fun providesBiometricStatusInteractor( + impl: BiometricStatusInteractorImpl + ): BiometricStatusInteractor + @Binds @SysUISingleton fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt new file mode 100644 index 0000000000000000000000000000000000000000..55a2d3d7563e4f34aa0546ea4185fa8cc5a57545 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt @@ -0,0 +1,74 @@ +/* + * 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.biometrics.domain.interactor + +import android.app.ActivityTaskManager +import com.android.systemui.biometrics.data.repository.BiometricStatusRepository +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** Encapsulates business logic for interacting with biometric authentication state. */ +interface BiometricStatusInteractor { + /** + * The logical reason for the current side fingerprint sensor auth operation if one is on-going, + * filtered for when the overlay should be shown, otherwise [NotRunning]. + */ + val sfpsAuthenticationReason: Flow<AuthenticationReason> +} + +class BiometricStatusInteractorImpl +@Inject +constructor( + private val activityTaskManager: ActivityTaskManager, + biometricStatusRepository: BiometricStatusRepository, +) : BiometricStatusInteractor { + + override val sfpsAuthenticationReason: Flow<AuthenticationReason> = + biometricStatusRepository.fingerprintAuthenticationReason.map { reason: AuthenticationReason + -> + if (reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager)) { + reason + } else { + AuthenticationReason.NotRunning + } + } + + companion object { + private const val TAG = "BiometricStatusInteractor" + } +} + +/** True if the sfps overlay should always be updated for this request source, false otherwise. */ +private fun AuthenticationReason.isReasonToAlwaysUpdateSfpsOverlay( + activityTaskManager: ActivityTaskManager +): Boolean = + when (this) { + AuthenticationReason.DeviceEntryAuthentication -> false + AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER) -> + when (activityTaskManager.topClass()) { + // TODO(b/186176653): exclude fingerprint overlays from this list view + "com.android.settings.biometrics.fingerprint.FingerprintSettings" -> false + else -> true + } + else -> true + } + +internal fun ActivityTaskManager.topClass(): String = + getTasks(1).firstOrNull()?.topActivity?.className ?: "" diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt index f51379940640b0fbfcbf0bdf213620a885f5e4d4..f4231ac01fee90989a3ee9928ec2ed595f8402c6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt @@ -141,7 +141,6 @@ constructor( } } } - SideFpsSensorLocation( left = sensorLeft, top = sensorTop, @@ -149,7 +148,15 @@ constructor( isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation ) } - .distinctUntilChanged() + .distinctUntilChanged( + areEquivalent = { old: SideFpsSensorLocation, new: SideFpsSensorLocation -> + old.left == new.left && + old.top == new.top && + old.length == new.length && + old.isSensorVerticalInDefaultOrientation == + new.isSensorVerticalInDefaultOrientation + } + ) .onEach { logger.sensorLocationStateChanged( it.left, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/SideFpsControllerRefactor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/SideFpsControllerRefactor.kt new file mode 100644 index 0000000000000000000000000000000000000000..899b07e89964257d2410e55b36c54da8443a579d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/SideFpsControllerRefactor.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.shared + +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils +import com.android.systemui.shared.Flags + +/** Helper for reading or using the sidefps controller refactor flag state. */ +@Suppress("NOTHING_TO_INLINE") +object SideFpsControllerRefactor { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.sidefpsControllerRefactor() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationReason.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationReason.kt new file mode 100644 index 0000000000000000000000000000000000000000..0c3debbe0fc47046688838f5a01fd2002b5b4fdf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationReason.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.shared.model + +/** + * The logical reason for a fingerprint auth operation if one is on-going, otherwise [NotRunning]. + */ +sealed interface AuthenticationReason { + /** Device entry requested authentication */ + data object DeviceEntryAuthentication : AuthenticationReason + + /** Settings requested authentication */ + data class SettingsAuthentication(val settingsOperation: SettingsOperations) : + AuthenticationReason + + /** App requested authentication */ + data object BiometricPromptAuthentication : AuthenticationReason + + /** Authentication requested for other reason */ + data object OtherAuthentication : AuthenticationReason + + /** Authentication requested for unknown reason */ + data object Unknown : AuthenticationReason + + /** Authentication is not running */ + data object NotRunning : AuthenticationReason + + /** Settings operations that request biometric authentication */ + enum class SettingsOperations { + ENROLL_ENROLLING, + ENROLL_FIND_SENSOR, + OTHER + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LottieCallback.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LottieCallback.kt new file mode 100644 index 0000000000000000000000000000000000000000..0b3005530e6d29d2921b3c2d6b9cf89812f9d6c8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LottieCallback.kt @@ -0,0 +1,22 @@ +/* + * 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.biometrics.shared.model + +import com.airbnb.lottie.model.KeyPath + +/** Represents properties of a LottieAnimationView callback */ +data class LottieCallback(val keypath: KeyPath, val color: Int) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 32d9067bd9e483c318a4ddb730717fef1974fccc..90e4a3821634cf45fed65c3750ac6121d6d908aa 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -376,6 +376,22 @@ object BiometricViewBinder { } } + // Talkback directional guidance + backgroundView.setOnHoverListener { _, event -> + launch { + viewModel.onAnnounceAccessibilityHint( + event, + accessibilityManager.isTouchExplorationEnabled + ) + } + false + } + launch { + viewModel.accessibilityHint.collect { message -> + if (message.isNotBlank()) view.announceForAccessibility(message) + } + } + // Play haptics launch { viewModel.hapticsToPlay.collect { hapticFeedbackConstant -> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt new file mode 100644 index 0000000000000000000000000000000000000000..a8c9446fd689ea78093a7f7d01198d1a7c1d9d14 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt @@ -0,0 +1,224 @@ +/* + * 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.biometrics.ui.binder + +import android.content.Context +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.view.accessibility.AccessibilityEvent +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.airbnb.lottie.LottieAnimationView +import com.airbnb.lottie.LottieComposition +import com.airbnb.lottie.LottieProperty +import com.android.app.animation.Interpolators +import com.android.keyguard.KeyguardPINView +import com.android.systemui.CoreStartable +import com.android.systemui.biometrics.FpsUnlockTracker +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor +import com.android.systemui.biometrics.shared.model.AuthenticationReason.NotRunning +import com.android.systemui.biometrics.shared.model.LottieCallback +import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor +import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +/** Binds the side fingerprint sensor indicator view to [SideFpsOverlayViewModel]. */ +@SysUISingleton +class SideFpsOverlayViewBinder +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Application private val applicationContext: Context, + private val biometricStatusInteractor: BiometricStatusInteractor, + private val displayStateInteractor: DisplayStateInteractor, + private val deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor, + private val fpsUnlockTracker: FpsUnlockTracker, + private val layoutInflater: LayoutInflater, + private val sideFpsProgressBarViewModel: SideFpsProgressBarViewModel, + private val sfpsSensorInteractor: SideFpsSensorInteractor, + private val windowManager: WindowManager +) : CoreStartable { + + override fun start() { + if (!SideFpsControllerRefactor.isEnabled) { + return + } + applicationScope + .launch { + combine( + biometricStatusInteractor.sfpsAuthenticationReason, + deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry, + sideFpsProgressBarViewModel.isVisible, + ::Triple + ) + .sample(displayStateInteractor.isInRearDisplayMode, ::Pair) + .collect { (combinedFlows, isInRearDisplayMode: Boolean) -> + val ( + systemServerAuthReason, + showIndicatorForDeviceEntry, + progressBarIsVisible) = + combinedFlows + if (!isInRearDisplayMode) { + if (progressBarIsVisible) { + hide() + } else if (systemServerAuthReason != NotRunning) { + show() + } else if (showIndicatorForDeviceEntry) { + show() + } else { + hide() + } + } + } + } + .invokeOnCompletion { fpsUnlockTracker.stopTracking() } + } + + private var overlayView: View? = null + private var lottie: LottieAnimationView? = null + + /** Show the side fingerprint sensor indicator */ + private fun show() { + overlayView?.let { + if (it.isAttachedToWindow) { + lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation) + lottie?.pauseAnimation() + windowManager.removeView(it) + } + } + + overlayView = layoutInflater.inflate(R.layout.sidefps_view, null, false) + val overlayViewModel = + SideFpsOverlayViewModel( + applicationContext, + biometricStatusInteractor, + deviceEntrySideFpsOverlayInteractor, + displayStateInteractor, + sfpsSensorInteractor, + sideFpsProgressBarViewModel + ) + bind(overlayView!!, overlayViewModel, fpsUnlockTracker, windowManager) + overlayView!!.visibility = View.INVISIBLE + windowManager.addView(overlayView, overlayViewModel.defaultOverlayViewParams) + } + + /** Hide the side fingerprint sensor indicator */ + private fun hide() { + if (overlayView != null) { + windowManager.removeView(overlayView) + overlayView = null + } + } + + companion object { + private const val TAG = "SideFpsOverlayViewBinder" + + /** Binds overlayView (side fingerprint sensor indicator view) to SideFpsOverlayViewModel */ + fun bind( + overlayView: View, + viewModel: SideFpsOverlayViewModel, + fpsUnlockTracker: FpsUnlockTracker, + windowManager: WindowManager + ) { + overlayView.repeatWhenAttached { + fpsUnlockTracker.startTracking() + + val lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation) + lottie.addLottieOnCompositionLoadedListener { composition: LottieComposition -> + viewModel.setLottieBounds(composition.bounds) + overlayView.visibility = View.VISIBLE + } + it.alpha = 0f + val overlayShowAnimator = + it.animate() + .alpha(1f) + .setDuration(KeyguardPINView.ANIMATION_DURATION) + .setInterpolator(Interpolators.ALPHA_IN) + + overlayShowAnimator.start() + + it.setAccessibilityDelegate( + object : View.AccessibilityDelegate() { + override fun dispatchPopulateAccessibilityEvent( + host: View, + event: AccessibilityEvent + ): Boolean { + return if ( + event.getEventType() === + android.view.accessibility.AccessibilityEvent + .TYPE_WINDOW_STATE_CHANGED + ) { + true + } else { + super.dispatchPopulateAccessibilityEvent(host, event) + } + } + } + ) + + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.lottieCallbacks.collect { callbacks -> + lottie.addOverlayDynamicColor(callbacks) + } + } + + launch { + viewModel.overlayViewParams.collect { params -> + windowManager.updateViewLayout(it, params) + lottie.resumeAnimation() + } + } + + launch { + viewModel.overlayViewProperties.collect { properties -> + it.rotation = properties.overlayViewRotation + lottie.setAnimation(properties.indicatorAsset) + } + } + } + } + } + } +} + +private fun LottieAnimationView.addOverlayDynamicColor(colorCallbacks: List<LottieCallback>) { + addLottieOnCompositionLoadedListener { + for (callback in colorCallbacks) { + addValueCallback(callback.keypath, LottieProperty.COLOR_FILTER) { + PorterDuffColorFilter(callback.color, PorterDuff.Mode.SRC_ATOP) + } + } + resumeAnimation() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 647aaf392ed802ebb629f34fdda23d7be95a6d72..6d0a58e202bdafabd3488b51e33785305740c1d7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -21,9 +21,12 @@ import android.hardware.biometrics.BiometricPrompt import android.util.Log import android.view.HapticFeedbackConstants import android.view.MotionEvent +import com.android.systemui.Flags.bpTalkback +import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.shared.model.DisplayRotation @@ -35,7 +38,9 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -49,7 +54,9 @@ class PromptViewModel constructor( displayStateInteractor: DisplayStateInteractor, promptSelectorInteractor: PromptSelectorInteractor, - @Application context: Context, + @Application private val context: Context, + private val udfpsOverlayInteractor: UdfpsOverlayInteractor, + private val udfpsUtils: UdfpsUtils ) { /** The set of modalities available for this prompt */ val modalities: Flow<BiometricModalities> = @@ -69,6 +76,11 @@ constructor( val faceIconHeight: Int = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size) + private val _accessibilityHint = MutableSharedFlow<String>() + + /** Hint for talkback directional guidance */ + val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow() + private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false) /** If the user is currently authenticating (i.e. at least one biometric is scanning). */ @@ -516,6 +528,40 @@ constructor( return false } + /** Sets the message used for UDFPS directional guidance */ + suspend fun onAnnounceAccessibilityHint( + event: MotionEvent, + touchExplorationEnabled: Boolean, + ): Boolean { + if (bpTalkback() && modalities.first().hasUdfps && touchExplorationEnabled) { + // TODO(b/315184924): Remove uses of UdfpsUtils + val scaledTouch = + udfpsUtils.getTouchInNativeCoordinates( + event.getPointerId(0), + event, + udfpsOverlayInteractor.udfpsOverlayParams.value + ) + if ( + !udfpsUtils.isWithinSensorArea( + event.getPointerId(0), + event, + udfpsOverlayInteractor.udfpsOverlayParams.value + ) + ) { + _accessibilityHint.emit( + udfpsUtils.onTouchOutsideOfSensorArea( + touchExplorationEnabled, + context, + scaledTouch.x, + scaledTouch.y, + udfpsOverlayInteractor.udfpsOverlayParams.value + ) + ) + } + } + return false + } + /** * Switch to the credential view. * diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..ce726034913fc2ad09c641ee10aa3e9747bc4686 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt @@ -0,0 +1,229 @@ +/* + * 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.biometrics.ui.viewmodel + +import android.content.Context +import android.content.res.Configuration +import android.graphics.Color +import android.graphics.PixelFormat +import android.graphics.Point +import android.graphics.Rect +import android.view.Gravity +import android.view.WindowManager +import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION +import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY +import com.airbnb.lottie.model.KeyPath +import com.android.systemui.biometrics.Utils +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor +import com.android.systemui.biometrics.domain.model.SideFpsSensorLocation +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.LottieCallback +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor +import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged + +/** Models UI of the side fingerprint sensor indicator view. */ +class SideFpsOverlayViewModel +@Inject +constructor( + @Application private val applicationContext: Context, + biometricStatusInteractor: BiometricStatusInteractor, + deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor, + displayStateInteractor: DisplayStateInteractor, + sfpsSensorInteractor: SideFpsSensorInteractor, + sideFpsProgressBarViewModel: SideFpsProgressBarViewModel +) { + /** Contains properties of the side fingerprint sensor indicator */ + data class OverlayViewProperties( + /** The raw asset for the indicator animation */ + val indicatorAsset: Int, + /** Rotation of the overlayView */ + val overlayViewRotation: Float, + ) + + private val _lottieBounds: MutableStateFlow<Rect?> = MutableStateFlow(null) + + /** Used for setting lottie bounds once the composition has loaded. */ + fun setLottieBounds(bounds: Rect) { + _lottieBounds.value = bounds + } + + private val displayRotation = displayStateInteractor.currentRotation + private val sensorLocation = sfpsSensorInteractor.sensorLocation + + /** Default LayoutParams for the overlayView */ + val defaultOverlayViewParams: WindowManager.LayoutParams + get() = + WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS, + PixelFormat.TRANSLUCENT + ) + .apply { + title = TAG + fitInsetsTypes = 0 // overrides default, avoiding status bars during layout + gravity = Gravity.TOP or Gravity.LEFT + layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION + } + + private val indicatorAsset: Flow<Int> = + combine(displayRotation, sensorLocation) { rotation: DisplayRotation, sensorLocation -> + val yAligned = sensorLocation.isSensorVerticalInDefaultOrientation + val newAsset: Int = + when (rotation) { + DisplayRotation.ROTATION_0 -> + if (yAligned) { + R.raw.sfps_pulse + } else { + R.raw.sfps_pulse_landscape + } + DisplayRotation.ROTATION_180 -> + if (yAligned) { + R.raw.sfps_pulse + } else { + R.raw.sfps_pulse_landscape + } + else -> + if (yAligned) { + R.raw.sfps_pulse_landscape + } else { + R.raw.sfps_pulse + } + } + newAsset + } + .distinctUntilChanged() + + private val overlayViewRotation: Flow<Float> = + combine( + displayRotation, + sensorLocation, + ) { rotation: DisplayRotation, sensorLocation -> + val yAligned = sensorLocation.isSensorVerticalInDefaultOrientation + when (rotation) { + DisplayRotation.ROTATION_90 -> if (yAligned) 0f else 180f + DisplayRotation.ROTATION_180 -> 180f + DisplayRotation.ROTATION_270 -> if (yAligned) 180f else 0f + else -> 0f + } + } + .distinctUntilChanged() + + /** Contains properties (animation asset and view rotation) for overlayView */ + val overlayViewProperties: Flow<OverlayViewProperties> = + combine(indicatorAsset, overlayViewRotation) { asset: Int, rotation: Float -> + OverlayViewProperties(asset, rotation) + } + + /** LayoutParams for placement of overlayView (the side fingerprint sensor indicator view) */ + val overlayViewParams: Flow<WindowManager.LayoutParams> = + combine( + _lottieBounds, + sensorLocation, + displayRotation, + ) { bounds: Rect?, sensorLocation: SideFpsSensorLocation, displayRotation: DisplayRotation + -> + val topLeft = Point(sensorLocation.left, sensorLocation.top) + + if (sensorLocation.isSensorVerticalInDefaultOrientation) { + if (displayRotation == DisplayRotation.ROTATION_0) { + topLeft.x -= bounds!!.width() + } else if (displayRotation == DisplayRotation.ROTATION_270) { + topLeft.y -= bounds!!.height() + } + } else { + if (displayRotation == DisplayRotation.ROTATION_180) { + topLeft.y -= bounds!!.height() + } else if (displayRotation == DisplayRotation.ROTATION_270) { + topLeft.x -= bounds!!.width() + } + } + defaultOverlayViewParams.apply { + x = topLeft.x + y = topLeft.y + } + } + + /** List of LottieCallbacks use for adding dynamic color to the overlayView */ + val lottieCallbacks: Flow<List<LottieCallback>> = + combine( + biometricStatusInteractor.sfpsAuthenticationReason, + deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry.distinctUntilChanged(), + sideFpsProgressBarViewModel.isVisible, + ) { reason: AuthenticationReason, showIndicatorForDeviceEntry: Boolean, progressBarIsVisible + -> + val callbacks = mutableListOf<LottieCallback>() + if (showIndicatorForDeviceEntry) { + val indicatorColor = + com.android.settingslib.Utils.getColorAttrDefaultColor( + applicationContext, + com.android.internal.R.attr.materialColorPrimaryFixed + ) + val outerRimColor = + com.android.settingslib.Utils.getColorAttrDefaultColor( + applicationContext, + com.android.internal.R.attr.materialColorPrimaryFixedDim + ) + val chevronFill = + com.android.settingslib.Utils.getColorAttrDefaultColor( + applicationContext, + com.android.internal.R.attr.materialColorOnPrimaryFixed + ) + callbacks.add(LottieCallback(KeyPath(".blue600", "**"), indicatorColor)) + callbacks.add(LottieCallback(KeyPath(".blue400", "**"), outerRimColor)) + callbacks.add(LottieCallback(KeyPath(".black", "**"), chevronFill)) + } else { + if (!isDarkMode(applicationContext)) { + callbacks.add(LottieCallback(KeyPath(".black", "**"), Color.WHITE)) + } + for (key in listOf(".blue600", ".blue400")) { + callbacks.add( + LottieCallback( + KeyPath(key, "**"), + applicationContext.getColor( + com.android.settingslib.color.R.color.settingslib_color_blue400 + ), + ) + ) + } + } + callbacks + } + + companion object { + private const val TAG = "SideFpsOverlayViewModel" + } +} + +private fun isDarkMode(context: Context): Boolean { + val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + return darkMode == Configuration.UI_MODE_NIGHT_YES +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt index 1e0e16c5472f9b9eca31052488b72266bdf6baf7..c2a1d8fe26c3bcc23d91a6ae92cc32a4add5c09d 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.bouncer.data.repository import android.os.Build import android.util.Log +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel import com.android.systemui.dagger.SysUISingleton @@ -121,6 +122,7 @@ interface KeyguardBouncerRepository { fun setAlternateBouncerUIAvailable(isAvailable: Boolean) + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR fun setSideFpsShowing(isShowing: Boolean) } @@ -261,7 +263,9 @@ constructor( _isBackButtonEnabled.value = isBackButtonEnabled } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR override fun setSideFpsShowing(isShowing: Boolean) { + SideFpsControllerRefactor.assertInLegacyMode() _sideFpsShowing.value = isShowing } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index aa7758f9380f80eefff3ae6e6f8441a0a909b6c7..621ca5dc6f5a36749d2ce002ecc94f23d9816982 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.DejankUtils +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN @@ -116,9 +117,11 @@ constructor( /** Allow for interaction when just about fully visible */ val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing private var currentUserActiveUnlockRunning = false + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** This callback needs to be a class field so it does not get garbage collected. */ val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { @@ -135,7 +138,10 @@ constructor( } init { - keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + if (!SideFpsControllerRefactor.isEnabled) { + keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + } applicationScope.launch { trustRepository.isCurrentUserActiveUnlockRunning.collect { currentUserActiveUnlockRunning = it @@ -333,8 +339,10 @@ constructor( repository.setPrimaryStartDisappearAnimation(runnable) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** Determine whether to show the side fps animation. */ fun updateSideFpsVisibility() { + SideFpsControllerRefactor.assertInLegacyMode() val sfpsEnabled: Boolean = context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer) val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt index ac3d4b6c853b84f18cc1c97c8b51112b797f41c3..5dcd6615509d93639456be1dfcc230fd52042560 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt @@ -27,6 +27,7 @@ import com.android.keyguard.KeyguardSecurityContainerController import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityView import com.android.keyguard.dagger.KeyguardBouncerComponent +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.bouncer.ui.BouncerViewDelegate @@ -233,15 +234,21 @@ object KeyguardBouncerViewBinder { .collect { view.systemUiVisibility = it } } - launch { - viewModel.shouldUpdateSideFps.collect { - viewModel.updateSideFpsVisibility() + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + if (!SideFpsControllerRefactor.isEnabled) { + launch { + viewModel.shouldUpdateSideFps.collect { + viewModel.updateSideFpsVisibility() + } } } - launch { - viewModel.sideFpsShowing.collect { - securityContainerController.updateSideFpsVisibility(it) + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + if (!SideFpsControllerRefactor.isEnabled) { + launch { + viewModel.sideFpsShowing.collect { + securityContainerController.updateSideFpsVisibility(it) + } } } awaitCancellation() diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt index 649ae2fb1007fd1282e3c46c86119d214f899f89..1c9d1f01e89e7a6927e68cedbd72d6aa592ee69a 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.view.View +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel import com.android.systemui.bouncer.ui.BouncerView @@ -61,9 +62,11 @@ constructor( /** Observe whether keyguard is authenticated already. */ val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticatedBiometrics + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** Observe whether the side fps is showing. */ val sideFpsShowing: Flow<Boolean> = interactor.sideFpsShowing + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** Observe whether we should update fps is showing. */ val shouldUpdateSideFps: Flow<Unit> = merge( @@ -87,7 +90,9 @@ constructor( interactor.onMessageShown() } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR fun updateSideFpsVisibility() { + SideFpsControllerRefactor.assertInLegacyMode() interactor.updateSideFpsVisibility() } diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java index 7ac1cc796d4b0e989c9d3c899993d52f0b8ac4ad..9d4ed20ca58293051a7f8f9a2ebf81d8d8bd5744 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java @@ -25,11 +25,15 @@ import static com.android.systemui.controls.dagger.ControlsComponent.Visibility. import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.util.Log; import android.view.View; import android.widget.ImageView; +import androidx.annotation.Nullable; + import com.android.internal.logging.UiEventLogger; +import com.android.settingslib.Utils; import com.android.systemui.CoreStartable; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent; @@ -43,6 +47,7 @@ import com.android.systemui.dagger.qualifiers.SystemUser; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.ViewController; import com.android.systemui.util.condition.ConditionalCoreStartable; @@ -195,34 +200,70 @@ public class DreamHomeControlsComplication implements Complication { private final ActivityStarter mActivityStarter; private final Context mContext; + private final ConfigurationController mConfigurationController; private final ControlsComponent mControlsComponent; private final UiEventLogger mUiEventLogger; + private final ConfigurationController.ConfigurationListener mConfigurationListener = + new ConfigurationController.ConfigurationListener() { + @Override + public void onUiModeChanged() { + reloadResources(); + } + }; + @Inject DreamHomeControlsChipViewController( @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view, ActivityStarter activityStarter, Context context, + ConfigurationController configurationController, ControlsComponent controlsComponent, UiEventLogger uiEventLogger) { super(view); mActivityStarter = activityStarter; mContext = context; + mConfigurationController = configurationController; mControlsComponent = controlsComponent; mUiEventLogger = uiEventLogger; } @Override protected void onViewAttached() { - mView.setImageResource(mControlsComponent.getTileImageId()); - mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId())); + reloadResources(); mView.setOnClickListener(this::onClickHomeControls); + mConfigurationController.addCallback(mConfigurationListener); } @Override - protected void onViewDetached() {} + protected void onViewDetached() { + mConfigurationController.removeCallback(mConfigurationListener); + } + + private void reloadResources() { + final String title = getControlsTitle(); + if (title != null) { + mView.setContentDescription(title); + } + mView.setImageResource(mControlsComponent.getTileImageId()); + mView.setImageTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); + final Drawable background = mView.getBackground(); + if (background != null) { + background.setTintList( + Utils.getColorAttr(mContext, com.android.internal.R.attr.colorSurface)); + } + } + + @Nullable + private String getControlsTitle() { + try { + return mContext.getString(mControlsComponent.getTileTitleId()); + } catch (Resources.NotFoundException e) { + return null; + } + } private void onClickHomeControls(View v) { if (DEBUG) Log.d(TAG, "home controls complication tapped"); diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java index 08d0595eba23ff12263773de0f7b744df597886e..b6dcfcbfad56e1e6d2dfa675e73b698a23f1e9b9 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java @@ -24,9 +24,8 @@ import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.widget.ImageView; -import com.android.settingslib.Utils; -import com.android.systemui.res.R; import com.android.systemui.complication.DreamHomeControlsComplication; +import com.android.systemui.res.R; import com.android.systemui.shared.shadow.DoubleShadowIconDrawable; import com.android.systemui.shared.shadow.DoubleShadowTextHelper; @@ -98,7 +97,7 @@ public interface DreamHomeControlsComplicationComponent { @DreamHomeControlsComplicationScope @Named(DREAM_HOME_CONTROLS_BACKGROUND_DRAWABLE) static Drawable providesHomeControlsBackground(Context context, Resources resources) { - final Drawable background = new DoubleShadowIconDrawable(createShadowInfo( + return new DoubleShadowIconDrawable(createShadowInfo( resources, R.dimen.dream_overlay_bottom_affordance_key_text_shadow_radius, R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dx, @@ -117,11 +116,6 @@ public interface DreamHomeControlsComplicationComponent { R.dimen.dream_overlay_bottom_affordance_width), resources.getDimensionPixelSize(R.dimen.dream_overlay_bottom_affordance_inset) ); - - background.setTintList( - Utils.getColorAttr(context, com.android.internal.R.attr.colorSurface)); - - return background; } private static DoubleShadowTextHelper.ShadowInfo createShadowInfo(Resources resources, diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt index ae67e60b75f7d746ba0a4d6e7ac3d72724aaf83d..4d89a826b60a71b4cede564c88f86d7381be9107 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt @@ -78,6 +78,22 @@ object RefactorFlagUtils { inline fun assertInLegacyMode(isEnabled: Boolean, flagName: Any) = check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." } + /** + * Called to ensure the new code is only run when the flag is enabled. This will throw an + * exception if the flag is disabled to ensure that the refactor author catches issues in + * testing. + * + * Example usage: + * ``` + * public void setSomeNewController(SomeController someController) { + * SomeRefactor.assertInNewMode(); + * mSomeController = someController; + * } + * ```` + */ + inline fun assertInNewMode(isEnabled: Boolean, flagName: Any) = + check(isEnabled) { "New code path not supported when $flagName is disabled." } + /** * This will [Log.wtf] with the given message, assuming [ASSERT_TAG] is loggable at that level. * This means an engineer can prevent this from crashing by running the command: diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 33dd3d9bea1601a30fd188c134bc78cadeff5d86..edf964815ca1e28a99914fc9cae43705cfe2ba7b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -35,7 +35,7 @@ import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionOldType; import static android.view.WindowManager.TransitionType; -import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER; +import static com.android.systemui.Flags.refactorGetCurrentUser; import android.annotation.NonNull; import android.app.ActivityManager; @@ -609,7 +609,7 @@ public class KeyguardService extends Service { public void setCurrentUser(int userId) { trace("Deprecated/NOT USED: setCurrentUser userId=" + userId); checkPermission(); - if (!mFlags.isEnabled(REFACTOR_GETCURRENTUSER)) { + if (!refactorGetCurrentUser()) { mKeyguardViewMediator.setCurrentUser(userId); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 57f3b7071d1d116bd34567342a1979903e0805f3..d7a1906f68e4f3147277cd3ba99a7ac02c6a240d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -38,7 +38,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE; import static com.android.systemui.DejankUtils.whitelistIpcs; -import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER; +import static com.android.systemui.Flags.refactorGetCurrentUser; import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; @@ -617,7 +617,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void onUserSwitching(int userId) { if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId)); synchronized (KeyguardViewMediator.this) { - if (mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) { + if (refactorGetCurrentUser()) { notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); } resetKeyguardDonePendingLocked(); @@ -1502,7 +1502,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - if (!mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) { + if (!refactorGetCurrentUser()) { KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId()); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt index 96386f3dc59693e59f9d725e207e40409ed5afbd..9a13558d3327e4f1c4b2af2b04e307fa4c5226a7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt @@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn /** Encapsulates state about device entry fingerprint auth mechanism. */ @@ -61,6 +62,9 @@ interface DeviceEntryFingerprintAuthRepository { /** Provide the current status of fingerprint authentication. */ val authenticationStatus: Flow<FingerprintAuthenticationStatus> + + /** Indicates whether to update the side fingerprint sensor indicator visibility. */ + val shouldUpdateIndicatorVisibility: Flow<Boolean> } /** @@ -256,6 +260,37 @@ constructor( awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } + override val shouldUpdateIndicatorVisibility: Flow<Boolean> = + conflatedCallbackFlow { + val sendShouldUpdateIndicatorVisibility = + { shouldUpdateIndicatorVisibility: Boolean -> + trySendWithFailureLogging( + shouldUpdateIndicatorVisibility, + TAG, + "Error sending shouldUpdateIndicatorVisibility " + + "$shouldUpdateIndicatorVisibility" + ) + } + + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onBiometricRunningStateChanged( + running: Boolean, + biometricSourceType: BiometricSourceType? + ) { + sendShouldUpdateIndicatorVisibility(true) + } + override fun onStrongAuthStateChanged(userId: Int) { + sendShouldUpdateIndicatorVisibility(true) + } + } + sendShouldUpdateIndicatorVisibility(false) + keyguardUpdateMonitor.registerCallback(callback) + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } + } + .flowOn(mainDispatcher) + .shareIn(scope, started = SharingStarted.WhileSubscribed(), replay = 1) + companion object { const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt new file mode 100644 index 0000000000000000000000000000000000000000..de15fd6a958fdfa7b0a099c174cab3d32f13896b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt @@ -0,0 +1,92 @@ +/* + * 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 android.content.Context +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** + * Encapsulates business logic for device entry events that impact the side fingerprint sensor + * overlay. + */ +@SysUISingleton +class DeviceEntrySideFpsOverlayInteractor +@Inject +constructor( + @Application private val context: Context, + deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, + alternateBouncerInteractor: AlternateBouncerInteractor, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, +) { + + init { + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG) + } + + private val showIndicatorForPrimaryBouncer: Flow<Boolean> = + merge( + primaryBouncerInteractor.isShowing, + primaryBouncerInteractor.startingToHide, + primaryBouncerInteractor.startingDisappearAnimation.filterNotNull(), + deviceEntryFingerprintAuthRepository.shouldUpdateIndicatorVisibility.filter { it } + ) + .map { shouldShowIndicatorForPrimaryBouncer() } + + private val showIndicatorForAlternateBouncer: Flow<Boolean> = + alternateBouncerInteractor.isVisible + + /** + * Indicates whether the primary or alternate bouncers request showing the side fingerprint + * sensor indicator. + */ + val showIndicatorForDeviceEntry: Flow<Boolean> = + combine(showIndicatorForPrimaryBouncer, showIndicatorForAlternateBouncer) { + showForPrimaryBouncer, + showForAlternateBouncer -> + showForPrimaryBouncer || showForAlternateBouncer + } + + private fun shouldShowIndicatorForPrimaryBouncer(): Boolean { + val sfpsEnabled: Boolean = + context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer) + val sfpsDetectionRunning = keyguardUpdateMonitor.isFingerprintDetectionRunning + val isUnlockingWithFpAllowed = keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed + return primaryBouncerInteractor.isBouncerShowing() && + sfpsEnabled && + sfpsDetectionRunning && + isUnlockingWithFpAllowed && + !primaryBouncerInteractor.isAnimatingAway() + } + + companion object { + private const val TAG = "DeviceEntrySideFpsOverlayInteractor" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt index 560e4adf956a96ba3ebaf1ebac30bc6e424e3ec4..9a6dca3c495879d042411f0de8739aae4ae70bc4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt @@ -21,6 +21,7 @@ import android.graphics.Point import com.android.systemui.CoreStartable import com.android.systemui.Flags import com.android.systemui.biometrics.SideFpsController +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.ui.view.SideFpsProgressBar @@ -46,6 +47,7 @@ constructor( private val viewModel: SideFpsProgressBarViewModel, private val view: SideFpsProgressBar, @Application private val applicationScope: CoroutineScope, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR private val sfpsController: dagger.Lazy<SideFpsController>, private val logger: SideFpsLogger, private val commandRegistry: CommandRegistry, @@ -109,12 +111,14 @@ constructor( view.updateView(visible, location, length, thickness, rotation) // We have to hide the SFPS indicator as the progress bar will // be shown at the same location - if (visible) { - logger.hidingSfpsIndicator() - sfpsController.get().hideIndicator() - } else if (fpDetectRunning) { - logger.showingSfpsIndicator() - sfpsController.get().showIndicator() + if (!SideFpsControllerRefactor.isEnabled) { + if (visible) { + logger.hidingSfpsIndicator() + sfpsController.get().hideIndicator() + } else if (fpDetectRunning) { + logger.showingSfpsIndicator() + sfpsController.get().showIndicator() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt index 2d0712c7f9cc2ffce22e6c94753f72a59a3f90ac..1dbf1f14b569e00ff2d31a00e35ce05fea81fc5a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.animation.ValueAnimator import android.content.Context import android.graphics.Point +import androidx.annotation.VisibleForTesting import androidx.core.animation.doOnEnd import com.android.systemui.Flags import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor @@ -213,4 +214,9 @@ constructor( } } } + + @VisibleForTesting + fun setVisible(isVisible: Boolean) { + _visible.value = isVisible + } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 9846f4b6c9800d9207ae73474b2e090e98882691..e660b97b5c6b9a441c74ea0deae8d1a3c6c611bd 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -69,7 +69,6 @@ import androidx.annotation.DimenRes; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.policy.GestureNavigationSettingsObserver; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; @@ -81,6 +80,7 @@ import com.android.systemui.plugins.NavigationEdgeBackPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputChannelCompat; @@ -1113,6 +1113,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack // Capture inputs mInputMonitor.pilferPointers(); if (mBackAnimation != null) { + mBackAnimation.onPilferPointers(); // Notify FalsingManager that an intentional gesture has occurred. mFalsingManager.isFalseTouch(BACK_GESTURE); } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt index 6d55d0549d2eb4ed75dbc5cf42254e7b0c723503..de490a58a49826023900158542bb1ed069bec6ab 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt @@ -27,7 +27,6 @@ import android.os.UserManager import android.util.Log import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -43,6 +42,7 @@ import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEnabledKey import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE import com.android.systemui.notetask.NoteTaskInfoResolver +import com.android.systemui.res.R import com.android.systemui.stylus.StylusManager import dagger.Lazy import java.util.concurrent.Executor @@ -100,7 +100,8 @@ constructor( isEnabled && isUserUnlocked && isDefaultNotesAppSet && - (isConfigSelected || isStylusEverUsed) + isConfigSelected && + isStylusEverUsed ) { val contentDescription = ContentDescription.Resource(pickerNameResourceId) val icon = Icon.Resource(pickerIconResourceId, contentDescription) diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt index 935d07229ae5f3c336d2e77587104b93aab9b717..42bee3c8f877b81d53529136b18a784dcbc144b6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt @@ -22,8 +22,8 @@ class QSPipelineFlagsRepository @Inject constructor() { AconfigFlags.FLAG_QS_NEW_PIPELINE ) - fun assertNewTilesInLegacyMode() = - RefactorFlagUtils.assertInLegacyMode( + fun assertNewTiles() = + RefactorFlagUtils.assertInNewMode( AconfigFlags.qsNewTiles(), AconfigFlags.FLAG_QS_NEW_TILES ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 38bbce45e143c26457ecab6dc06ffa51cfc54131..17e6375967fc8bfe62ea1410576aa0a01fd68b3a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -19,6 +19,7 @@ import android.util.Log; import androidx.annotation.Nullable; +import com.android.systemui.accessibility.qs.QSAccessibilityModule; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; @@ -41,7 +42,7 @@ import javax.inject.Provider; * com.android.systemui.qs.tiles.DreamTile#TILE_SPEC}) * * After, create or find an existing Module class to house the tile's binding method (e.g. {@link - * com.android.systemui.accessibility.AccessibilityModule}). If creating a new module, add your + * QSAccessibilityModule}). If creating a new module, add your * module to the SystemUI dagger graph by including it in an appropriate module. */ @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt index 52e49f9d2653b047edbc570d3f2d378ff78b1250..382cfe267aae42415c3a5efa91c38fe228fa2f1d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt @@ -45,7 +45,7 @@ constructor( ) : QSFactory { init { - QSPipelineFlagsRepository.assertNewTilesInLegacyMode() + QSPipelineFlagsRepository.assertNewTiles() for (viewModelTileSpec in tileMap.keys) { require(qsTileConfigProvider.hasConfig(viewModelTileSpec)) { "No config for $viewModelTileSpec" @@ -56,7 +56,7 @@ constructor( override fun createTile(tileSpec: String): QSTile? { val viewModel: QSTileViewModel = when (val spec = TileSpec.create(tileSpec)) { - is TileSpec.CustomTileSpec -> createCustomTileViewModel(spec) + is TileSpec.CustomTileSpec -> null is TileSpec.PlatformTileSpec -> tileMap[tileSpec]?.get() is TileSpec.Invalid -> null } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt new file mode 100644 index 0000000000000000000000000000000000000000..1efbfd70fa9803a2709019b5ed6325e904cb75bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.colorcorrection.domain + +import android.content.res.Resources +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [ColorCorrectionTileModel] to [QSTileState]. */ +class ColorCorrectionTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, +) : QSTileDataToStateMapper<ColorCorrectionTileModel> { + + override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction) + + if (data.isEnabled) { + activationState = QSTileState.ActivationState.ACTIVE + secondaryLabel = subtitleArray[2] + } else { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = subtitleArray[1] + } + contentDescription = label + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractor.kt new file mode 100644 index 0000000000000000000000000000000000000000..cd33d451ba813fe8d37a01543d577b078580b8a0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractor.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor + +import android.os.UserHandle +import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Observes color correction state changes providing the [ColorCorrectionTileModel]. */ +class ColorCorrectionTileDataInteractor +@Inject +constructor( + private val colorCorrectionRepository: ColorCorrectionRepository, +) : QSTileDataInteractor<ColorCorrectionTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<ColorCorrectionTileModel> { + return colorCorrectionRepository.isEnabled(user).map { ColorCorrectionTileModel(it) } + } + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt new file mode 100644 index 0000000000000000000000000000000000000000..d1838029db4edab29aa09102d5ef6457c90e7e12 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor + +import android.content.Intent +import android.provider.Settings +import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import javax.inject.Inject + +/** Handles color correction tile clicks. */ +class ColorCorrectionUserActionInteractor +@Inject +constructor( + private val colorCorrectionRepository: ColorCorrectionRepository, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, +) : QSTileUserActionInteractor<ColorCorrectionTileModel> { + + override suspend fun handleInput(input: QSTileInput<ColorCorrectionTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + colorCorrectionRepository.setIsEnabled( + !data.isEnabled, + user, + ) + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.view, + Intent(Settings.ACTION_COLOR_CORRECTION_SETTINGS) + ) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/model/ColorCorrectionTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/model/ColorCorrectionTileModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..66487e1bba608a97efd05a72b2e7ded41f9c4f11 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/model/ColorCorrectionTileModel.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.colorcorrection.domain.model + +/** + * Color correction tile model. + * + * @param isEnabled is true when the color correction is enabled; + */ +@JvmInline value class ColorCorrectionTileModel(val isEnabled: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 886fa70d715db9db498c7ba79f7ecf34439c4d0a..2b9ad50c1257dbacb1995204a6a7f6a306f2d80b 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -18,8 +18,8 @@ package com.android.systemui.theme; import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; -import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.Flags.themeOverlayControllerWakefulnessDeprecation; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET; @@ -364,15 +364,23 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - boolean newWorkProfile = Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction()); - boolean isManagedProfile = mUserManager.isManagedProfile( - intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); - if (newWorkProfile) { - if (!mDeviceProvisionedController.isCurrentUserSetup() && isManagedProfile) { + boolean newProfile = Intent.ACTION_PROFILE_ADDED.equals(intent.getAction()); + if (newProfile) { + UserHandle newUserHandle = intent.getParcelableExtra(Intent.EXTRA_USER, + android.os.UserHandle.class); + boolean isManagedProfile = + mUserManager.isManagedProfile(newUserHandle.getIdentifier()); + if (!mDeviceProvisionedController.isUserSetup(newUserHandle.getIdentifier()) + && isManagedProfile) { Log.i(TAG, "User setup not finished when " + intent.getAction() + " was received. Deferring... Managed profile? " + isManagedProfile); return; } + if (android.os.Flags.allowPrivateProfile() && isPrivateProfile(newUserHandle)) { + mDeferredThemeEvaluation = true; + Log.i(TAG, "Deferring theme for private profile till user setup is complete"); + return; + } if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); reevaluateSystemTheme(true /* forceReload */); } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) { @@ -432,7 +440,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { public void start() { if (DEBUG) Log.d(TAG, "Start"); final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); + filter.addAction(Intent.ACTION_PROFILE_ADDED); filter.addAction(Intent.ACTION_WALLPAPER_CHANGED); mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor, UserHandle.ALL); @@ -608,6 +616,15 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { return new FabricatedOverlay.Builder("com.android.systemui", name, "android").build(); } + @VisibleForTesting + protected boolean isPrivateProfile(UserHandle userHandle) { + Context usercontext = mContext.createContextAsUser(userHandle,0); + if (usercontext.getSystemService(UserManager.class).isPrivateProfile()) { + return true; + } + return false; + } + private void createOverlays(int color) { boolean nightMode = isNightMode(); mColorScheme = new ColorScheme(color, nightMode, mThemeStyle); @@ -784,7 +801,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { Set<UserHandle> managedProfiles = new HashSet<>(); for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) { - if (userInfo.isManagedProfile()) { + if (userInfo.isProfile()) { managedProfiles.add(userInfo.getUserHandle()); } } diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt index 3ed05aac3e10d7f9838e85cab7b4f534bd0f93b6..0fb4b43865aa1da63f05e89b35a2c8715b4de78e 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt @@ -2,9 +2,8 @@ package com.android.systemui.user.domain.interactor import android.annotation.UserIdInt import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.Flags.refactorGetCurrentUser import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER import com.android.systemui.user.data.repository.UserRepository import javax.inject.Inject import kotlinx.coroutines.flow.distinctUntilChanged @@ -12,12 +11,7 @@ import kotlinx.coroutines.flow.map /** Encapsulates business logic to interact the selected user */ @SysUISingleton -class SelectedUserInteractor -@Inject -constructor( - private val repository: UserRepository, - private val flags: FeatureFlagsClassic, -) { +class SelectedUserInteractor @Inject constructor(private val repository: UserRepository) { /** Flow providing the ID of the currently selected user. */ val selectedUser = repository.selectedUserInfo.map { it.id }.distinctUntilChanged() @@ -34,7 +28,7 @@ constructor( @UserIdInt @JvmOverloads fun getSelectedUserId(bypassFlag: Boolean = false): Int { - return if (bypassFlag || flags.isEnabled(REFACTOR_GETCURRENTUSER)) { + return if (bypassFlag || refactorGetCurrentUser()) { repository.getSelectedUserInfo().id } else { KeyguardUpdateMonitor.getCurrentUser() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index ea20d29556dc05ddcddbba0dd8d266168a2cc4b9..91219f02818cb1e558595c499aee269efdfef96c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -47,12 +47,14 @@ import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorI import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.events.ANIMATING_OUT +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -101,6 +103,12 @@ open class AuthContainerViewTest : SysuiTestCase() { lateinit var interactionJankMonitor: InteractionJankMonitor @Mock lateinit var vibrator: VibratorHelper + @Mock + lateinit var udfpsUtils: UdfpsUtils + @Mock + lateinit var authController: AuthController + @Mock + lateinit var selectedUserInteractor: SelectedUserInteractor private val testScope = TestScope(StandardTestDispatcher()) private val fakeExecutor = FakeExecutor(FakeSystemClock()) @@ -123,6 +131,7 @@ open class AuthContainerViewTest : SysuiTestCase() { private lateinit var displayRepository: FakeDisplayRepository private lateinit var displayStateInteractor: DisplayStateInteractor + private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) @@ -140,6 +149,12 @@ open class AuthContainerViewTest : SysuiTestCase() { displayStateRepository, displayRepository, ) + udfpsOverlayInteractor = + UdfpsOverlayInteractor( + authController, + selectedUserInteractor, + testScope.backgroundScope, + ) } @After @@ -532,6 +547,8 @@ open class AuthContainerViewTest : SysuiTestCase() { displayStateInteractor, promptSelectorInteractor, context, + udfpsOverlayInteractor, + udfpsUtils ), { credentialViewModel }, Handler(TestableLooper.get(this).looper), diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 8e54eb7334ddaa39365d000aaf438dbd82a8a29c..4c510ee0b8a6d773cf1df5a512d2339281019cfe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -27,6 +27,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.logging.KeyguardLogger +import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION import com.android.systemui.SysuiTestCase import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FeatureFlags @@ -40,6 +41,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.leak.RotationUtils import com.android.systemui.util.mockito.any +import javax.inject.Provider import org.junit.After import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -51,14 +53,14 @@ import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.MockitoSession import org.mockito.quality.Strictness -import javax.inject.Provider + @SmallTest @RunWith(AndroidTestingRunner::class) @@ -246,6 +248,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test @RunWithLooper(setAsMainLooper = true) fun testAnimatorRunWhenWakeAndUnlock_fingerprint() { + mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION) val fpsLocation = Point(5, 5) `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) controller.onViewAttached() @@ -266,6 +269,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test @RunWithLooper(setAsMainLooper = true) fun testAnimatorRunWhenWakeAndUnlock_faceUdfpsFingerDown() { + mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION) val faceLocation = Point(5, 5) `when`(authController.faceSensorLocation).thenReturn(faceLocation) controller.onViewAttached() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..27d93eb31922ce32e1d4a666cabbf8bd498aa27e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt @@ -0,0 +1,176 @@ +/* + * 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.biometrics.data.repository + +import android.hardware.biometrics.AuthenticationStateListener +import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR +import android.platform.test.annotations.RequiresFlagsEnabled +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR +import com.android.systemui.util.mockito.withArgCaptor +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.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class BiometricStatusRepositoryTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() + @Mock private lateinit var biometricManager: BiometricManager + + private lateinit var underTest: BiometricStatusRepository + + private val testScope = TestScope(StandardTestDispatcher()) + + @Before + fun setUp() { + underTest = BiometricStatusRepositoryImpl(testScope.backgroundScope, biometricManager) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenBiometricPromptAuthenticationStarted() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + listener.onAuthenticationStarted(REASON_AUTH_BP) + assertThat(fingerprintAuthenticationReason) + .isEqualTo(AuthenticationReason.BiometricPromptAuthentication) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenDeviceEntryAuthenticationStarted() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + listener.onAuthenticationStarted(REASON_AUTH_KEYGUARD) + assertThat(fingerprintAuthenticationReason) + .isEqualTo(AuthenticationReason.DeviceEntryAuthentication) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenOtherAuthenticationStarted() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + listener.onAuthenticationStarted(REASON_AUTH_OTHER) + assertThat(fingerprintAuthenticationReason) + .isEqualTo(AuthenticationReason.OtherAuthentication) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenSettingsAuthenticationStarted() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + listener.onAuthenticationStarted(REASON_AUTH_SETTINGS) + assertThat(fingerprintAuthenticationReason) + .isEqualTo(AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER)) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenEnrollmentAuthenticationStarted() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + listener.onAuthenticationStarted(REASON_ENROLL_FIND_SENSOR) + assertThat(fingerprintAuthenticationReason) + .isEqualTo( + AuthenticationReason.SettingsAuthentication( + SettingsOperations.ENROLL_FIND_SENSOR + ) + ) + + listener.onAuthenticationStarted(REASON_ENROLL_ENROLLING) + assertThat(fingerprintAuthenticationReason) + .isEqualTo( + AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING) + ) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenAuthenticationStopped() = + testScope.runTest { + val fingerprintAuthenticationReason by + collectLastValue(underTest.fingerprintAuthenticationReason) + runCurrent() + + val listener = biometricManager.captureListener() + + listener.onAuthenticationStarted(REASON_AUTH_BP) + listener.onAuthenticationStopped() + assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + } +} + +private fun BiometricManager.captureListener() = + withArgCaptor<AuthenticationStateListener> { + verify(this@captureListener).registerAuthenticationStateListener(capture()) + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..6978923879b45ef79bfaa97734e6301a27387a76 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt @@ -0,0 +1,173 @@ +/* + * 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.biometrics.domain.interactor + +import android.app.ActivityManager +import android.app.ActivityTaskManager +import android.content.ComponentName +import android.platform.test.annotations.RequiresFlagsEnabled +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR +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.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class BiometricStatusInteractorImplTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() + @Mock private lateinit var activityTaskManager: ActivityTaskManager + + private lateinit var biometricStatusRepository: FakeBiometricStatusRepository + private lateinit var underTest: BiometricStatusInteractorImpl + + private val testScope = TestScope(StandardTestDispatcher()) + + @Before + fun setup() { + biometricStatusRepository = FakeBiometricStatusRepository() + underTest = BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository) + } + + @Test + fun updatesSfpsAuthenticationReason_whenBiometricPromptAuthenticationStarted() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.BiometricPromptAuthentication + ) + assertThat(sfpsAuthenticationReason) + .isEqualTo(AuthenticationReason.BiometricPromptAuthentication) + } + + @Test + fun doesNotUpdateSfpsAuthenticationReason_whenDeviceEntryAuthenticationStarted() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.DeviceEntryAuthentication + ) + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + } + + @Test + fun updatesSfpsAuthenticationReason_whenOtherAuthenticationStarted() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.OtherAuthentication + ) + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.OtherAuthentication) + } + + @Test + fun doesNotUpdateSfpsAuthenticationReason_whenOtherSettingsAuthenticationStarted() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + `when`(activityTaskManager.getTasks(Mockito.anyInt())) + .thenReturn(listOf(fpSettingsTask())) + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER) + ) + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + } + + @Test + fun updatesSfpsAuthenticationReason_whenEnrollmentAuthenticationStarted() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_FIND_SENSOR) + ) + assertThat(sfpsAuthenticationReason) + .isEqualTo( + AuthenticationReason.SettingsAuthentication( + SettingsOperations.ENROLL_FIND_SENSOR + ) + ) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING) + ) + assertThat(sfpsAuthenticationReason) + .isEqualTo( + AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING) + ) + } + + @Test + fun updatesFingerprintAuthenticationReason_whenAuthenticationStopped() = + testScope.runTest { + val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason) + runCurrent() + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.BiometricPromptAuthentication + ) + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning) + } +} + +private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings") + +private fun settingsTask(cls: String) = + ActivityManager.RunningTaskInfo().apply { + topActivity = ComponentName.createRelative("com.android.settings", cls) + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt index 22e3e7fedda06b769a27429130bcb9a771bb5458..74c43131b9555e55d9ab94c80b2ae90417f6fd38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.biometrics.shared.model +import android.hardware.fingerprint.FingerprintSensorProperties import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.faceSensorPropertiesInternal @@ -34,6 +35,46 @@ class BiometricModalitiesTest : SysuiTestCase() { assertThat(BiometricModalities().isEmpty).isTrue() } + @Test + fun hasUdfps() { + with( + BiometricModalities( + fingerprintProperties = fingerprintSensorPropertiesInternal( + sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL + ).first(), + ) + ) { + assertThat(isEmpty).isFalse() + assertThat(hasUdfps).isTrue() + assertThat(hasSfps).isFalse() + assertThat(hasFace).isFalse() + assertThat(hasFaceOnly).isFalse() + assertThat(hasFingerprint).isTrue() + assertThat(hasFingerprintOnly).isTrue() + assertThat(hasFaceAndFingerprint).isFalse() + } + } + + @Test + fun hasSfps() { + with( + BiometricModalities( + fingerprintProperties = fingerprintSensorPropertiesInternal( + sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON + ).first(), + ) + ) { + assertThat(isEmpty).isFalse() + assertThat(hasUdfps).isFalse() + assertThat(hasSfps).isTrue() + assertThat(hasFace).isFalse() + assertThat(hasFaceOnly).isFalse() + assertThat(hasFingerprint).isTrue() + assertThat(hasFingerprintOnly).isTrue() + assertThat(hasFaceAndFingerprint).isFalse() + } + } + @Test fun fingerprintOnly() { with( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..b4ae00ddd608c75ba4d78ce4763c3e71f705047a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt @@ -0,0 +1,484 @@ +/* + * 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.biometrics.ui.binder + +import android.animation.Animator +import android.app.ActivityTaskManager +import android.graphics.Rect +import android.hardware.biometrics.SensorLocationInternal +import android.hardware.display.DisplayManager +import android.hardware.display.DisplayManagerGlobal +import android.os.Handler +import android.testing.TestableLooper +import android.view.Display +import android.view.DisplayInfo +import android.view.LayoutInflater +import android.view.View +import android.view.ViewPropertyAnimator +import android.view.WindowInsets +import android.view.WindowManager +import android.view.WindowMetrics +import androidx.test.filters.SmallTest +import com.airbnb.lottie.LottieAnimationView +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider +import com.android.systemui.biometrics.FpsUnlockTracker +import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl +import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel +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.display.data.repository.FakeDisplayRepository +import com.android.systemui.dump.logcatLogBuffer +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor +import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel +import com.android.systemui.log.SideFpsLogger +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.unfold.compat.ScreenSizeFoldProvider +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import java.util.Optional +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +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.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.any +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class SideFpsOverlayViewBinderTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() + @Mock private lateinit var activityTaskManager: ActivityTaskManager + @Mock private lateinit var displayManager: DisplayManager + @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor + @Mock + private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider + @Mock private lateinit var fpsUnlockTracker: FpsUnlockTracker + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var layoutInflater: LayoutInflater + @Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider + @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var sideFpsView: View + @Mock private lateinit var windowManager: WindowManager + + private val contextDisplayInfo = DisplayInfo() + + private val bouncerRepository = FakeKeyguardBouncerRepository() + private val biometricSettingsRepository = FakeBiometricSettingsRepository() + private val biometricStatusRepository = FakeBiometricStatusRepository() + private val deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() + private val displayRepository = FakeDisplayRepository() + private val displayStateRepository = FakeDisplayStateRepository() + private val fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + + private lateinit var underTest: SideFpsOverlayViewBinder + + private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + private lateinit var biometricStatusInteractor: BiometricStatusInteractor + private lateinit var deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor + private lateinit var displayStateInteractor: DisplayStateInteractorImpl + private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor + private lateinit var sfpsSensorInteractor: SideFpsSensorInteractor + + private lateinit var sideFpsProgressBarViewModel: SideFpsProgressBarViewModel + + private lateinit var viewModel: SideFpsOverlayViewModel + + private var displayWidth: Int = 0 + private var displayHeight: Int = 0 + private var boundsWidth: Int = 0 + private var boundsHeight: Int = 0 + + private lateinit var deviceConfig: DeviceConfig + private lateinit var sensorLocation: SensorLocationInternal + + private val testScope = TestScope(StandardTestDispatcher()) + private val fakeExecutor = FakeExecutor(FakeSystemClock()) + + enum class DeviceConfig { + X_ALIGNED, + Y_ALIGNED, + } + + @Before + fun setup() { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) + + allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread + + mContext = spy(mContext) + + val resources = mContext.resources + whenever(mContext.display) + .thenReturn( + Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources) + ) + + alternateBouncerInteractor = + AlternateBouncerInteractor( + mock(StatusBarStateController::class.java), + mock(KeyguardStateController::class.java), + bouncerRepository, + fingerprintPropertyRepository, + biometricSettingsRepository, + FakeSystemClock(), + keyguardUpdateMonitor, + testScope.backgroundScope, + ) + + biometricStatusInteractor = + BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository) + + displayStateInteractor = + DisplayStateInteractorImpl( + testScope.backgroundScope, + mContext, + fakeExecutor, + displayStateRepository, + displayRepository, + ) + displayStateInteractor.setScreenSizeFoldProvider(screenSizeFoldProvider) + + primaryBouncerInteractor = + 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), + mContext, + keyguardUpdateMonitor, + FakeTrustRepository(), + testScope.backgroundScope, + selectedUserInteractor, + faceAuthInteractor + ) + + deviceEntrySideFpsOverlayInteractor = + DeviceEntrySideFpsOverlayInteractor( + mContext, + deviceEntryFingerprintAuthRepository, + primaryBouncerInteractor, + alternateBouncerInteractor, + keyguardUpdateMonitor + ) + + whenever(fingerprintInteractiveToAuthProvider.enabledForCurrentUser) + .thenReturn(MutableStateFlow(false)) + + sfpsSensorInteractor = + SideFpsSensorInteractor( + mContext, + fingerprintPropertyRepository, + windowManager, + displayStateInteractor, + Optional.of(fingerprintInteractiveToAuthProvider), + SideFpsLogger(logcatLogBuffer("SfpsLogger")) + ) + + sideFpsProgressBarViewModel = + SideFpsProgressBarViewModel( + mContext, + deviceEntryFingerprintAuthRepository, + sfpsSensorInteractor, + displayStateInteractor, + testScope.backgroundScope, + ) + + viewModel = + SideFpsOverlayViewModel( + mContext, + biometricStatusInteractor, + deviceEntrySideFpsOverlayInteractor, + displayStateInteractor, + sfpsSensorInteractor, + sideFpsProgressBarViewModel + ) + + underTest = + SideFpsOverlayViewBinder( + testScope.backgroundScope, + mContext, + biometricStatusInteractor, + displayStateInteractor, + deviceEntrySideFpsOverlayInteractor, + fpsUnlockTracker, + layoutInflater, + sideFpsProgressBarViewModel, + sfpsSensorInteractor, + windowManager + ) + + context.addMockSystemService(DisplayManager::class.java, displayManager) + context.addMockSystemService(WindowManager::class.java, windowManager) + + `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView) + `when`(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation))) + .thenReturn(mock(LottieAnimationView::class.java)) + with(mock(ViewPropertyAnimator::class.java)) { + `when`(sideFpsView.animate()).thenReturn(this) + `when`(alpha(Mockito.anyFloat())).thenReturn(this) + `when`(setStartDelay(Mockito.anyLong())).thenReturn(this) + `when`(setDuration(Mockito.anyLong())).thenReturn(this) + `when`(setListener(any())).thenAnswer { + (it.arguments[0] as Animator.AnimatorListener).onAnimationEnd( + mock(Animator::class.java) + ) + this + } + } + } + + @Test + fun verifyIndicatorNotAdded_whenInRearDisplayMode() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = true + ) + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + sideFpsProgressBarViewModel.setVisible(false) + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + verify(windowManager, never()).addView(any(), any()) + } + } + + @Test + fun verifyIndicatorShowAndHide_onPrimaryBouncerShowAndHide() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + sideFpsProgressBarViewModel.setVisible(false) + // Show primary bouncer + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + verify(windowManager).addView(any(), any()) + + // Hide primary bouncer + updatePrimaryBouncer( + isShowing = false, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + verify(windowManager).removeView(any()) + } + } + + @Test + fun verifyIndicatorShowAndHide_onAlternateBouncerShowAndHide() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + sideFpsProgressBarViewModel.setVisible(false) + // Show alternate bouncer + bouncerRepository.setAlternateVisible(true) + runCurrent() + + verify(windowManager).addView(any(), any()) + + // Hide alternate bouncer + bouncerRepository.setAlternateVisible(false) + runCurrent() + + verify(windowManager).removeView(any()) + } + } + + @Test + fun verifyIndicatorShownAndHidden_onSystemServerAuthenticationStartedAndStopped() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + sideFpsProgressBarViewModel.setVisible(false) + updatePrimaryBouncer( + isShowing = false, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + // System server authentication started + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.BiometricPromptAuthentication + ) + runCurrent() + + verify(windowManager).addView(any(), any()) + + // System server authentication stopped + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + runCurrent() + + verify(windowManager).removeView(any()) + } + } + + private fun updatePrimaryBouncer( + isShowing: Boolean, + isAnimatingAway: Boolean, + fpsDetectionRunning: Boolean, + isUnlockingWithFpAllowed: Boolean, + ) { + bouncerRepository.setPrimaryShow(isShowing) + bouncerRepository.setPrimaryStartingToHide(false) + val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null + bouncerRepository.setPrimaryStartDisappearAnimation(primaryStartDisappearAnimation) + + whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) + .thenReturn(fpsDetectionRunning) + whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) + .thenReturn(isUnlockingWithFpAllowed) + mContext.orCreateTestableResources.addOverride( + R.bool.config_show_sidefps_hint_on_bouncer, + true + ) + } + + private suspend fun TestScope.setupTestConfiguration( + deviceConfig: DeviceConfig, + rotation: DisplayRotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode: Boolean, + ) { + this@SideFpsOverlayViewBinderTest.deviceConfig = deviceConfig + + when (deviceConfig) { + DeviceConfig.X_ALIGNED -> { + displayWidth = 3000 + displayHeight = 1500 + boundsWidth = 200 + boundsHeight = 100 + sensorLocation = SensorLocationInternal("", 2500, 0, boundsWidth / 2) + } + DeviceConfig.Y_ALIGNED -> { + displayWidth = 2500 + displayHeight = 2000 + boundsWidth = 100 + boundsHeight = 200 + sensorLocation = SensorLocationInternal("", displayWidth, 300, boundsHeight / 2) + } + } + + whenever(windowManager.maximumWindowMetrics) + .thenReturn( + WindowMetrics( + Rect(0, 0, displayWidth, displayHeight), + mock(WindowInsets::class.java), + ) + ) + + contextDisplayInfo.uniqueId = DISPLAY_ID + + fingerprintPropertyRepository.setProperties( + sensorId = 1, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.POWER_BUTTON, + sensorLocations = mapOf(DISPLAY_ID to sensorLocation) + ) + + displayStateRepository.setIsInRearDisplayMode(isInRearDisplayMode) + displayStateRepository.setCurrentRotation(rotation) + displayRepository.emitDisplayChangeEvent(0) + underTest.start() + runCurrent() + } + + companion object { + private const val DISPLAY_ID = "displayId" + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index d06cbbb5e433539999e46cb6fb374259ce38e095..7475235cfeae061b9a509bb263df2c4d2f89be28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics.ui.viewmodel import android.content.res.Configuration +import android.graphics.Point import android.hardware.biometrics.PromptInfo import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorProperties @@ -25,7 +26,10 @@ import android.view.HapticFeedbackConstants import android.view.MotionEvent import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils +import com.android.systemui.Flags.FLAG_BP_TALKBACK import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.AuthController +import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository @@ -33,6 +37,7 @@ import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.extractAuthenticatorTypes import com.android.systemui.biometrics.faceSensorPropertiesInternal import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal @@ -45,8 +50,10 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.res.R -import com.android.systemui.statusbar.VibratorHelper +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +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 @@ -77,7 +84,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @Mock private lateinit var lockPatternUtils: LockPatternUtils - @Mock private lateinit var vibrator: VibratorHelper + @Mock private lateinit var authController: AuthController + @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var udfpsUtils: UdfpsUtils private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val testScope = TestScope() @@ -87,6 +96,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa private lateinit var displayStateRepository: FakeDisplayStateRepository private lateinit var displayRepository: FakeDisplayRepository private lateinit var displayStateInteractor: DisplayStateInteractor + private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor private lateinit var selector: PromptSelectorInteractor private lateinit var viewModel: PromptViewModel @@ -116,11 +126,24 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa displayStateRepository, displayRepository, ) + udfpsOverlayInteractor = + UdfpsOverlayInteractor( + authController, + selectedUserInteractor, + testScope.backgroundScope + ) selector = PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) selector.resetPrompt() - viewModel = PromptViewModel(displayStateInteractor, selector, mContext) + viewModel = + PromptViewModel( + displayStateInteractor, + selector, + mContext, + udfpsOverlayInteractor, + udfpsUtils + ) iconViewModel = viewModel.iconViewModel } @@ -1153,6 +1176,29 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(size).isEqualTo(PromptSize.LARGE) } + @Test + fun hint_for_talkback_guidance() = runGenericTest { + mSetFlagsRule.enableFlags(FLAG_BP_TALKBACK) + val hint by collectLastValue(viewModel.accessibilityHint) + + // Touches should fall outside of sensor area + whenever(udfpsUtils.getTouchInNativeCoordinates(any(), any(), any())) + .thenReturn(Point(0, 0)) + whenever(udfpsUtils.onTouchOutsideOfSensorArea(any(), any(), any(), any(), any())) + .thenReturn("Direction") + + viewModel.onAnnounceAccessibilityHint( + obtainMotionEvent(MotionEvent.ACTION_HOVER_ENTER), + true + ) + + if (testCase.modalities.hasUdfps) { + assertThat(hint?.isNotBlank()).isTrue() + } else { + assertThat(hint.isNullOrBlank()).isTrue() + } + } + /** Asserts that the selected buttons are visible now. */ private suspend fun TestScope.assertButtonsVisible( tryAgain: Boolean = false, @@ -1220,14 +1266,19 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa authenticatedModality = BiometricModality.Face, ), TestCase( - fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), + fingerprint = + fingerprintSensorPropertiesInternal( + strong = true, + sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON + ) + .first(), authenticatedModality = BiometricModality.Fingerprint, ), TestCase( fingerprint = fingerprintSensorPropertiesInternal( strong = true, - sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON + sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL ) .first(), authenticatedModality = BiometricModality.Fingerprint, @@ -1264,19 +1315,29 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa TestCase( face = faceSensorPropertiesInternal(strong = true).first(), fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), - authenticatedModality = BiometricModality.Fingerprint, + authenticatedModality = BiometricModality.Face, + confirmationRequested = true, ), TestCase( face = faceSensorPropertiesInternal(strong = true).first(), - fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), - authenticatedModality = BiometricModality.Face, + fingerprint = + fingerprintSensorPropertiesInternal( + strong = true, + sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON + ) + .first(), + authenticatedModality = BiometricModality.Fingerprint, confirmationRequested = true, ), TestCase( face = faceSensorPropertiesInternal(strong = true).first(), - fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), + fingerprint = + fingerprintSensorPropertiesInternal( + strong = true, + sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL + ) + .first(), authenticatedModality = BiometricModality.Fingerprint, - confirmationRequested = true, ), ) } @@ -1309,6 +1370,9 @@ internal data class TestCase( else -> false } + val modalities: BiometricModalities + get() = BiometricModalities(fingerprint, face) + val authenticatedByFingerprint: Boolean get() = authenticatedModality == BiometricModality.Fingerprint diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2267bdcafad840051e8065c01f894a5e1fc6270e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt @@ -0,0 +1,576 @@ +/* + * 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.biometrics.ui.viewmodel + +import android.app.ActivityTaskManager +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import android.graphics.Color +import android.graphics.Rect +import android.hardware.biometrics.SensorLocationInternal +import android.hardware.display.DisplayManagerGlobal +import android.os.Handler +import android.view.Display +import android.view.DisplayInfo +import android.view.WindowInsets +import android.view.WindowManager +import android.view.WindowMetrics +import androidx.test.filters.SmallTest +import com.airbnb.lottie.model.KeyPath +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.settingslib.Utils +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider +import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl +import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.LottieCallback +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.coroutines.collectLastValue +import com.android.systemui.display.data.repository.FakeDisplayRepository +import com.android.systemui.dump.logcatLogBuffer +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor +import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel +import com.android.systemui.log.SideFpsLogger +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.unfold.compat.ScreenSizeFoldProvider +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import java.util.Optional +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +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.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class SideFpsOverlayViewModelTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var activityTaskManager: ActivityTaskManager + @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor + @Mock + private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider + @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var windowManager: WindowManager + + private val contextDisplayInfo = DisplayInfo() + + private val bouncerRepository = FakeKeyguardBouncerRepository() + private val biometricSettingsRepository = FakeBiometricSettingsRepository() + private val biometricStatusRepository = FakeBiometricStatusRepository() + private val deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() + private val displayRepository = FakeDisplayRepository() + private val displayStateRepository = FakeDisplayStateRepository() + private val fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + + private val indicatorColor = + Utils.getColorAttrDefaultColor( + context, + com.android.internal.R.attr.materialColorPrimaryFixed + ) + private val outerRimColor = + Utils.getColorAttrDefaultColor( + context, + com.android.internal.R.attr.materialColorPrimaryFixedDim + ) + private val chevronFill = + Utils.getColorAttrDefaultColor( + context, + com.android.internal.R.attr.materialColorOnPrimaryFixed + ) + private val color_blue400 = + context.getColor(com.android.settingslib.color.R.color.settingslib_color_blue400) + + private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + private lateinit var biometricStatusInteractor: BiometricStatusInteractor + private lateinit var deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor + private lateinit var displayStateInteractor: DisplayStateInteractorImpl + private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor + private lateinit var sfpsSensorInteractor: SideFpsSensorInteractor + + private lateinit var sideFpsProgressBarViewModel: SideFpsProgressBarViewModel + + private lateinit var underTest: SideFpsOverlayViewModel + + private var displayWidth: Int = 0 + private var displayHeight: Int = 0 + private var boundsWidth: Int = 0 + private var boundsHeight: Int = 0 + + private lateinit var deviceConfig: DeviceConfig + private lateinit var sensorLocation: SensorLocationInternal + + private val testScope = TestScope(StandardTestDispatcher()) + private val fakeExecutor = FakeExecutor(FakeSystemClock()) + + enum class DeviceConfig { + X_ALIGNED, + Y_ALIGNED, + } + + @Before + fun setup() { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) + + mContext = spy(mContext) + + val resources = mContext.resources + whenever(mContext.display) + .thenReturn( + Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources) + ) + + alternateBouncerInteractor = + AlternateBouncerInteractor( + mock(StatusBarStateController::class.java), + mock(KeyguardStateController::class.java), + bouncerRepository, + fingerprintPropertyRepository, + biometricSettingsRepository, + FakeSystemClock(), + keyguardUpdateMonitor, + testScope.backgroundScope, + ) + + biometricStatusInteractor = + BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository) + + displayStateInteractor = + DisplayStateInteractorImpl( + testScope.backgroundScope, + mContext, + fakeExecutor, + displayStateRepository, + displayRepository, + ) + displayStateInteractor.setScreenSizeFoldProvider(screenSizeFoldProvider) + + primaryBouncerInteractor = + 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), + mContext, + keyguardUpdateMonitor, + FakeTrustRepository(), + testScope.backgroundScope, + selectedUserInteractor, + faceAuthInteractor + ) + + deviceEntrySideFpsOverlayInteractor = + DeviceEntrySideFpsOverlayInteractor( + mContext, + deviceEntryFingerprintAuthRepository, + primaryBouncerInteractor, + alternateBouncerInteractor, + keyguardUpdateMonitor + ) + + whenever(fingerprintInteractiveToAuthProvider.enabledForCurrentUser) + .thenReturn(MutableStateFlow(false)) + + sfpsSensorInteractor = + SideFpsSensorInteractor( + mContext, + fingerprintPropertyRepository, + windowManager, + displayStateInteractor, + Optional.of(fingerprintInteractiveToAuthProvider), + SideFpsLogger(logcatLogBuffer("SfpsLogger")) + ) + + sideFpsProgressBarViewModel = + SideFpsProgressBarViewModel( + mContext, + deviceEntryFingerprintAuthRepository, + sfpsSensorInteractor, + displayStateInteractor, + testScope.backgroundScope, + ) + + underTest = + SideFpsOverlayViewModel( + mContext, + biometricStatusInteractor, + deviceEntrySideFpsOverlayInteractor, + displayStateInteractor, + sfpsSensorInteractor, + sideFpsProgressBarViewModel, + ) + } + + @Test + fun updatesOverlayViewProperties_onDisplayRotationChange_xAlignedSensor() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + + val overlayViewProperties by collectLastValue(underTest.overlayViewProperties) + + runCurrent() + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f) + } + } + + @Test + fun updatesOverlayViewProperties_onDisplayRotationChange_yAlignedSensor() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.Y_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + + val overlayViewProperties by collectLastValue(underTest.overlayViewProperties) + + runCurrent() + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0) + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) + + assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape) + assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f) + } + } + + @Test + fun updatesOverlayViewParams_onDisplayRotationChange_xAlignedSensor() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.X_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + + val overlayViewParams by collectLastValue(underTest.overlayViewParams) + + underTest.setLottieBounds(Rect(0, 0, boundsWidth, boundsHeight)) + runCurrent() + + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(sensorLocation.sensorLocationX) + assertThat(overlayViewParams!!.y).isEqualTo(0) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(0) + assertThat(overlayViewParams!!.y) + .isEqualTo( + displayHeight - sensorLocation.sensorLocationX - sensorLocation.sensorRadius * 2 + ) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x) + .isEqualTo( + displayWidth - sensorLocation.sensorLocationX - sensorLocation.sensorRadius * 2 + ) + assertThat(overlayViewParams!!.y).isEqualTo(displayHeight - boundsHeight) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(displayWidth - boundsWidth) + assertThat(overlayViewParams!!.y).isEqualTo(sensorLocation.sensorLocationX) + } + } + + @Test + fun updatesOverlayViewParams_onDisplayRotationChange_yAlignedSensor() { + testScope.runTest { + setupTestConfiguration( + DeviceConfig.Y_ALIGNED, + rotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode = false + ) + + val overlayViewParams by collectLastValue(underTest.overlayViewParams) + + underTest.setLottieBounds(Rect(0, 0, boundsWidth, boundsHeight)) + runCurrent() + + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(displayWidth - boundsWidth) + assertThat(overlayViewParams!!.y).isEqualTo(sensorLocation.sensorLocationY) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(sensorLocation.sensorLocationY) + assertThat(overlayViewParams!!.y).isEqualTo(0) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x).isEqualTo(0) + assertThat(overlayViewParams!!.y) + .isEqualTo( + displayHeight - sensorLocation.sensorLocationY - sensorLocation.sensorRadius * 2 + ) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) + assertThat(overlayViewParams).isNotNull() + assertThat(overlayViewParams!!.x) + .isEqualTo( + displayWidth - sensorLocation.sensorLocationY - sensorLocation.sensorRadius * 2 + ) + assertThat(overlayViewParams!!.y).isEqualTo(displayHeight - boundsHeight) + } + } + + @Test + fun updatesLottieCallbacks_onShowIndicatorForDeviceEntry() { + testScope.runTest { + val lottieCallbacks by collectLastValue(underTest.lottieCallbacks) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.NotRunning + ) + sideFpsProgressBarViewModel.setVisible(false) + + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue600", "**"), indicatorColor)) + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue400", "**"), outerRimColor)) + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".black", "**"), chevronFill)) + } + } + + @Test + fun updatesLottieCallbacks_onShowIndicatorForSystemServer_inDarkMode() { + testScope.runTest { + val lottieCallbacks by collectLastValue(underTest.lottieCallbacks) + setDarkMode(true) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.BiometricPromptAuthentication + ) + sideFpsProgressBarViewModel.setVisible(false) + + updatePrimaryBouncer( + isShowing = false, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue600", "**"), color_blue400)) + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue400", "**"), color_blue400)) + } + } + + @Test + fun updatesLottieCallbacks_onShowIndicatorForSystemServer_inLightMode() { + testScope.runTest { + val lottieCallbacks by collectLastValue(underTest.lottieCallbacks) + setDarkMode(false) + + biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.BiometricPromptAuthentication + ) + sideFpsProgressBarViewModel.setVisible(false) + + updatePrimaryBouncer( + isShowing = false, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + runCurrent() + + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".black", "**"), Color.WHITE)) + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue600", "**"), color_blue400)) + assertThat(lottieCallbacks) + .contains(LottieCallback(KeyPath(".blue400", "**"), color_blue400)) + } + } + + private fun setDarkMode(inDarkMode: Boolean) { + val uiMode = + if (inDarkMode) { + UI_MODE_NIGHT_YES + } else { + UI_MODE_NIGHT_NO + } + + mContext.resources.configuration.uiMode = uiMode + } + + private fun updatePrimaryBouncer( + isShowing: Boolean, + isAnimatingAway: Boolean, + fpsDetectionRunning: Boolean, + isUnlockingWithFpAllowed: Boolean, + ) { + bouncerRepository.setPrimaryShow(isShowing) + bouncerRepository.setPrimaryStartingToHide(false) + val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null + bouncerRepository.setPrimaryStartDisappearAnimation(primaryStartDisappearAnimation) + + whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) + .thenReturn(fpsDetectionRunning) + whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) + .thenReturn(isUnlockingWithFpAllowed) + mContext.orCreateTestableResources.addOverride( + R.bool.config_show_sidefps_hint_on_bouncer, + true + ) + } + + private suspend fun TestScope.setupTestConfiguration( + deviceConfig: DeviceConfig, + rotation: DisplayRotation = DisplayRotation.ROTATION_0, + isInRearDisplayMode: Boolean, + ) { + this@SideFpsOverlayViewModelTest.deviceConfig = deviceConfig + + when (deviceConfig) { + DeviceConfig.X_ALIGNED -> { + displayWidth = 3000 + displayHeight = 1500 + boundsWidth = 200 + boundsHeight = 100 + sensorLocation = SensorLocationInternal("", 2500, 0, boundsWidth / 2) + } + DeviceConfig.Y_ALIGNED -> { + displayWidth = 2500 + displayHeight = 2000 + boundsWidth = 100 + boundsHeight = 200 + sensorLocation = SensorLocationInternal("", displayWidth, 300, boundsHeight / 2) + } + } + + whenever(windowManager.maximumWindowMetrics) + .thenReturn( + WindowMetrics( + Rect(0, 0, displayWidth, displayHeight), + mock(WindowInsets::class.java), + ) + ) + + contextDisplayInfo.uniqueId = DISPLAY_ID + + fingerprintPropertyRepository.setProperties( + sensorId = 1, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.POWER_BUTTON, + sensorLocations = mapOf(DISPLAY_ID to sensorLocation) + ) + + displayStateRepository.setIsInRearDisplayMode(isInRearDisplayMode) + + displayStateRepository.setCurrentRotation(rotation) + + displayRepository.emitDisplayChangeEvent(0) + runCurrent() + } + + companion object { + private const val DISPLAY_ID = "displayId" + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt index 37a093e4c23f472319862c3d8a238f4c7cc84a2b..dacf23a5a567b625b08c3020e8b61b75d91f513b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -37,6 +37,7 @@ import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any @@ -342,6 +343,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { assertThat(underTest.willDismissWithAction()).isFalse() } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility() { updateSideFpsVisibilityParameters( @@ -355,6 +357,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { verify(repository).setSideFpsShowing(true) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility_notVisible() { updateSideFpsVisibilityParameters( @@ -368,6 +371,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { verify(repository).setSideFpsShowing(false) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility_sfpsNotEnabled() { updateSideFpsVisibilityParameters( @@ -381,6 +385,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { verify(repository).setSideFpsShowing(false) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility_fpsDetectionNotRunning() { updateSideFpsVisibilityParameters( @@ -394,6 +399,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { verify(repository).setSideFpsShowing(false) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility_UnlockingWithFpNotAllowed() { updateSideFpsVisibilityParameters( @@ -407,6 +413,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { verify(repository).setSideFpsShowing(false) } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Test fun testSideFpsVisibility_AnimatingAway() { updateSideFpsVisibilityParameters( @@ -492,6 +499,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { isUnlockingWithFpAllowed: Boolean, isAnimatingAway: Boolean ) { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) whenever(repository.primaryBouncerShow.value).thenReturn(isVisible) resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled) whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java index 220718027eee8f15142fad2af3f0bfb2a6976860..07c980bb66566c7e6735de11ab420967676a16f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java @@ -21,14 +21,13 @@ import static com.android.systemui.controls.dagger.ControlsComponent.Visibility. import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; -import android.content.Context; import android.content.res.Resources; import android.testing.AndroidTestingRunner; import android.view.View; @@ -48,6 +47,7 @@ import com.android.systemui.controls.management.ControlsListingController; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.statusbar.policy.ConfigurationController; import org.junit.Before; import org.junit.Test; @@ -70,9 +70,6 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Mock private DreamOverlayStateController mDreamOverlayStateController; - @Mock - private Context mContext; - @Mock private Resources mResources; @@ -100,6 +97,9 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Mock private UiEventLogger mUiEventLogger; + @Mock + private ConfigurationController mConfigurationController; + @Captor private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor; @@ -109,7 +109,8 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); - when(mContext.getString(anyInt())).thenReturn(""); + mContext.ensureTestableResources(); + when(mControlsComponent.getControlsController()).thenReturn( Optional.of(mControlsController)); when(mControlsComponent.getControlsListingController()).thenReturn( @@ -225,6 +226,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { mHomeControlsView, mActivityStarter, mContext, + mConfigurationController, mControlsComponent, mUiEventLogger); viewController.onViewAttached(); @@ -237,6 +239,24 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { verify(mUiEventLogger).log(DreamOverlayUiEvent.DREAM_HOME_CONTROLS_TAPPED); } + @Test + public void testUnregistersConfigurationCallback() { + final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController = + new DreamHomeControlsComplication.DreamHomeControlsChipViewController( + mHomeControlsView, + mActivityStarter, + mContext, + mConfigurationController, + mControlsComponent, + mUiEventLogger); + viewController.onViewAttached(); + verify(mConfigurationController).addCallback(any()); + verify(mConfigurationController, never()).removeCallback(any()); + + viewController.onViewDetached(); + verify(mConfigurationController).removeCallback(any()); + } + private void setHaveFavorites(boolean value) { final List<StructureInfo> favorites = mock(List.class); when(favorites.isEmpty()).thenReturn(!value); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 4ef18cf1a15fc13f28d1c9fe45d2af230f2c5620..b38c9ecb6287720865b389448ab7973ab425ead1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -24,6 +24,7 @@ import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER; import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION; import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT; import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE; @@ -266,7 +267,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mSceneContainerFlags); mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false); - mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true); + mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER); DejankUtils.setImmediate(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..70d3f81de35f03f8a9c44b53499c472c56309837 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt @@ -0,0 +1,238 @@ +/* + * 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 android.os.Handler +import android.platform.test.annotations.RequiresFlagsEnabled +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +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.coroutines.collectLastValue +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R +import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +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.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@OptIn(ExperimentalCoroutinesApi::class) +@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) +@SmallTest +@RunWith(JUnit4::class) +class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor + + private val bouncerRepository = FakeKeyguardBouncerRepository() + private val biometricSettingsRepository = FakeBiometricSettingsRepository() + + private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor + private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + + private lateinit var underTest: DeviceEntrySideFpsOverlayInteractor + + private val testScope = TestScope(StandardTestDispatcher()) + + @Before + fun setup() { + primaryBouncerInteractor = + 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), + mContext, + keyguardUpdateMonitor, + FakeTrustRepository(), + testScope.backgroundScope, + mSelectedUserInteractor, + faceAuthInteractor + ) + alternateBouncerInteractor = + AlternateBouncerInteractor( + mock(StatusBarStateController::class.java), + mock(KeyguardStateController::class.java), + bouncerRepository, + FakeFingerprintPropertyRepository(), + biometricSettingsRepository, + FakeSystemClock(), + keyguardUpdateMonitor, + testScope.backgroundScope, + ) + underTest = + DeviceEntrySideFpsOverlayInteractor( + mContext, + FakeDeviceEntryFingerprintAuthRepository(), + primaryBouncerInteractor, + alternateBouncerInteractor, + keyguardUpdateMonitor + ) + } + + @Test + fun updatesShowIndicatorForDeviceEntry_onPrimaryBouncerShowing() = + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + assertThat(showIndicatorForDeviceEntry).isEqualTo(true) + } + + @Test + fun updatesShowIndicatorForDeviceEntry_onPrimaryBouncerHidden() = + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + updatePrimaryBouncer( + isShowing = false, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + assertThat(showIndicatorForDeviceEntry).isEqualTo(false) + } + + @Test + fun updatesShowIndicatorForDeviceEntry_fromPrimaryBouncer_whenFpsDetectionNotRunning() { + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = false, + isUnlockingWithFpAllowed = true + ) + assertThat(showIndicatorForDeviceEntry).isEqualTo(false) + } + } + + @Test + fun updatesShowIndicatorForDeviceEntry_fromPrimaryBouncer_onUnlockingWithFpDisallowed() { + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = false + ) + assertThat(showIndicatorForDeviceEntry).isEqualTo(false) + } + } + + @Test + fun updatesShowIndicatorForDeviceEntry_onPrimaryBouncerAnimatingAway() { + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + updatePrimaryBouncer( + isShowing = true, + isAnimatingAway = true, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true + ) + assertThat(showIndicatorForDeviceEntry).isEqualTo(false) + } + } + + @Test + fun updatesShowIndicatorForDeviceEntry_onAlternateBouncerRequest() = + testScope.runTest { + val showIndicatorForDeviceEntry by + collectLastValue(underTest.showIndicatorForDeviceEntry) + runCurrent() + + bouncerRepository.setAlternateVisible(true) + assertThat(showIndicatorForDeviceEntry).isEqualTo(true) + + bouncerRepository.setAlternateVisible(false) + assertThat(showIndicatorForDeviceEntry).isEqualTo(false) + } + + private fun updatePrimaryBouncer( + isShowing: Boolean, + isAnimatingAway: Boolean, + fpsDetectionRunning: Boolean, + isUnlockingWithFpAllowed: Boolean, + ) { + bouncerRepository.setPrimaryShow(isShowing) + bouncerRepository.setPrimaryStartingToHide(false) + val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null + bouncerRepository.setPrimaryStartDisappearAnimation(primaryStartDisappearAnimation) + + whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) + .thenReturn(fpsDetectionRunning) + whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) + .thenReturn(isUnlockingWithFpAllowed) + mContext.orCreateTestableResources.addOverride( + R.bool.config_show_sidefps_hint_on_bouncer, + true + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt index 8dea57c2eb342817c73698760257227435082294..8e81185d6dcf0db07a0d1fc6082d3532b977328f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt @@ -20,8 +20,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,11 +44,7 @@ import org.junit.runner.RunWith class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() { private lateinit var underTest: FromPrimaryBouncerTransitionInteractor - private val mSelectedUserInteractor = - SelectedUserInteractor( - FakeUserRepository(), - FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) } - ) + private val mSelectedUserInteractor = SelectedUserInteractor(FakeUserRepository()) // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our // underTest interactor is provided to any classes that need it. diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt index 119ffd28bebe982ec8ff065f88c94ae561abf08c..ebd34de463f4e714e16bd9ce6767adf5b63d4ddf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt @@ -28,7 +28,6 @@ import android.os.UserManager import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import com.android.dx.mockito.inline.extended.ExtendedMockito -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -40,6 +39,7 @@ import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.C import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEntryPoint import com.android.systemui.notetask.NoteTaskInfoResolver +import com.android.systemui.res.R import com.android.systemui.stylus.StylusManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any @@ -134,12 +134,9 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { // region lockScreenState @Test fun lockScreenState_stylusUsed_userUnlocked_isSelected_shouldEmitVisible() = runTest { - TestConfig() - .setStylusEverUsed(true) - .setUserUnlocked(true) - .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>()) - val underTest = createUnderTest() + TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections(underTest) + val actual by collectLastValue(underTest.lockScreenState) assertThat(actual).isEqualTo(createLockScreenStateVisible()) @@ -148,10 +145,11 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { @Test fun lockScreenState_stylusUsed_userUnlocked_isSelected_noDefaultNotesAppSet_shouldEmitHidden() = runTest { + val underTest = createUnderTest() TestConfig() .setStylusEverUsed(true) .setUserUnlocked(true) - .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>()) + .setConfigSelections(underTest) whenever( roleManager.getRoleHoldersAsUser( eq(RoleManager.ROLE_NOTES), @@ -160,7 +158,6 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { ) .thenReturn(emptyList()) - val underTest = createUnderTest() val actual by collectLastValue(underTest.lockScreenState) assertThat(actual).isEqualTo(LockScreenState.Hidden) @@ -168,12 +165,9 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { @Test fun lockScreenState_stylusUnused_userUnlocked_isSelected_shouldEmitHidden() = runTest { - TestConfig() - .setStylusEverUsed(false) - .setUserUnlocked(true) - .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>()) - val underTest = createUnderTest() + TestConfig().setStylusEverUsed(false).setUserUnlocked(true).setConfigSelections(underTest) + val actual by collectLastValue(underTest.lockScreenState) assertThat(actual).isEqualTo(LockScreenState.Hidden) @@ -181,25 +175,22 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { @Test fun lockScreenState_stylusUsed_userLocked_isSelected_shouldEmitHidden() = runTest { - TestConfig() - .setStylusEverUsed(true) - .setUserUnlocked(false) - .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>()) - val underTest = createUnderTest() + TestConfig().setStylusEverUsed(true).setUserUnlocked(false).setConfigSelections(underTest) + val actual by collectLastValue(underTest.lockScreenState) assertThat(actual).isEqualTo(LockScreenState.Hidden) } @Test - fun lockScreenState_stylusUsed_userUnlocked_noSelected_shouldEmitVisible() = runTest { + fun lockScreenState_stylusUsed_userUnlocked_noSelected_shouldEmitHidden() = runTest { TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections() val underTest = createUnderTest() val actual by collectLastValue(underTest.lockScreenState) - assertThat(actual).isEqualTo(createLockScreenStateVisible()) + assertThat(actual).isEqualTo(LockScreenState.Hidden) } @Test @@ -223,13 +214,13 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun lockScreenState_stylusUsed_userUnlocked_customSelections_shouldEmitVisible() = runTest { + fun lockScreenState_stylusUsed_userUnlocked_customSelections_shouldEmitHidden() = runTest { TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections(mock()) val underTest = createUnderTest() val actual by collectLastValue(underTest.lockScreenState) - assertThat(actual).isEqualTo(createLockScreenStateVisible()) + assertThat(actual).isEqualTo(LockScreenState.Hidden) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 112368895888b307637305cc007568e16f98ba16..b58a41c89a4ec877a6a7a7b86ecf73b12f47f152 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.theme; +import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; @@ -90,6 +91,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { private static final int USER_SYSTEM = UserHandle.USER_SYSTEM; private static final int USER_SECONDARY = 10; + private static final UserHandle MANAGED_USER_HANDLE = UserHandle.of(100); + private static final UserHandle PRIVATE_USER_HANDLE = UserHandle.of(101); + @Mock private JavaAdapter mJavaAdapter; @Mock @@ -174,6 +178,14 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { Integer.toHexString(mColorScheme.getSeed() | 0xff000000))); return overlay; } + + @VisibleForTesting + protected boolean isPrivateProfile(UserHandle userHandle) { + if (userHandle.getIdentifier() == PRIVATE_USER_HANDLE.getIdentifier()) { + return true; + } + return false; + } }; mWakefulnessLifecycle.dispatchFinishedWakingUp(); @@ -675,7 +687,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { @Test public void onProfileAdded_setsTheme() { mBroadcastReceiver.getValue().onReceive(null, - new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED)); + new Intent(Intent.ACTION_PROFILE_ADDED) + .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE)); verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); } @@ -684,7 +697,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { reset(mDeviceProvisionedController); when(mUserManager.isManagedProfile(anyInt())).thenReturn(false); mBroadcastReceiver.getValue().onReceive(null, - new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED)); + new Intent(Intent.ACTION_PROFILE_ADDED) + .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE)); verify(mThemeOverlayApplier) .applyCurrentUserOverlays(any(), any(), anyInt(), any()); } @@ -694,11 +708,25 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { reset(mDeviceProvisionedController); when(mUserManager.isManagedProfile(anyInt())).thenReturn(true); mBroadcastReceiver.getValue().onReceive(null, - new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED)); + new Intent(Intent.ACTION_PROFILE_ADDED) + .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE)); + verify(mThemeOverlayApplier, never()) + .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + } + + @Test + public void onPrivateProfileAdded_ignoresUntilStartComplete() { + mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE); + reset(mDeviceProvisionedController); + when(mUserManager.isManagedProfile(anyInt())).thenReturn(false); + mBroadcastReceiver.getValue().onReceive(null, + (new Intent(Intent.ACTION_PROFILE_ADDED)) + .putExtra(Intent.EXTRA_USER, PRIVATE_USER_HANDLE)); verify(mThemeOverlayApplier, never()) .applyCurrentUserOverlays(any(), any(), anyInt(), any()); } + @Test public void onWallpaperColorsChanged_firstEventBeforeUserSetup_shouldBeAccepted() { // By default, on setup() we make this controller return that the user finished setup diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt index 60fe7d2248e476df4e2c42dc0a1eb93f0bff152e..140e919d613ffa50008318c681b28803c05ac569 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt @@ -2,9 +2,8 @@ package com.android.systemui.user.domain.interactor import android.content.pm.UserInfo import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.user.data.repository.FakeUserRepository import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.runBlocking @@ -24,15 +23,12 @@ class SelectedUserInteractorTest : SysuiTestCase() { @Before fun setUp() { userRepository.setUserInfos(USER_INFOS) - underTest = - SelectedUserInteractor( - userRepository, - FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) } - ) + underTest = SelectedUserInteractor(userRepository) } @Test fun getSelectedUserIdReturnsId() { + mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER) runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) } val actualId = underTest.getSelectedUserId() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeColorCorrectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeColorCorrectionRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..607a4f3e1c0f28edebef4b11e358d90580cbc0df --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeColorCorrectionRepository.kt @@ -0,0 +1,39 @@ +/* + * 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.accessibility.data.repository + +import android.os.UserHandle +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeColorCorrectionRepository : ColorCorrectionRepository { + private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>() + + override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> { + return getFlow(userHandle.identifier) + } + + override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean { + getFlow(userHandle.identifier).value = isEnabled + return true + } + + /** initializes the flow if already not */ + private fun getFlow(userId: Int): MutableStateFlow<Boolean> { + return userMap.getOrPut(userId) { MutableStateFlow(false) } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..1c8bd3b58dfe8e7687e1d76ea8ce609d0c75f6e5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt @@ -0,0 +1,34 @@ +/* + * 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.biometrics.data.repository + +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeBiometricStatusRepository : BiometricStatusRepository { + private val _fingerprintAuthenticationReason = + MutableStateFlow<AuthenticationReason>(AuthenticationReason.NotRunning) + override val fingerprintAuthenticationReason: StateFlow<AuthenticationReason> = + _fingerprintAuthenticationReason.asStateFlow() + + fun setFingerprintAuthenticationReason(reason: AuthenticationReason) { + _fingerprintAuthenticationReason.value = reason + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt index f84481c556644aa6a39c683dcd318a0133865b12..ff5179a7042c2f150f82af0ad71da186fdb2f5b9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt @@ -1,5 +1,6 @@ package com.android.systemui.bouncer.data.repository +import com.android.systemui.biometrics.shared.SideFpsControllerRefactor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel import com.android.systemui.dagger.SysUISingleton @@ -113,7 +114,9 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos _isBackButtonEnabled.value = isBackButtonEnabled } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR override fun setSideFpsShowing(isShowing: Boolean) { + SideFpsControllerRefactor.assertInLegacyMode() _sideFpsShowing.value = isShowing } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt index c9160efb75a472f624d9c2ae5acc3da3009dc622..1d44929a20f0db70bdf6acc79e7d16c30f43d98b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt @@ -54,6 +54,11 @@ class FakeDeviceEntryFingerprintAuthRepository @Inject constructor() : private var _authenticationStatus = MutableStateFlow<FingerprintAuthenticationStatus?>(null) override val authenticationStatus: Flow<FingerprintAuthenticationStatus> get() = _authenticationStatus.filterNotNull() + + private var _shouldUpdateIndicatorVisibility = MutableStateFlow(false) + override val shouldUpdateIndicatorVisibility: Flow<Boolean> + get() = _shouldUpdateIndicatorVisibility + fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) { _authenticationStatus.value = status } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt index 3d8ae1e9801a82b066714f9959eec35bd0ea4758..3cabf0c074235721a61caf155444e1bf211a07da 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt @@ -27,8 +27,6 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInte 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.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -57,12 +55,6 @@ object KeyguardDismissInteractorFactory { keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(), bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(), keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(KeyguardUpdateMonitor::class.java), - featureFlags: FakeFeatureFlagsClassic = - FakeFeatureFlagsClassic().apply { - set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, true) - set(Flags.FULL_SCREEN_USER_SWITCHER, false) - set(Flags.REFACTOR_GETCURRENTUSER, true) - }, powerRepository: FakePowerRepository = FakePowerRepository(), userRepository: FakeUserRepository = FakeUserRepository(), ): WithDependencies { @@ -98,8 +90,7 @@ object KeyguardDismissInteractorFactory { PowerInteractorFactory.create( repository = powerRepository, ) - val selectedUserInteractor = - SelectedUserInteractor(repository = userRepository, flags = featureFlags) + val selectedUserInteractor = SelectedUserInteractor(repository = userRepository) return WithDependencies( trustRepository = trustRepository, keyguardRepository = keyguardRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/colorcorrection/ColorCorrectionTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/colorcorrection/ColorCorrectionTileKosmos.kt new file mode 100644 index 0000000000000000000000000000000000000000..0357036d907cfa252df99ba067a29aa0f4228ee5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/colorcorrection/ColorCorrectionTileKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.colorcorrection + +import com.android.systemui.accessibility.qs.QSAccessibilityModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger + +val Kosmos.qsColorCorrectionTileConfig by + Kosmos.Fixture { QSAccessibilityModule.provideColorCorrectionTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index d78bcb93b25652f62ed070ef0648105234b52181..0b41926ed13e5065412d2627fd81e3f9abe0053f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -387,7 +387,7 @@ class SceneTestUtils( } fun selectedUserInteractor(): SelectedUserInteractor { - return SelectedUserInteractor(userRepository, featureFlags) + return SelectedUserInteractor(userRepository) } fun bouncerActionButtonInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt index 427f92a7f5143fef7957023cb203ad286a36e00a..89672f10965794abcc9428efb3820b7f940f88ab 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt @@ -16,9 +16,7 @@ package com.android.systemui.user.domain.interactor -import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.kosmos.Kosmos import com.android.systemui.user.data.repository.userRepository -val Kosmos.selectedUserInteractor by - Kosmos.Fixture { SelectedUserInteractor(userRepository, featureFlagsClassic) } +val Kosmos.selectedUserInteractor by Kosmos.Fixture { SelectedUserInteractor(userRepository) } diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp index b8cf13b11534340759733a6799a222912c2e7a54..e2488a51bba13dc6b36f3661ccac5e7b108fe2c6 100644 --- a/services/accessibility/Android.bp +++ b/services/accessibility/Android.bp @@ -19,6 +19,9 @@ java_library_static { defaults: [ "platform_service_defaults", ], + lint: { + error_checks: ["MissingPermissionAnnotation"], + }, srcs: [ ":services.accessibility-sources", "//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc", diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 97d36d443620ee3b5418aa4fc1a21c6c009e6f41..1d73843260cbb8d8e49bcc160986832c80919fdd 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -118,6 +118,7 @@ import java.util.Set; * This class represents an accessibility client - either an AccessibilityService or a UiAutomation. * It is responsible for behavior common to both types of clients. */ +@SuppressWarnings("MissingPermissionAnnotation") abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter, FingerprintGestureDispatcher.FingerprintGestureClient { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 9ddc35ae240b645a2c1de7bf238802b269acfb13..abcd8e2a2d7e13a0d04a96f1e9dcc7bec95b1c59 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -57,6 +57,7 @@ import java.util.StringJoiner; * * NOTE: This class has to be created and poked only from the main thread. */ +@SuppressWarnings("MissingPermissionAnnotation") class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation { private static final String TAG = AccessibilityInputFilter.class.getSimpleName(); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index edb41639514b4d9cd78e62003cb8ae25e45566d3..3d8d7b738233fd0cc6d44900263f2124ae86886f 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -195,6 +195,7 @@ import java.util.function.Predicate; * event dispatch for {@link AccessibilityEvent}s generated across all processes * on the device. Events are dispatched to {@link AccessibilityService}s. */ +@SuppressWarnings("MissingPermissionAnnotation") public class AccessibilityManagerService extends IAccessibilityManager.Stub implements AbstractAccessibilityServiceConnection.SystemSupport, AccessibilityUserState.ServiceInfoChangeListener, diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 40ca694dddd8ecc62c414dc6de6f94bb296f9c84..5ebe16115a4b28ff2cf63ca27194272aade4a2ce 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -65,6 +65,7 @@ import java.util.Set; * passed to the service it represents as soon it is bound. It also serves as the * connection for the service. */ +@SuppressWarnings("MissingPermissionAnnotation") class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection { private static final String LOG_TAG = "AccessibilityServiceConnection"; diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java index a525e7c64bc3e60c29262d7278b7255dc6064bea..b119d7d117cdc0e301e83974fe6f08d2e817e9a6 100644 --- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java +++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java @@ -34,6 +34,7 @@ import java.util.List; * If we are stripping and/or replacing the actions from a window, we need to intercept the * nodes heading back to the service and swap out the actions. */ +@SuppressWarnings("MissingPermissionAnnotation") public class ActionReplacingCallback extends IAccessibilityInteractionConnectionCallback.Stub { private static final boolean DEBUG = false; private static final String LOG_TAG = "ActionReplacingCallback"; diff --git a/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java index c9ec16edc54e9e16645013d379f35f2f8e61d4a7..e10e87c51d59104ca8814b42e17f7081d9c46f39 100644 --- a/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java +++ b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java @@ -33,6 +33,7 @@ import java.util.List; /** * Encapsulate fingerprint gesture logic */ +@SuppressWarnings("MissingPermissionAnnotation") public class FingerprintGestureDispatcher extends IFingerprintClientActiveCallback.Stub implements Handler.Callback{ private static final int MSG_REGISTER = 1; diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java index ab01fc324ed2e5077ea456c6ebd47b935bd8b695..6aa4702ec7d594f8feb64abea6e220c91a2b2d0e 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java @@ -64,6 +64,7 @@ import java.util.Set; * * TODO(241429275): Initialize this when a proxy is registered. */ +@SuppressWarnings("MissingPermissionAnnotation") public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection { private static final String LOG_TAG = "ProxyAccessibilityServiceConnection"; diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 53c629a9ed2d9c26ad12bd073f78ae8ae07253ec..f69104db7c106e70eb1542fc2114e1ebec0c7b08 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -245,6 +245,7 @@ class UiAutomationManager { } } + @SuppressWarnings("MissingPermissionAnnotation") private class UiAutomationService extends AbstractAccessibilityServiceConnection { private final Handler mMainHandler; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java index e6af54bd937ca2b02b90696ece133f480143570e..e11c36a918303ed713808ef374901ced08871881 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java @@ -922,6 +922,7 @@ public class MagnificationConnectionManager implements disableWindowMagnification(displayId, true); } + @SuppressWarnings("MissingPermissionAnnotation") private class ConnectionCallback extends IMagnificationConnectionCallback.Stub implements IBinder.DeathRecipient { private boolean mExpiredDeathRecipient = false; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java index c63784a79ffd01c8a65316c14e9ca2fc895ba824..db5b3133169ab1ffe2d6c06e3ca48aa1cbb4f529 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java @@ -246,6 +246,7 @@ class MagnificationConnectionWrapper { return new RemoteAnimationCallback(callback, trace); } + @SuppressWarnings("MissingPermissionAnnotation") private static class RemoteAnimationCallback extends IRemoteMagnificationAnimationCallback.Stub { private final MagnificationAnimationCallback mCallback; diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 77a5e3db2abad6d8b8e448ec3ba04b33fcd35215..a4b28967e3b271aa4a0fc1c9442e197baf42954e 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -72,6 +72,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.ShortcutServiceInternal; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; +import android.content.pm.UserPackage; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -559,10 +560,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku onClickIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(appUserId); } else if (provider.maskedBySuspendedPackage) { showBadge = mUserManager.hasBadge(appUserId); - final String suspendingPackage = mPackageManagerInternal.getSuspendingPackage( + final UserPackage suspendingPackage = mPackageManagerInternal.getSuspendingPackage( appInfo.packageName, appUserId); // TODO(b/281839596): don't rely on platform always meaning suspended by admin. - if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) { + if (suspendingPackage != null + && PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) { onClickIntent = mDevicePolicyManagerInternal.createShowAdminSupportIntent( appUserId, true); } else { diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig index ca6fefdd8245ab71954199d3cf73925577d11bcb..b5130a1c4cfc4f7bd181583a6501e88ae4623b08 100644 --- a/services/autofill/bugfixes.aconfig +++ b/services/autofill/bugfixes.aconfig @@ -29,7 +29,7 @@ flag { } flag { - name: "ignore_invisible_view_group_in_assist_structure" + name: "include_invisible_view_group_in_assist_structure" namespace: "autofill" description: "Mitigation for autofill providers miscalculating view visibility" bug: "291795358" diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 23e7ce68c1d0bd359e14392f4506c8406729c2b6..9fdf5c2d0fc1637f00ca293519baa88844c8bf10 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -92,9 +92,10 @@ class CompanionDeviceShellCommand extends ShellCommand { int userId = getNextIntArgRequired(); String packageName = getNextArgRequired(); String address = getNextArgRequired(); + String deviceProfile = getNextArg(); final MacAddress macAddress = MacAddress.fromString(address); mService.createNewAssociation(userId, packageName, macAddress, - null, null, false); + null, deviceProfile, false); } break; @@ -350,7 +351,7 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" Print this help text."); pw.println(" list USER_ID"); pw.println(" List all Associations for a user."); - pw.println(" associate USER_ID PACKAGE MAC_ADDRESS"); + pw.println(" associate USER_ID PACKAGE MAC_ADDRESS [DEVICE_PROFILE]"); pw.println(" Create a new Association."); pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS"); pw.println(" Remove an existing Association."); diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 8dc6537d7e80195bc6d7d6096dae9c731973c223..0d5cdcbe484c6d4a13385a9f0d2c2952f82ece11 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -439,6 +439,12 @@ public class VirtualDeviceManagerService extends SystemService { if (associationInfo == null) { throw new IllegalArgumentException("No association with ID " + associationId); } + if (!VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES + .contains(associationInfo.getDeviceProfile()) + && Flags.persistentDeviceIdApi()) { + throw new IllegalArgumentException("Unsupported CDM Association device profile " + + associationInfo.getDeviceProfile() + " for virtual device creation."); + } Objects.requireNonNull(params); Objects.requireNonNull(activityListener); Objects.requireNonNull(soundEffectListener); diff --git a/services/core/Android.bp b/services/core/Android.bp index a0ccbf3acf0a67ffc98b0e05a8d134ad5f680565..f5a80d80f2713577d1e5d31c23c3405a10bbdbf5 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -200,6 +200,7 @@ java_library_static { "notification_flags_lib", "biometrics_flags_lib", "am_flags_lib", + "com_android_systemui_shared_flags_lib", "com_android_wm_shell_flags_lib", "com.android.server.utils_aconfig-java", "service-jobscheduler-deviceidle.flags-aconfig-java", diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 136692eb90d5543402c068b0be9456243a45f029..cac2efba1c8935ebe861388c9fe37d21877ae897 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -289,11 +289,11 @@ public abstract class PackageManagerInternal { * * @param suspendedPackage The package that has been suspended. * @param userId The user for which to check. - * @return Name of the package that suspended the given package. Returns {@code null} if the - * given package is not currently suspended and the platform package name - i.e. - * {@code "android"} - if the package was suspended by a device admin. + * @return User id and package name of the package that suspended the given package. Returns + * {@code null} if the given package is not currently suspended and the platform package name + * - i.e. {@code "android"} - if the package was suspended by a device admin. */ - public abstract String getSuspendingPackage(String suspendedPackage, int userId); + public abstract UserPackage getSuspendingPackage(String suspendedPackage, int userId); /** * Suspend or unsuspend packages upon admin request. @@ -312,13 +312,13 @@ public abstract class PackageManagerInternal { * suspended application. * * @param suspendedPackage The package that has been suspended. - * @param suspendingPackage + * @param suspendingPackage The package responsible for suspension. * @param userId The user for which to check. * @return A {@link SuspendDialogInfo} object describing the dialog to be shown. */ @Nullable public abstract SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage, - String suspendingPackage, int userId); + UserPackage suspendingPackage, int userId); /** * Gets any distraction flags set via @@ -1168,14 +1168,14 @@ public abstract class PackageManagerInternal { public abstract void clearBlockUninstallForUser(@UserIdInt int userId); /** - * Unsuspends all packages suspended by the given package for the user. + * Unsuspends all packages suspended by an admin for the user. */ - public abstract void unsuspendForSuspendingPackage(String suspendingPackage, int userId); + public abstract void unsuspendAdminSuspendedPackages(int userId); /** - * Returns {@code true} if the package is suspending any packages for the user. + * Returns {@code true} if an admin is suspending any packages for the user. */ - public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId); + public abstract boolean isAdminSuspendingAnyPackages(int userId); /** * Register to listen for loading progress of an installed package. diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 33726d17da62ece8c2acdf4ccc7336e34cbf56c6..5a44ac803cb4ee151069a91a5c4795260c1abc59 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -974,6 +974,7 @@ public final class BatteryService extends SystemService { unplugBattery(/* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw); } break; case "get": { + final int opts = parseOptions(shell); final String key = shell.getNextArg(); if (key == null) { pw.println("No property specified"); @@ -1007,11 +1008,17 @@ public final class BatteryService extends SystemService { break; case "current_now": if (batteryServiceSupportCurrentAdbCommand()) { + if ((opts & OPTION_FORCE_UPDATE) != 0) { + updateHealthInfo(); + } pw.println(mHealthInfo.batteryCurrentMicroamps); } break; case "current_average": if (batteryServiceSupportCurrentAdbCommand()) { + if ((opts & OPTION_FORCE_UPDATE) != 0) { + updateHealthInfo(); + } pw.println(mHealthInfo.batteryCurrentAverageMicroamps); } break; @@ -1125,6 +1132,14 @@ public final class BatteryService extends SystemService { return 0; } + private void updateHealthInfo() { + try { + mHealthServiceWrapper.scheduleUpdate(); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to update health service data.", e); + } + } + private void setChargerAcOnline(boolean online, boolean forceUpdate) { if (!mUpdatesStopped) { copyV1Battery(mLastHealthInfo, mHealthInfo); diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 0629e6373b6eda08fd0619f8933a5b235828cc62..dafea9a199fdc6a1616efca58b8549b002e8827b 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -36,6 +36,7 @@ import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.ComponentInfoInternal; @@ -384,6 +385,26 @@ public class AuthService extends SystemService { } } + @Override + public void registerAuthenticationStateListener(AuthenticationStateListener listener) + throws RemoteException { + checkInternalPermission(); + final IFingerprintService fingerprintService = mInjector.getFingerprintService(); + if (fingerprintService != null) { + fingerprintService.registerAuthenticationStateListener(listener); + } + } + + @Override + public void unregisterAuthenticationStateListener(AuthenticationStateListener listener) + throws RemoteException { + checkInternalPermission(); + final IFingerprintService fingerprintService = mInjector.getFingerprintService(); + if (fingerprintService != null) { + fingerprintService.unregisterAuthenticationStateListener(listener); + } + } + @Override public void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback) throws RemoteException { diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index a47135fd032ff8843d42685ad9ed6ef2ebf39f44..f9568ea9d72bad6ce27c16eae82a365415ebd529 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -27,7 +27,7 @@ import android.hardware.biometrics.AuthenticateOptions; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.os.IBinder; import android.os.RemoteException; import android.security.KeyStore; @@ -430,19 +430,19 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions> return mLockoutTracker; } - protected int getShowOverlayReason() { + protected int getRequestReason() { if (isKeyguard()) { - return BiometricOverlayConstants.REASON_AUTH_KEYGUARD; + return BiometricRequestConstants.REASON_AUTH_KEYGUARD; } else if (isBiometricPrompt()) { // BP reason always takes precedent over settings, since callers from within // settings can always invoke BP. - return BiometricOverlayConstants.REASON_AUTH_BP; + return BiometricRequestConstants.REASON_AUTH_BP; } else if (isSettings()) { // This is pretty much only for FingerprintManager#authenticate usage from // FingerprintSettings. - return BiometricOverlayConstants.REASON_AUTH_SETTINGS; + return BiometricRequestConstants.REASON_AUTH_SETTINGS; } else { - return BiometricOverlayConstants.REASON_AUTH_OTHER; + return BiometricRequestConstants.REASON_AUTH_OTHER; } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java new file mode 100644 index 0000000000000000000000000000000000000000..58635353c7806c90627b8f7fbb96afc9d11730a5 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java @@ -0,0 +1,108 @@ +/* + * 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.server.biometrics.sensors; + +import android.annotation.NonNull; +import android.hardware.biometrics.AuthenticationStateListener; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Low-level callback interface between BiometricManager and AuthService. Allows core system + * services (e.g. SystemUI) to register and unregister listeners for updates about the current + * state of biometric authentication. + * @hide */ +public class AuthenticationStateListeners implements IBinder.DeathRecipient { + + private static final String TAG = "AuthenticationStateListeners"; + + @NonNull + private final CopyOnWriteArrayList<AuthenticationStateListener> mAuthenticationStateListeners = + new CopyOnWriteArrayList<>(); + + /** + * Enables clients to register an AuthenticationStateListener for updates about the current + * state of biometric authentication. + * @param listener listener to register + */ + public void registerAuthenticationStateListener( + @NonNull AuthenticationStateListener listener) { + mAuthenticationStateListeners.add(listener); + try { + listener.asBinder().linkToDeath(this, 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death", e); + } + } + + /** + * Enables clients to unregister an AuthenticationStateListener. + * @param listener listener to register + */ + public void unregisterAuthenticationStateListener( + @NonNull AuthenticationStateListener listener) { + mAuthenticationStateListeners.remove(listener); + } + + /** + * Defines behavior in response to authentication starting + * @param requestReason reason from [BiometricRequestConstants.RequestReason] for requesting + * authentication starting + */ + public void onAuthenticationStarted(int requestReason) { + for (AuthenticationStateListener listener: mAuthenticationStateListeners) { + try { + listener.onAuthenticationStarted(requestReason); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception in notifying listener that authentication " + + "started", e); + } + } + } + + /** + * Defines behavior in response to authentication stopping + */ + public void onAuthenticationStopped() { + for (AuthenticationStateListener listener: mAuthenticationStateListeners) { + try { + listener.onAuthenticationStopped(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception in notifying listener that authentication " + + "stopped", e); + } + } + } + + @Override + public void binderDied() { + // Do nothing, handled below + } + + @Override + public void binderDied(IBinder who) { + Slog.w(TAG, "Callback binder died: " + who); + if (mAuthenticationStateListeners.removeIf(listener -> listener.asBinder().equals(who))) { + Slog.w(TAG, "Removed dead listener for " + who); + } else { + Slog.w(TAG, "No dead listeners found"); + } + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java index 483ce75eae98df91f002fed6c931427ac0c6d31a..2c4d30b622addafe49d7e8c99bd8420b190ef772 100644 --- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java @@ -19,7 +19,7 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.fingerprint.FingerprintManager; import android.os.IBinder; import android.os.RemoteException; @@ -135,14 +135,14 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En return true; } - protected int getOverlayReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) { + protected int getRequestReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) { switch (reason) { case FingerprintManager.ENROLL_FIND_SENSOR: - return BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR; + return BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR; case FingerprintManager.ENROLL_ENROLL: - return BiometricOverlayConstants.REASON_ENROLL_ENROLLING; + return BiometricRequestConstants.REASON_ENROLL_ENROLLING; default: - return BiometricOverlayConstants.REASON_UNKNOWN; + return BiometricRequestConstants.REASON_UNKNOWN; } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java index aeb6b6e2a90737b4485496b0d030dfe0aa71dcc8..3d20efc88c77b850601e11039f78fbe33e4a529e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java +++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java @@ -16,9 +16,11 @@ package com.android.server.biometrics.sensors; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; @@ -42,6 +44,8 @@ public final class SensorOverlays { private static final String TAG = "SensorOverlays"; @NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController; + + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @NonNull private final Optional<ISidefpsController> mSidefpsController; /** @@ -57,6 +61,17 @@ public final class SensorOverlays { mSidefpsController = Optional.ofNullable(sidefpsController); } + /** + * Create an overlay controller for each modality. + * + * @param udfpsOverlayController under display fps or null if not present on device + */ + public SensorOverlays(@Nullable IUdfpsOverlayController udfpsOverlayController) { + mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController); + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + mSidefpsController = Optional.empty(); + } + /** * Show the overlay. * @@ -64,13 +79,15 @@ public final class SensorOverlays { * @param reason reason for showing * @param client client performing operation */ - public void show(int sensorId, @BiometricOverlayConstants.ShowReason int reason, + public void show(int sensorId, @BiometricRequestConstants.RequestReason int reason, @NonNull AcquisitionClient<?> client) { - if (mSidefpsController.isPresent()) { - try { - mSidefpsController.get().show(sensorId, reason); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when showing the side-fps overlay", e); + if (!sidefpsControllerRefactor()) { + if (mSidefpsController.isPresent()) { + try { + mSidefpsController.get().show(sensorId, reason); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when showing the side-fps overlay", e); + } } } @@ -98,11 +115,13 @@ public final class SensorOverlays { * @param sensorId sensor id */ public void hide(int sensorId) { - if (mSidefpsController.isPresent()) { - try { - mSidefpsController.get().hide(sensorId); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e); + if (!sidefpsControllerRefactor()) { + if (mSidefpsController.isPresent()) { + try { + mSidefpsController.get().hide(sensorId); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e); + } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 769554315b6e78fd08d556d1a8e7393bcb02fda6..83b306b07c277075aefd2ed995769dcb3594d44b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -33,6 +33,7 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricSensorReceiver; @@ -81,6 +82,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.server.SystemService; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricStateCallback; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -128,6 +130,8 @@ public class FingerprintService extends SystemService { private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal> mBiometricStateCallback; @NonNull + private final AuthenticationStateListeners mAuthenticationStateListeners; + @NonNull private final Handler mHandler; @NonNull private final FingerprintServiceRegistry mRegistry; @@ -891,6 +895,7 @@ public class FingerprintService extends SystemService { return providers; }); } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void addAuthenticatorsRegisteredCallback( @@ -900,6 +905,24 @@ public class FingerprintService extends SystemService { mRegistry.addAllRegisteredCallback(callback); } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override + public void registerAuthenticationStateListener( + @NonNull AuthenticationStateListener listener) { + super.registerAuthenticationStateListener_enforcePermission(); + + mAuthenticationStateListeners.registerAuthenticationStateListener(listener); + } + + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override + public void unregisterAuthenticationStateListener( + @NonNull AuthenticationStateListener listener) { + super.unregisterAuthenticationStateListener_enforcePermission(); + + mAuthenticationStateListeners.unregisterAuthenticationStateListener(listener); + } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) { @@ -957,6 +980,7 @@ public class FingerprintService extends SystemService { } } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void setSidefpsController(@NonNull ISidefpsController controller) { @@ -1014,6 +1038,7 @@ public class FingerprintService extends SystemService { mLockoutResetDispatcher = new LockoutResetDispatcher(context); mLockPatternUtils = new LockPatternUtils(context); mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context)); + mAuthenticationStateListeners = new AuthenticationStateListeners(); mFingerprintProvider = fingerprintProvider != null ? fingerprintProvider : (name) -> { final String fqName = IFingerprint.DESCRIPTOR + "/" + name; @@ -1022,9 +1047,9 @@ public class FingerprintService extends SystemService { if (fp != null) { try { return new FingerprintProvider(getContext(), - mBiometricStateCallback, fp.getSensorProps(), name, - mLockoutResetDispatcher, mGestureAvailabilityDispatcher, - mBiometricContext); + mBiometricStateCallback, mAuthenticationStateListeners, + fp.getSensorProps(), name, mLockoutResetDispatcher, + mGestureAvailabilityDispatcher, mBiometricContext); } catch (RemoteException e) { Slog.e(TAG, "Remote exception in getSensorProps: " + fqName); } @@ -1086,13 +1111,13 @@ public class FingerprintService extends SystemService { Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */, UserHandle.USER_CURRENT) != 0) { fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), - mBiometricStateCallback, hidlSensor, - mLockoutResetDispatcher, mGestureAvailabilityDispatcher, + mBiometricStateCallback, mAuthenticationStateListeners, + hidlSensor, mLockoutResetDispatcher, mGestureAvailabilityDispatcher, BiometricContext.getInstance(getContext())); } else { fingerprint21 = Fingerprint21.newInstance(getContext(), - mBiometricStateCallback, hidlSensor, mHandler, - mLockoutResetDispatcher, mGestureAvailabilityDispatcher); + mBiometricStateCallback, mAuthenticationStateListeners, hidlSensor, + mHandler, mLockoutResetDispatcher, mGestureAvailabilityDispatcher); } providers.add(fingerprint21); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index a15d1a4df39f053a8b6b7d43594a208c11771140..fc37d7020a211979674cd811c41f1e6f293cd390 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -135,6 +135,7 @@ public interface ServiceProvider extends void onPowerPressed(); + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR /** * Sets side-fps controller * @param controller side-fps controller diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 337c3c299e54a67692b2a846f3099b1187130419..29c5a3de3a80911bd00f33823ef7c7a1dfde0d79 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskStackListener; @@ -48,6 +50,7 @@ import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -84,6 +87,7 @@ public class FingerprintAuthenticationClient private final int mSkipWaitForPowerVendorAcquireMessage; private final long mFingerUpIgnoresPower = 500; private final AuthSessionCoordinator mAuthSessionCoordinator; + @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners; @Nullable private ICancellationSignal mCancellationSignal; private boolean mIsPointerDown; @@ -110,7 +114,9 @@ public class FingerprintAuthenticationClient boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener, @Nullable IUdfpsOverlayController udfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable ISidefpsController sidefpsController, + @NonNull AuthenticationStateListeners authenticationStateListeners, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull Handler handler, @@ -136,7 +142,12 @@ public class FingerprintAuthenticationClient false /* shouldVibrate */, biometricStrength); setRequestId(requestId); - mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + } + mAuthenticationStateListeners = authenticationStateListeners; mSensorProps = sensorProps; mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */); mHandler = handler; @@ -216,6 +227,9 @@ public class FingerprintAuthenticationClient if (authenticated) { mState = STATE_STOPPED; mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } else { mState = STATE_STARTED_PAUSED_ATTEMPTED; } @@ -241,6 +255,9 @@ public class FingerprintAuthenticationClient // controlled by the HAL, the framework must stop the sensor before finishing the // client. mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); cancel(); } @@ -266,11 +283,17 @@ public class FingerprintAuthenticationClient } mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this); + mSensorOverlays.show(getSensorId(), getRequestReason(), this); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason()); + } try { mCancellationSignal = doAuthenticate(); @@ -280,6 +303,9 @@ public class FingerprintAuthenticationClient BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } mCallback.onClientFinished(this, false /* success */); } } @@ -323,6 +349,9 @@ public class FingerprintAuthenticationClient @Override protected void stopHalOperation() { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } unsubscribeBiometricContext(); if (mCancellationSignal != null) { @@ -423,6 +452,9 @@ public class FingerprintAuthenticationClient } mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } mCallback.onClientFinished(this, false /* success */); } @@ -450,6 +482,9 @@ public class FingerprintAuthenticationClient } mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } mCallback.onClientFinished(this, false /* success */); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index e2413ee1c016241447611429922538e6981fe627..e58e5ae117b57370fe3e4cdf6fb95a736c9b5ef5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -16,10 +16,12 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.IUdfpsOverlayController; @@ -66,7 +68,12 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession> true /* shouldVibrate */, biometricLogger, biometricContext); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; - mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays( + udfpsOverlayController, null /* sideFpsController */); + } mOptions = options; } @@ -93,7 +100,7 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession> @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, + mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD, this); try { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 06550d8b4fce756e890ea6a576cb527aab76bd41..c0761ed8f32b76f4b710fbfc3586483f4345056a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -42,6 +44,7 @@ import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.log.Probe; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -65,6 +68,7 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback; private final @FingerprintManager.EnrollReason int mEnrollReason; + @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners; @Nullable private ICancellationSignal mCancellationSignal; private final int mMaxTemplatesPerUser; private boolean mIsPointerDown; @@ -80,15 +84,18 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement } } - public FingerprintEnrollClient(@NonNull Context context, - @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, + public FingerprintEnrollClient( + @NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull FingerprintSensorPropertiesInternal sensorProps, @Nullable IUdfpsOverlayController udfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable ISidefpsController sidefpsController, + @NonNull AuthenticationStateListeners authenticationStateListeners, int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) { // UDFPS haptics occur when an image is acquired (instead of when the result is known) super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, @@ -96,7 +103,13 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement logger, biometricContext); setRequestId(requestId); mSensorProps = sensorProps; - mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + } + mAuthenticationStateListeners = authenticationStateListeners; + mMaxTemplatesPerUser = maxTemplatesPerUser; mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */); @@ -130,7 +143,11 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement if (remaining == 0) { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } + } @Override @@ -159,8 +176,10 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @Override public void onError(int errorCode, int vendorCode) { super.onError(errorCode, vendorCode); - mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } @Override @@ -171,8 +190,12 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), + mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason), this); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStarted( + getRequestReasonFromEnrollReason(mEnrollReason)); + } BiometricNotificationUtils.cancelBadCalibrationNotification(getContext()); try { @@ -210,6 +233,10 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @Override protected void stopHalOperation() { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } + unsubscribeBiometricContext(); if (mCancellationSignal != null) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 9985b06833c947c70d2a5e279e18dc5cb5eb648b..032ab87196f80e71916b550117f02b713ba2c88b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -64,6 +64,7 @@ import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; @@ -108,6 +109,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull private final BiometricStateCallback mBiometricStateCallback; @NonNull + private final AuthenticationStateListeners mAuthenticationStateListeners; + @NonNull private final String mHalInstanceName; @NonNull private final Handler mHandler; @@ -122,6 +125,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull private final BiometricContext mBiometricContext; @Nullable private IFingerprint mDaemon; @Nullable private IUdfpsOverlayController mUdfpsOverlayController; + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable private ISidefpsController mSidefpsController; private AuthSessionCoordinator mAuthSessionCoordinator; @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector; @@ -157,16 +161,19 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public FingerprintProvider(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull SensorProps[] props, @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull BiometricContext biometricContext) { - this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher, - gestureAvailabilityDispatcher, biometricContext, null /* daemon */); + this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName, + lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, + null /* daemon */); } @VisibleForTesting FingerprintProvider(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull SensorProps[] props, @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @@ -174,6 +181,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi IFingerprint daemon) { mContext = context; mBiometricStateCallback = biometricStateCallback; + mAuthenticationStateListeners = authenticationStateListeners; mHalInstanceName = halInstanceName; mFingerprintSensors = new SensorList<>(ActivityManager.getService()); mHandler = new Handler(Looper.getMainLooper()); @@ -434,7 +442,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mBiometricContext, mFingerprintSensors.get(sensorId).getSensorProperties(), mUdfpsOverlayController, mSidefpsController, - maxTemplatesPerUser, enrollReason); + mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason); scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback( mBiometricStateCallback, new ClientMonitorCallback() { @Override @@ -498,7 +506,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mBiometricContext, isStrongBiometric, mTaskStackListener, mUdfpsOverlayController, mSidefpsController, - allowBackgroundAuthentication, + mAuthenticationStateListeners, allowBackgroundAuthentication, mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler, Utils.getCurrentStrength(sensorId), SystemClock.elapsedRealtimeClock(), @@ -732,6 +740,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Override public void setSidefpsController(@NonNull ISidefpsController controller) { mSidefpsController = controller; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 8bfa560b80317c42cfedbe6538cff73c0ff8874f..d3cecd0e34c7387fc7145e82ae63f8055b0fc623 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -68,6 +68,7 @@ import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; @@ -115,6 +116,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider final Context mContext; @NonNull private final BiometricStateCallback mBiometricStateCallback; + @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners; private final ActivityTaskManager mActivityTaskManager; @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties; private final BiometricScheduler mScheduler; @@ -128,6 +130,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @Nullable private IBiometricsFingerprint mDaemon; @NonNull private final HalResultController mHalResultController; @Nullable private IUdfpsOverlayController mUdfpsOverlayController; + + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable private ISidefpsController mSidefpsController; @NonNull private final BiometricContext mBiometricContext; @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector; @@ -330,6 +334,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @VisibleForTesting Fingerprint21(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull BiometricScheduler scheduler, @NonNull Handler handler, @@ -338,6 +343,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @NonNull BiometricContext biometricContext) { mContext = context; mBiometricStateCallback = biometricStateCallback; + mAuthenticationStateListeners = authenticationStateListeners; mBiometricContext = biometricContext; mSensorProperties = sensorProps; @@ -378,6 +384,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider public static Fingerprint21 newInstance(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @@ -388,8 +395,9 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider gestureAvailabilityDispatcher); final HalResultController controller = new HalResultController(sensorProps.sensorId, context, handler, scheduler); - return new Fingerprint21(context, biometricStateCallback, sensorProps, scheduler, handler, - lockoutResetDispatcher, controller, BiometricContext.getInstance(context)); + return new Fingerprint21(context, biometricStateCallback, authenticationStateListeners, + sensorProps, scheduler, handler, lockoutResetDispatcher, controller, + BiometricContext.getInstance(context)); } @Override @@ -719,7 +727,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mSensorProperties.sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason); + mBiometricContext, mUdfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + mSidefpsController, + mAuthenticationStateListeners, enrollReason); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -761,10 +772,14 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext, null /* sensorProps */, - mUdfpsOverlayController, mSidefpsController, + mUdfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + mSidefpsController, + mAuthenticationStateListeners, mContext.getResources().getInteger( com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser), enrollReason); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -880,6 +895,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mBiometricContext, isStrongBiometric, mTaskStackListener, mUdfpsOverlayController, mSidefpsController, + mAuthenticationStateListeners, allowBackgroundAuthentication, mSensorProperties, mHandler, Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker); mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); @@ -898,6 +914,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mBiometricContext, isStrongBiometric, mTaskStackListener, mLockoutTracker, mUdfpsOverlayController, mSidefpsController, + mAuthenticationStateListeners, allowBackgroundAuthentication, mSensorProperties, Utils.getCurrentStrength(mSensorId)); mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); @@ -1123,6 +1140,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mUdfpsOverlayController = controller; } + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Override public void setSidefpsController(@NonNull ISidefpsController controller) { mSidefpsController = controller; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index c1a9370b542325c59d18cb67e09cd11cd8aceb26..88dae6fcc453775fbb8647fa08820cb567c44bfa 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -41,6 +41,7 @@ import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; @@ -248,6 +249,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage public static Fingerprint21UdfpsMock newInstance(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @@ -259,8 +261,9 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher); final MockHalResultController controller = new MockHalResultController(sensorProps.sensorId, context, handler, scheduler); - return new Fingerprint21UdfpsMock(context, biometricStateCallback, sensorProps, scheduler, - handler, lockoutResetDispatcher, controller, biometricContext); + return new Fingerprint21UdfpsMock(context, biometricStateCallback, + authenticationStateListeners, sensorProps, scheduler, handler, + lockoutResetDispatcher, controller, biometricContext); } private static abstract class FakeFingerRunnable implements Runnable { @@ -388,14 +391,15 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage private Fingerprint21UdfpsMock(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull TestableBiometricScheduler scheduler, @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull MockHalResultController controller, @NonNull BiometricContext biometricContext) { - super(context, biometricStateCallback, sensorProps, scheduler, handler, - lockoutResetDispatcher, controller, biometricContext); + super(context, biometricStateCallback, authenticationStateListeners, sensorProps, scheduler, + handler, lockoutResetDispatcher, controller, biometricContext); mScheduler = scheduler; mScheduler.init(this); mHandler = handler; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 9966e910d502ba4c7c6eaf4581ef30e11ab13e99..4c1d4d6d6d124a5527d51cc50764b04b76c2e221 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskStackListener; @@ -40,6 +42,7 @@ import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthenticationClient; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -68,6 +71,7 @@ class FingerprintAuthenticationClient @NonNull private final SensorOverlays mSensorOverlays; @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback; + @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners; private boolean mIsPointerDown; @@ -81,7 +85,9 @@ class FingerprintAuthenticationClient @NonNull TaskStackListener taskStackListener, @NonNull LockoutFrameworkImpl lockoutTracker, @Nullable IUdfpsOverlayController udfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable ISidefpsController sidefpsController, + @NonNull AuthenticationStateListeners authenticationStateListeners, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps, @Authenticators.Types int sensorStrength) { @@ -91,7 +97,12 @@ class FingerprintAuthenticationClient false /* shouldVibrate */, sensorStrength); setRequestId(requestId); mLockoutFrameworkImpl = lockoutTracker; - mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + } + mAuthenticationStateListeners = authenticationStateListeners; mSensorProps = sensorProps; mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */); } @@ -128,6 +139,9 @@ class FingerprintAuthenticationClient mState = STATE_STOPPED; resetFailedAttempts(getTargetUserId()); mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } else { mState = STATE_STARTED_PAUSED_ATTEMPTED; final @LockoutTracker.LockoutMode int lockoutMode = @@ -141,6 +155,9 @@ class FingerprintAuthenticationClient // controlled by the HAL, the framework must stop the sensor before finishing the // client. mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); cancel(); } @@ -156,6 +173,9 @@ class FingerprintAuthenticationClient } mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } private void resetFailedAttempts(int userId) { @@ -205,7 +225,10 @@ class FingerprintAuthenticationClient @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this); + mSensorOverlays.show(getSensorId(), getRequestReason(), this); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason()); + } try { // GroupId was never used. In fact, groupId is always the same as userId. @@ -215,6 +238,9 @@ class FingerprintAuthenticationClient onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } mCallback.onClientFinished(this, false /* success */); } } @@ -222,6 +248,9 @@ class FingerprintAuthenticationClient @Override protected void stopHalOperation() { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } try { getFreshDaemon().cancel(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java index 0d7f9f23e0e110f077d9c1ad2e0bf97cc3ec23a6..6e029d2268e20e6b12ec1063857e5d2838c30d98 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java @@ -16,12 +16,14 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.FingerprintAuthenticateOptions; @@ -71,7 +73,12 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> options.getOpPackageName(), 0 /* cookie */, options.getSensorId(), true /* shouldVibrate */, biometricLogger, biometricContext); setRequestId(requestId); - mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays( + udfpsOverlayController, null /* sideFpsController */); + } mIsStrongBiometric = isStrongBiometric; } @@ -97,7 +104,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, + mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD, this); try { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index 382e7e2121f49206e3d36f5f0bfe657627e41ef4..26332ff6893c3bd6139535dcfbf05eb6a1b8ecd5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; +import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -34,6 +36,7 @@ import android.util.Slog; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -58,22 +61,31 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint @NonNull private final SensorOverlays mSensorOverlays; private final @FingerprintManager.EnrollReason int mEnrollReason; + @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners; private boolean mIsPointerDown; - FingerprintEnrollClient(@NonNull Context context, - @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, - long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, + FingerprintEnrollClient( + @NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, + @NonNull IBinder token, long requestId, + @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId, @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, @Nullable IUdfpsOverlayController udfpsOverlayController, + // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable ISidefpsController sidefpsController, + @NonNull AuthenticationStateListeners authenticationStateListeners, @FingerprintManager.EnrollReason int enrollReason) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger, biometricContext); setRequestId(requestId); - mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + if (sidefpsControllerRefactor()) { + mSensorOverlays = new SensorOverlays(udfpsOverlayController); + } else { + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); + } + mAuthenticationStateListeners = authenticationStateListeners; mEnrollReason = enrollReason; if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { @@ -110,8 +122,12 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint @Override protected void startHalOperation() { - mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), + mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason), this); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStarted( + getRequestReasonFromEnrollReason(mEnrollReason)); + } BiometricNotificationUtils.cancelBadCalibrationNotification(getContext()); try { @@ -122,6 +138,9 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } mCallback.onClientFinished(this, false /* success */); } } @@ -129,6 +148,9 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint @Override protected void stopHalOperation() { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } try { getFreshDaemon().cancel(); @@ -149,6 +171,9 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint if (remaining == 0) { mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } } @@ -170,6 +195,9 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint super.onError(errorCode, vendorCode); mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } } @Override diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index dff14b5fbdd0aae4ffe5829692116a6f715fb57c..6ec6a123a4e75383e99a8f62a335c4828b46f81e 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -327,7 +327,7 @@ public final class DeviceStateManagerService extends SystemService { Optional<DeviceState> getOverrideState() { synchronized (mLock) { if (mActiveOverride.isPresent()) { - return getStateLocked(mActiveOverride.get().getRequestedState()); + return getStateLocked(mActiveOverride.get().getRequestedStateIdentifier()); } return Optional.empty(); } @@ -342,7 +342,7 @@ public final class DeviceStateManagerService extends SystemService { Optional<DeviceState> getOverrideBaseState() { synchronized (mLock) { if (mActiveBaseStateOverride.isPresent()) { - return getStateLocked(mActiveBaseStateOverride.get().getRequestedState()); + return getStateLocked(mActiveBaseStateOverride.get().getRequestedStateIdentifier()); } return Optional.empty(); } @@ -499,6 +499,7 @@ public final class DeviceStateManagerService extends SystemService { * @return {@code true} if the pending state has changed as a result of this call, {@code false} * otherwise. */ + @GuardedBy("mLock") private boolean updatePendingStateLocked() { if (mPendingState.isPresent()) { // Have pending state, can not configure a new state until the state is committed. @@ -507,7 +508,8 @@ public final class DeviceStateManagerService extends SystemService { final DeviceState stateToConfigure; if (mActiveOverride.isPresent()) { - stateToConfigure = getStateLocked(mActiveOverride.get().getRequestedState()).get(); + stateToConfigure = getStateLocked( + mActiveOverride.get().getRequestedStateIdentifier()).get(); } else if (mBaseState.isPresent() && isSupportedStateLocked(mBaseState.get().getIdentifier())) { // Base state could have recently become unsupported after a change in supported states. @@ -599,7 +601,7 @@ public final class DeviceStateManagerService extends SystemService { // requested state is committed. OverrideRequest activeRequest = mActiveOverride.orElse(null); if (activeRequest != null - && activeRequest.getRequestedState() == newState.getIdentifier()) { + && activeRequest.getRequestedStateIdentifier() == newState.getIdentifier()) { ProcessRecord processRecord = mProcessRecords.get(activeRequest.getPid()); if (processRecord != null) { processRecord.notifyRequestActiveAsync(activeRequest.getToken()); @@ -666,21 +668,21 @@ public final class DeviceStateManagerService extends SystemService { case STATUS_ACTIVE: mActiveOverride = Optional.of(request); mDeviceStateNotificationController.showStateActiveNotificationIfNeeded( - request.getRequestedState(), request.getUid()); + request.getRequestedStateIdentifier(), request.getUid()); break; case STATUS_CANCELED: if (mActiveOverride.isPresent() && mActiveOverride.get() == request) { mActiveOverride = Optional.empty(); mDeviceStateNotificationController.cancelNotification( - request.getRequestedState()); + request.getRequestedStateIdentifier()); if ((flags & FLAG_THERMAL_CRITICAL) == FLAG_THERMAL_CRITICAL) { mDeviceStateNotificationController .showThermalCriticalNotificationIfNeeded( - request.getRequestedState()); + request.getRequestedStateIdentifier()); } else if ((flags & FLAG_POWER_SAVE_ENABLED) == FLAG_POWER_SAVE_ENABLED) { mDeviceStateNotificationController .showPowerSaveNotificationIfNeeded( - request.getRequestedState()); + request.getRequestedStateIdentifier()); } } break; @@ -723,7 +725,7 @@ public final class DeviceStateManagerService extends SystemService { */ @GuardedBy("mLock") private void enableBaseStateRequestLocked(OverrideRequest request) { - setBaseState(request.getRequestedState()); + setBaseState(request.getRequestedStateIdentifier()); mActiveBaseStateOverride = Optional.of(request); ProcessRecord processRecord = mProcessRecords.get(request.getPid()); processRecord.notifyRequestActiveAsync(request.getToken()); @@ -762,6 +764,11 @@ public final class DeviceStateManagerService extends SystemService { synchronized (mLock) { mProcessRecords.remove(processRecord.mPid); mOverrideRequestController.handleProcessDied(processRecord.mPid); + + if (shouldCancelOverrideRequestWhenRequesterNotOnTop()) { + OverrideRequest request = mActiveOverride.get(); + mOverrideRequestController.cancelRequest(request); + } } } @@ -787,7 +794,7 @@ public final class DeviceStateManagerService extends SystemService { } OverrideRequest request = new OverrideRequest(token, callingPid, callingUid, - state, flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + deviceState.get(), flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay if (!hasControlDeviceStatePermission && mRearDisplayState != null @@ -848,7 +855,7 @@ public final class DeviceStateManagerService extends SystemService { } OverrideRequest request = new OverrideRequest(token, callingPid, callingUid, - state, flags, OVERRIDE_REQUEST_TYPE_BASE_STATE); + deviceState.get(), flags, OVERRIDE_REQUEST_TYPE_BASE_STATE); mOverrideRequestController.addBaseStateRequest(request); } } @@ -1318,7 +1325,7 @@ public final class DeviceStateManagerService extends SystemService { if (mActiveOverride.isEmpty()) { return false; } - int identifier = mActiveOverride.get().getRequestedState(); + int identifier = mActiveOverride.get().getRequestedStateIdentifier(); DeviceState deviceState = mDeviceStates.get(identifier); return deviceState.hasFlag(DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP); } diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java index 74cf184e826e4221a60b9866f0a5acb4d69e5809..20485c1ac102cb9c48f187ebcb2d9f8af9cf6fe2 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequest.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java @@ -17,6 +17,7 @@ package com.android.server.devicestate; import android.annotation.IntDef; +import android.annotation.NonNull; import android.hardware.devicestate.DeviceStateRequest; import android.os.IBinder; @@ -32,7 +33,8 @@ final class OverrideRequest { private final IBinder mToken; private final int mPid; private final int mUid; - private final int mRequestedState; + @NonNull + private final DeviceState mRequestedState; @DeviceStateRequest.RequestFlags private final int mFlags; @OverrideRequestType @@ -69,7 +71,7 @@ final class OverrideRequest { @Retention(RetentionPolicy.SOURCE) public @interface OverrideRequestType {} - OverrideRequest(IBinder token, int pid, int uid, int requestedState, + OverrideRequest(IBinder token, int pid, int uid, @NonNull DeviceState requestedState, @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) { mToken = token; mPid = pid; @@ -91,10 +93,15 @@ final class OverrideRequest { return mUid; } - int getRequestedState() { + @NonNull + DeviceState getRequestedDeviceState() { return mRequestedState; } + int getRequestedStateIdentifier() { + return mRequestedState.getIdentifier(); + } + @DeviceStateRequest.RequestFlags int getFlags() { return mFlags; diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java index 46f0bc0d980568e50f624358f66665a73055447f..f5f2fa8cabdcc6767b691066240fe1921d91a8ca 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java @@ -204,6 +204,12 @@ final class OverrideRequestController { } if (mRequest != null && mRequest.getPid() == pid) { + if (mRequest.getRequestedDeviceState().hasFlag( + DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)) { + cancelCurrentRequestLocked(); + return; + } + if (mStickyRequestsAllowed) { // Do not cancel the requests now because sticky requests are allowed. These // requests will be cancelled on a call to cancelStickyRequests(). @@ -219,7 +225,7 @@ final class OverrideRequestController { * listener of all changes to request status as a result of this change. */ void handleBaseStateChanged(int state) { - if (mBaseStateRequest != null && state != mBaseStateRequest.getRequestedState()) { + if (mBaseStateRequest != null && state != mBaseStateRequest.getRequestedStateIdentifier()) { cancelBaseStateOverrideRequest(); } if (mRequest == null) { @@ -246,11 +252,12 @@ final class OverrideRequestController { flags |= isThermalCritical ? FLAG_THERMAL_CRITICAL : 0; flags |= isPowerSaveEnabled ? FLAG_POWER_SAVE_ENABLED : 0; if (mBaseStateRequest != null && !contains(newSupportedStates, - mBaseStateRequest.getRequestedState())) { + mBaseStateRequest.getRequestedStateIdentifier())) { cancelCurrentBaseStateRequestLocked(flags); } - if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) { + if (mRequest != null && !contains(newSupportedStates, + mRequest.getRequestedStateIdentifier())) { cancelCurrentRequestLocked(flags); } } @@ -262,7 +269,7 @@ final class OverrideRequestController { pw.println("Override Request active: " + requestActive); if (requestActive) { pw.println("Request: mPid=" + overrideRequest.getPid() - + ", mRequestedState=" + overrideRequest.getRequestedState() + + ", mRequestedState=" + overrideRequest.getRequestedStateIdentifier() + ", mFlags=" + overrideRequest.getFlags() + ", mStatus=" + statusToString(STATUS_ACTIVE)); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index c440a6461de25b7d82085285d0fb5cb48472b5ff..16e043cfb64d54644d92be4c901728ac9890b6a0 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -6499,10 +6499,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub nextEnabledImes).getId(); // Reset enabled IMEs. - settings.putEnabledInputMethodsStr(""); - nextEnabledImes.forEach( - imi -> settings.appendAndPutEnabledInputMethodLocked( - imi.getId())); + final String[] nextEnabledImeIds = new String[nextEnabledImes.size()]; + for (int i = 0; i < nextEnabledImeIds.length; ++i) { + nextEnabledImeIds[i] = nextEnabledImes.get(i).getId(); + } + settings.putEnabledInputMethodsStr(InputMethodUtils.concatEnabledImeIds( + settings.getEnabledInputMethodsStr(), nextEnabledImeIds)); // Reset selected IME. settings.putSelectedInputMethod(nextIme); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 2128356e69c6df3249d8c36b5222ded7f21beb57..773293f8332392438566fe6ad8f186062f5f7cab 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -33,6 +33,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IntArray; import android.util.Pair; import android.util.Printer; @@ -50,6 +51,8 @@ import com.android.server.textservices.TextServicesManagerInternal; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -952,24 +955,64 @@ final class InputMethodUtils { final String enabledInputMethodsStr = TextUtils.nullIfEmpty( SecureSettingsWrapper.getString(Settings.Secure.ENABLED_INPUT_METHODS, null, userId)); - if (enabledInputMethodsStr == null) { - return List.of(); + final ArrayList<String> result = new ArrayList<>(); + splitEnabledImeStr(enabledInputMethodsStr, result::add); + return result; + } + + /** + * Split enabled IME string ({@link Settings.Secure#ENABLED_INPUT_METHODS}) into IME IDs. + * + * @param text a text formatted with {@link Settings.Secure#ENABLED_INPUT_METHODS}. + * @param consumer {@link Consumer} called back when a new IME ID is found. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + static void splitEnabledImeStr(@Nullable String text, @NonNull Consumer<String> consumer) { + if (TextUtils.isEmpty(text)) { + return; } final TextUtils.SimpleStringSplitter inputMethodSplitter = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR); final TextUtils.SimpleStringSplitter subtypeSplitter = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR); - inputMethodSplitter.setString(enabledInputMethodsStr); - final ArrayList<String> result = new ArrayList<>(); + inputMethodSplitter.setString(text); while (inputMethodSplitter.hasNext()) { String nextImsStr = inputMethodSplitter.next(); subtypeSplitter.setString(nextImsStr); if (subtypeSplitter.hasNext()) { // The first element is ime id. - result.add(subtypeSplitter.next()); + consumer.accept(subtypeSplitter.next()); } } - return result; + } + + /** + * Concat given IME IDs with an existing enabled IME + * ({@link Settings.Secure#ENABLED_INPUT_METHODS}). + * + * @param existingEnabledImeId an existing {@link Settings.Secure#ENABLED_INPUT_METHODS} to + * which {@code imeIDs} will be added. + * @param imeIds an array of IME IDs to be added. For IME IDs that are already seen in + * {@code existingEnabledImeId} will be skipped. + * @return a new enabled IME ID string that can be stored in + * {@link Settings.Secure#ENABLED_INPUT_METHODS}. + */ + @NonNull + static String concatEnabledImeIds(@NonNull String existingEnabledImeId, + @NonNull String... imeIds) { + final ArraySet<String> alreadyEnabledIds = new ArraySet<>(); + final StringJoiner joiner = new StringJoiner(Character.toString(INPUT_METHOD_SEPARATOR)); + if (!TextUtils.isEmpty(existingEnabledImeId)) { + splitEnabledImeStr(existingEnabledImeId, alreadyEnabledIds::add); + joiner.add(existingEnabledImeId); + } + for (String id : imeIds) { + if (!alreadyEnabledIds.contains(id)) { + joiner.add(id); + alreadyEnabledIds.add(id); + } + } + return joiner.toString(); } /** diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index b12180bffdcdf0df8cb4bfcf1bed2358a9b5ee23..4b8de4e8c6f196c79d8267620d57e17f2e3d9a0d 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -19,6 +19,7 @@ package com.android.server.notification; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; +import android.service.notification.DeviceEffectsApplier; import java.util.Set; @@ -54,4 +55,22 @@ public interface NotificationManagerInternal { void cleanupHistoryFiles(); void removeBitmaps(); + + /** + * Sets the {@link DeviceEffectsApplier} that will be used to apply the different + * {@link android.service.notification.ZenDeviceEffects} that are relevant for the platform + * when {@link android.service.notification.ZenModeConfig.ZenRule} instances are activated and + * deactivated. + * + * <p>This method is optional and needs only be called if the platform supports non-standard + * effects (i.e. any that are not <em>public APIs</em> in + * {@link android.service.notification.ZenDeviceEffects}, or if they must be applied in a + * non-standard fashion. If not used, a {@link DefaultDeviceEffectsApplier} will be invoked, + * which should be sufficient for most devices. + * + * <p>If this method is called, it <em>must</em> be during system startup and <em>before</em> + * the {@link com.android.server.SystemService#PHASE_THIRD_PARTY_APPS_CAN_START} boot phase. + * Otherwise an {@link IllegalStateException} will be thrown. + */ + void setDeviceEffectsApplier(DeviceEffectsApplier applier); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 66a974080a434a7b18747930a3ae5a80ae47917c..e7ae61072db41da2c5ef6b38da221b5782f4bd6e 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -251,6 +251,7 @@ import android.provider.Settings.Secure; import android.service.notification.Adjustment; import android.service.notification.Condition; import android.service.notification.ConversationChannelWrapper; +import android.service.notification.DeviceEffectsApplier; import android.service.notification.IConditionProvider; import android.service.notification.INotificationListener; import android.service.notification.IStatusBarNotificationHolder; @@ -6964,6 +6965,18 @@ public class NotificationManagerService extends SystemService { } } } + + @Override + public void setDeviceEffectsApplier(DeviceEffectsApplier applier) { + if (!android.app.Flags.modesApi()) { + return; + } + if (mZenModeHelper == null) { + throw new IllegalStateException("ZenModeHelper is not yet ready!"); + } + // This can also throw IllegalStateException if called too late. + mZenModeHelper.setDeviceEffectsApplier(applier); + } }; private static boolean isBigPictureWithBitmapOrIcon(Notification n) { diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index 7b35589ae6824f5fd56405597376a16eae2506e0..7f58e75e0287d05aec4375dd4a0c3af518a65b7e 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -837,13 +837,11 @@ public final class BroadcastHelper { } final String removedPackage = packageRemovedInfo.mRemovedPackage; - final int removedAppId = packageRemovedInfo.mRemovedAppId; - final int uid = packageRemovedInfo.mUid; final String installerPackageName = packageRemovedInfo.mInstallerPackageName; final SparseArray<int[]> broadcastAllowList = packageRemovedInfo.mBroadcastAllowList; Bundle extras = new Bundle(2); - extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid); + extras.putInt(Intent.EXTRA_UID, packageRemovedInfo.mUid); extras.putBoolean(Intent.EXTRA_REPLACING, true); sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, removedPackage, extras, 0, null /*targetPackage*/, null, null, null, broadcastAllowList, null); @@ -888,8 +886,6 @@ public final class BroadcastHelper { boolean removedBySystem, boolean isArchived) { final String removedPackage = packageRemovedInfo.mRemovedPackage; - final int removedAppId = packageRemovedInfo.mRemovedAppId; - final int uid = packageRemovedInfo.mUid; final String installerPackageName = packageRemovedInfo.mInstallerPackageName; final int[] broadcastUserIds = packageRemovedInfo.mBroadcastUsers; final int[] instantUserIds = packageRemovedInfo.mInstantUserIds; @@ -902,8 +898,7 @@ public final class BroadcastHelper { final boolean isStaticSharedLib = packageRemovedInfo.mIsStaticSharedLib; Bundle extras = new Bundle(); - final int removedUid = removedAppId >= 0 ? removedAppId : uid; - extras.putInt(Intent.EXTRA_UID, removedUid); + extras.putInt(Intent.EXTRA_UID, packageRemovedInfo.mUid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, dataRemoved); extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, isRemovedPackageSystemUpdate); extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp); @@ -940,10 +935,10 @@ public final class BroadcastHelper { sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null, null, broadcastUserIds, instantUserIds, broadcastAllowList, null); - packageSender.notifyPackageRemoved(removedPackage, removedUid); + packageSender.notifyPackageRemoved(removedPackage, packageRemovedInfo.mUid); } } - if (removedAppId >= 0) { + if (packageRemovedInfo.mIsAppIdRemoved) { // If a system app's updates are uninstalled the UID is not actually removed. Some // services need to know the package name affected. if (isReplace) { diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index 27f4e11c53adda611b4e80fe99950f7207d6ab1e..482807c397ea7e9a98ea9853eea879d81b3d2c9c 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -518,7 +518,9 @@ public interface Computer extends PackageDataSnapshot { boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId) throws PackageManager.NameNotFoundException; - boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId); + /** Check if the package is suspending any package. */ + boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, + @UserIdInt int suspendingUserId, int targetUserId); @NonNull ParceledListSlice<IntentFilter> getAllIntentFilters(@NonNull String packageName); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index abfd5715810e3c2bdf383d3811e2e7371bd7c021..3cb2420cd22319ced20f9fca3e9a2779fba64275 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -96,6 +96,7 @@ import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; import android.content.pm.UserInfo; +import android.content.pm.UserPackage; import android.content.pm.VersionedPackage; import android.os.Binder; import android.os.Build; @@ -3864,19 +3865,15 @@ public class ComputerEngine implements Computer { } finally { Binder.restoreCallingIdentity(identity); } - - var usingSharedLibraryPair = - getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId); SharedLibraryInfo resLibInfo = new SharedLibraryInfo(libInfo.getPath(), libInfo.getPackageName(), libInfo.getAllCodePaths(), libInfo.getName(), libInfo.getLongVersion(), libInfo.getType(), declaringPackage, - usingSharedLibraryPair.first, (libInfo.getDependencies() == null ? null : new ArrayList<>(libInfo.getDependencies())), - libInfo.isNative()); - + libInfo.isNative(), + getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId)); if (result == null) { result = new ArrayList<>(); } @@ -5012,11 +5009,13 @@ public class ComputerEngine implements Computer { @Override public boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, - @UserIdInt int userId) { + @UserIdInt int suspendingUserId, int targetUserId) { + final UserPackage suspender = UserPackage.of(suspendingUserId, suspendingPackage); for (final PackageStateInternal packageState : getPackageStates().values()) { - final PackageUserStateInternal state = packageState.getUserStateOrDefault(userId); + final PackageUserStateInternal state = + packageState.getUserStateOrDefault(targetUserId); if (state.getSuspendParams() != null - && state.getSuspendParams().containsKey(suspendingPackage)) { + && state.getSuspendParams().containsKey(suspender)) { return true; } } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index dcf921c908851ddf68003d7b51d1ce555ad6dfa0..aa7f0d3c668a1721af5ece2d4cb469a8e7936a4b 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -510,7 +510,10 @@ final class DeletePackageHelper { } if (clearPackageStateAndReturn) { mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, flags); - outInfo.mRemovedAppId = ps.getAppId(); + // Legacy behavior to report appId as UID here. + // The final broadcasts will contain a per-user UID. + outInfo.mUid = ps.getAppId(); + outInfo.mIsAppIdRemoved = true; mPm.scheduleWritePackageRestrictions(user); return; } @@ -555,6 +558,7 @@ final class DeletePackageHelper { boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles, @NonNull PackageRemovedInfo outInfo, boolean writeSettings) { synchronized (mPm.mLock) { + // Since the package is being deleted in all users, report appId as the uid outInfo.mUid = ps.getAppId(); outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList( mPm.snapshotComputer(), ps, allUserHandles, diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 6480d6483062fa183fd92024ed25275df1604e85..0fa7aa5473b2f582b38c778e0e3b0ebf5ddeb8c9 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2912,7 +2912,8 @@ final class InstallPackageHelper { info.mInstallerPackageName = request.getInstallerPackageName(); info.mRemovedUsers = firstUserIds; info.mBroadcastUsers = firstUserIds; - info.mRemovedAppId = request.getAppId(); + info.mUid = request.getAppId(); + info.mIsAppIdRemoved = true; info.mRemovedPackageVersionCode = request.getPkg().getLongVersionCode(); info.mRemovedForAllUsers = true; diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 9a51cc0e26ba366b8ba251d631a8ab47d59ed9e3..ee780d99b6b6ecca4717f4963270e6b65c0d53c5 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -818,7 +818,8 @@ final class InstallRequest { public void setRemovedAppId(int appId) { if (mRemovedInfo != null) { - mRemovedInfo.mRemovedAppId = appId; + mRemovedInfo.mUid = appId; + mRemovedInfo.mIsAppIdRemoved = true; } } diff --git a/services/core/java/com/android/server/pm/KnownPackages.java b/services/core/java/com/android/server/pm/KnownPackages.java index 154709a6209544397523f9530796d3ea5781b5aa..83831ca61aa15d3b92e7bba7ddf1c5435b702888 100644 --- a/services/core/java/com/android/server/pm/KnownPackages.java +++ b/services/core/java/com/android/server/pm/KnownPackages.java @@ -77,6 +77,8 @@ public final class KnownPackages { // Please note the numbers should be continuous. public static final int LAST_KNOWN_PACKAGE = PACKAGE_WEARABLE_SENSING; + static final String SYSTEM_PACKAGE_NAME = "android"; + private final DefaultAppProvider mDefaultAppProvider; private final String mRequiredInstallerPackage; private final String mRequiredUninstallerPackage; @@ -186,7 +188,7 @@ public final class KnownPackages { case PACKAGE_SETUP_WIZARD: return snapshot.filterOnlySystemPackages(mSetupWizardPackage); case PACKAGE_SYSTEM: - return new String[]{"android"}; + return new String[]{SYSTEM_PACKAGE_NAME}; case PACKAGE_VERIFIER: return snapshot.filterOnlySystemPackages(mRequiredVerifierPackages); case PACKAGE_SYSTEM_TEXT_CLASSIFIER: diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index ba66377beb8abd6ee8dc60e0e9a17d49fdefd1fc..127bf495d2ac1752e9eb2c886c06a9ab7ca80159 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -822,7 +822,7 @@ public class LauncherAppsService extends SystemService { return new LauncherActivityInfoInternal( activityInfo, new IncrementalStatesInfo( - false /* isLoading */, 1 /* progress */, 0 /* loadingCompletedTime */), + false /* isLoading */, 0 /* progress */, 0 /* loadingCompletedTime */), user); } @@ -1598,6 +1598,33 @@ public class LauncherAppsService extends SystemService { } } + @Override + public @Nullable IntentSender getAppMarketActivityIntent(@NonNull String callingPackage, + @Nullable String packageName, @NonNull UserHandle user) { + // Only system launchers, which have access to recents should have access to this API. + // TODO(b/303803157): Update access control for this API to default Launcher app. + if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) { + throw new SecurityException("Caller is not the recents app"); + } + if (!canAccessProfile(user.getIdentifier(), + "Can't access AppMarketActivity for another user")) { + return null; + } + final long identity = Binder.clearCallingIdentity(); + try { + // TODO(b/316118005): Add code to launch the app installer for the packageName. + Intent appMarketIntent = new Intent(Intent.ACTION_MAIN); + appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET); + final PendingIntent pi = PendingIntent.getActivityAsUser( + mContext, /* requestCode */ 0, appMarketIntent, PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT, + /* options */ null, user); + return pi == null ? null : pi.getIntentSender(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public void startActivityAsUser(IApplicationThread caller, String callingPackage, String callingFeatureId, ComponentName component, Rect sourceBounds, diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 6b05edf7c25a5e3c6ad2f28020a0801c1581dd8f..2864a8b42445989f7cbe8b976ab927be2b8a7ec4 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -165,7 +165,7 @@ public class PackageArchiver { Computer snapshot = mPm.snapshotComputer(); int userId = userHandle.getIdentifier(); int binderUid = Binder.getCallingUid(); - if (!PackageManagerServiceUtils.isRootOrShell(binderUid)) { + if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) { verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid); } snapshot.enforceCrossUserPermission(binderUid, userId, true, true, diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 7d6dd62153c17e94916caa9096ec78d839eb1c59..2942bbb86e62898e6803c7d66faee7fddb435f90 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -821,6 +821,18 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements params.installFlags &= ~PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK; } + params.installFlags &= ~PackageManager.INSTALL_UNARCHIVE; + if (Flags.archiving() && params.appPackageName != null) { + PackageStateInternal ps = mPm.snapshotComputer().getPackageStateInternal( + params.appPackageName, SYSTEM_UID); + if (ps != null + && PackageArchiver.isArchived(ps.getUserStateOrDefault(userId)) + && PackageArchiver.getResponsibleInstallerPackage(ps) + .equals(requestedInstallerPackageName)) { + params.installFlags |= PackageManager.INSTALL_UNARCHIVE; + } + } + if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0 && !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid) && (snapshot.getFlagsForUid(callingUid) & ApplicationInfo.FLAG_SYSTEM) diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index c737b45ae8857174477107f266de2dacca25df92..8da168375447fe49c40d0c9e283d0453701bcd77 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -19,6 +19,8 @@ package com.android.server.pm; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.RESTRICTION_NONE; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -37,6 +39,7 @@ import android.content.pm.ProcessInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.SuspendDialogInfo; +import android.content.pm.UserPackage; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -250,7 +253,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { getSuspendPackageHelper().removeSuspensionsBySuspendingPackage(snapshot(), new String[]{packageName}, (suspendingPackage) -> !PackageManagerService.PLATFORM_PACKAGE_NAME.equals( - suspendingPackage), + suspendingPackage.packageName), userId); } @@ -269,7 +272,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { @Override @Deprecated - public final String getSuspendingPackage(String suspendedPackage, int userId) { + public final UserPackage getSuspendingPackage(String suspendedPackage, int userId) { return getSuspendPackageHelper().getSuspendingPackage(snapshot(), suspendedPackage, userId, Binder.getCallingUid()); } @@ -277,7 +280,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { @Override @Deprecated public final SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage, - String suspendingPackage, int userId) { + UserPackage suspendingPackage, int userId) { return getSuspendPackageHelper().getSuspendedDialogInfo(snapshot(), suspendedPackage, suspendingPackage, userId, Binder.getCallingUid()); } @@ -683,14 +686,16 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { @Override @Deprecated - public final void unsuspendForSuspendingPackage(final String packageName, int affectedUser) { - mService.unsuspendForSuspendingPackage(snapshot(), packageName, affectedUser); + public final void unsuspendAdminSuspendedPackages(int affectedUser) { + final int suspendingUserId = affectedUser; + mService.unsuspendForSuspendingPackage(snapshot(), PLATFORM_PACKAGE_NAME, suspendingUserId); } @Override @Deprecated - public final boolean isSuspendingAnyPackages(String suspendingPackage, int userId) { - return snapshot().isSuspendingAnyPackages(suspendingPackage, userId); + public final boolean isAdminSuspendingAnyPackages(int userId) { + final int suspendingUserId = userId; + return snapshot().isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, suspendingUserId, userId); } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 56365b676618ef3c715022795d7b8ff7e6241e05..bc441b84c58b461eca7e9c9258dcf6d14fba2ec2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -292,6 +292,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Predicate; /** * Keep track of all those APKs everywhere. @@ -3137,7 +3138,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } private void enforceCanSetPackagesSuspendedAsUser(@NonNull Computer snapshot, - boolean quarantined, String callingPackage, int callingUid, int userId, + boolean quarantined, UserPackage suspender, int callingUid, int targetUserId, String callingMethod) { if (callingUid == Process.ROOT_UID // Need to compare app-id to allow system dialogs access on secondary users @@ -3145,9 +3146,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService return; } - final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId); + final String ownerPackage = + mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(targetUserId); if (ownerPackage != null) { - final int ownerUid = snapshot.getPackageUid(ownerPackage, 0, userId); + final int ownerUid = snapshot.getPackageUid(ownerPackage, 0, targetUserId); if (ownerUid == callingUid) { return; } @@ -3168,25 +3170,27 @@ public class PackageManagerService implements PackageSender, TestUtilityService callingMethod); } - final int packageUid = snapshot.getPackageUid(callingPackage, 0, userId); + final int packageUid = snapshot.getPackageUid(suspender.packageName, 0, targetUserId); final boolean allowedPackageUid = packageUid == callingUid; // TODO(b/139383163): remove special casing for shell and enforce INTERACT_ACROSS_USERS_FULL final boolean allowedShell = callingUid == SHELL_UID && UserHandle.isSameApp(packageUid, callingUid); if (!allowedShell && !allowedPackageUid) { - throw new SecurityException("Calling package " + callingPackage + " in user " - + userId + " does not belong to calling uid " + callingUid); + throw new SecurityException("Suspending package " + suspender.packageName + + " in user " + targetUserId + " does not belong to calling uid " + callingUid); } } void unsuspendForSuspendingPackage(@NonNull Computer computer, String suspendingPackage, - @UserIdInt int userId) { + @UserIdInt int suspendingUserId) { // TODO: This can be replaced by a special parameter to iterate all packages, rather than // this weird pre-collect of all packages. final String[] allPackages = computer.getPackageStates().keySet().toArray(new String[0]); + final Predicate<UserPackage> suspenderPredicate = + UserPackage.of(suspendingUserId, suspendingPackage)::equals; mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer, - allPackages, suspendingPackage::equals, userId); + allPackages, suspenderPredicate, suspendingUserId); } void removeAllDistractingPackageRestrictions(@NonNull Computer snapshot, int userId) { @@ -5259,8 +5263,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService if (!snapshot.isPackageSuspendedForUser(packageName, userId)) { return null; } - return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId, - callingUid); + final UserPackage suspender = mSuspendPackageHelper.getSuspendingPackage( + snapshot, packageName, userId, callingUid); + return suspender != null ? suspender.packageName : null; } catch (PackageManager.NameNotFoundException e) { return null; } @@ -6198,7 +6203,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService @Override public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, PersistableBundle appExtras, PersistableBundle launcherExtras, - SuspendDialogInfo dialogInfo, int flags, String callingPackage, int userId) { + SuspendDialogInfo dialogInfo, int flags, String suspendingPackage, + int suspendingUserId, int targetUserId) { final int callingUid = Binder.getCallingUid(); boolean quarantined = false; if (Flags.quarantinedEnabled()) { @@ -6207,14 +6213,15 @@ public class PackageManagerService implements PackageSender, TestUtilityService } else if (FeatureFlagUtils.isEnabled(mContext, SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) { final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing); - quarantined = callingPackage.equals(wellbeingPkg); + quarantined = suspendingPackage.equals(wellbeingPkg); } } final Computer snapshot = snapshotComputer(); - enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, callingPackage, callingUid, - userId, "setPackagesSuspendedAsUser"); + final UserPackage suspender = UserPackage.of(targetUserId, suspendingPackage); + enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, suspender, callingUid, + targetUserId, "setPackagesSuspendedAsUser"); return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended, - appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid, + appExtras, launcherExtras, dialogInfo, suspender, targetUserId, callingUid, quarantined); } @@ -6653,7 +6660,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService final Computer computer = snapshotComputer(); final String[] allPackages = computer.getAllAvailablePackageNames(); mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer, allPackages, - (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage), + (suspender) -> !PLATFORM_PACKAGE_NAME.equals(suspender.packageName), userId); } @@ -6667,8 +6674,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService @Override public String[] setPackagesSuspendedByAdmin( @UserIdInt int userId, @NonNull String[] packageNames, boolean suspended) { - return mSuspendPackageHelper.setPackagesSuspendedByAdmin( - snapshotComputer(), userId, packageNames, suspended); + final int suspendingUserId = userId; + final UserPackage suspender = UserPackage.of( + suspendingUserId, PackageManagerService.PLATFORM_PACKAGE_NAME); + return mSuspendPackageHelper.setPackagesSuspended(snapshotComputer(), packageNames, + suspended, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, suspender, userId, Process.SYSTEM_UID, + false /* quarantined */); } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 215e9528a35e212690a6d79295050111913a1b35..243fb16b19ae4eeb34e6617597f8334acdd3192e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -2827,7 +2827,7 @@ class PackageManagerShellCommand extends ShellCommand { mInterface.setPackagesSuspendedAsUser(packageNames.toArray(new String[] {}), suspendedState, ((appExtras.size() > 0) ? appExtras : null), ((launcherExtras.size() > 0) ? launcherExtras : null), - info, flags, callingPackage, translatedUserId); + info, flags, callingPackage, UserHandle.USER_SYSTEM, translatedUserId); for (int i = 0; i < packageNames.size(); i++) { final String packageName = packageNames.get(i); pw.println("Package " + packageName + " new suspended state: " @@ -2872,16 +2872,16 @@ class PackageManagerShellCommand extends ShellCommand { UserHandle.USER_NULL, "runGrantRevokePermission")); List<PackageInfo> packageInfos; + PackageManager pm = mContext.createContextAsUser(translatedUser, 0).getPackageManager(); if (pkg == null) { - packageInfos = mContext.getPackageManager().getInstalledPackages( - PackageManager.GET_PERMISSIONS); + packageInfos = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS); } else { try { - packageInfos = Collections.singletonList( - mContext.getPackageManager().getPackageInfo(pkg, - PackageManager.GET_PERMISSIONS)); + packageInfos = Collections.singletonList(pm.getPackageInfo(pkg, + PackageManager.GET_PERMISSIONS)); } catch (NameNotFoundException e) { getErrPrintWriter().println("Error: package not found"); + getOutPrintWriter().println("Failure [package not found]"); return 1; } } diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java index 7ee1772adead35de7700621f50da32eba0ce8516..881b0b398f9a1739243af23ed73ca2b45e3ac2a2 100644 --- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java +++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java @@ -25,7 +25,7 @@ final class PackageRemovedInfo { String mRemovedPackage; String mInstallerPackageName; int mUid = -1; - int mRemovedAppId = -1; + boolean mIsAppIdRemoved = false; int[] mOrigUsers; int[] mRemovedUsers = null; int[] mBroadcastUsers = null; diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 7d0a1f6afe1dbf1804adbdc306017c5f45460eea..28a90f3d6ab6584c950f50ffd342678b3e9d3a57 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -32,6 +32,7 @@ import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; import android.content.pm.UserInfo; +import android.content.pm.UserPackage; import android.content.pm.overlay.OverlayPaths; import android.os.UserHandle; import android.os.incremental.IncrementalManager; @@ -952,7 +953,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal void setUserState(int userId, long ceDataInode, long deDataInode, int enabled, boolean installed, boolean stopped, boolean notLaunched, boolean hidden, - int distractionFlags, ArrayMap<String, SuspendParams> suspendParams, + int distractionFlags, ArrayMap<UserPackage, SuspendParams> suspendParams, boolean instantApp, boolean virtualPreload, String lastDisableAppCaller, ArraySet<String> enabledComponents, ArraySet<String> disabledComponents, int installReason, int uninstallReason, @@ -1182,7 +1183,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal if (state.isSuspended()) { for (int j = 0; j < state.getSuspendParams().size(); j++) { proto.write(PackageProto.UserInfoProto.SUSPENDING_PACKAGE, - state.getSuspendParams().keyAt(j)); + state.getSuspendParams().keyAt(j).packageName); } } proto.write(PackageProto.UserInfoProto.IS_STOPPED, state.isStopped()); diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 981f24bc179a1c12922b5fa02febd345a2e87c75..b6de0e5c030f0db95c8f6628fca2376e8d48066a 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -20,15 +20,19 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIB import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY; +import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX; import static com.android.server.pm.PackageManagerService.SCAN_BOOTING; import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP; +import static com.android.server.pm.PackageManagerService.TAG; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; +import android.os.Build; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; +import android.util.Slog; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; @@ -49,6 +53,8 @@ import java.util.Map; * as install) led to the request. */ final class ReconcilePackageUtils { + private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE || true; + public static List<ReconciledPackage> reconcilePackages( List<InstallRequest> installRequests, Map<String, AndroidPackage> allPackages, @@ -90,6 +96,8 @@ final class ReconcilePackageUtils { } } + final AndroidPackage systemPackage = allPackages.get(KnownPackages.SYSTEM_PACKAGE_NAME); + for (InstallRequest installRequest : installRequests) { final String installPackageName = installRequest.getParsedPackage().getPackageName(); final List<SharedLibraryInfo> allowedSharedLibInfos = @@ -133,6 +141,9 @@ final class ReconcilePackageUtils { if (parsedPackage != null) { signingDetails = parsedPackage.getSigningDetails(); } + final boolean isSystemPackage = + ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0); + final boolean isApex = (scanFlags & SCAN_AS_APEX) != 0; SharedUserSetting sharedUserSetting = settings.getSharedUserSettingLPr( signatureCheckPs); if (ksms.shouldCheckUpgradeKeySetLocked( @@ -141,7 +152,7 @@ final class ReconcilePackageUtils { // We just determined the app is signed correctly, so bring // over the latest parsed certs. } else { - if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { + if (!isSystemPackage) { throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + parsedPackage.getPackageName() + " upgrade keys do not match the previously installed" @@ -168,9 +179,23 @@ final class ReconcilePackageUtils { removeAppKeySetData = true; } + if (!isSystemPackage && !isApex && signingDetails != null + && systemPackage != null && systemPackage.getSigningDetails() != null + && systemPackage.getSigningDetails().checkCapability( + signingDetails, + SigningDetails.CertCapabilities.PERMISSION)) { + Slog.d(TAG, "Non-preload app associated with system signature: " + + signatureCheckPs.getPackageName()); + if (!ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE) { + throw new ReconcileFailure( + INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, + "Non-preload app associated with system signature: " + + signatureCheckPs.getPackageName()); + } + } + // if this is is a sharedUser, check to see if the new package is signed by a - // newer - // signing certificate than the existing one, and if so, copy over the new + // newer signing certificate than the existing one, and if so, copy over the new // details if (sharedUserSetting != null) { // Attempt to merge the existing lineage for the shared SigningDetails with @@ -203,7 +228,7 @@ final class ReconcilePackageUtils { } } } catch (PackageManagerException e) { - if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { + if (!isSystemPackage) { throw new ReconcileFailure(e); } signingDetails = parsedPackage.getSigningDetails(); diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 8ff4a6dd9440febbd3e85c37ee54dafbdc83ee35..fefab3ba15b968a6339d93f0a321daaf6a7fb166 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -347,8 +347,7 @@ final class RemovePackageHelper { mPm.mAppsFilter.removePackage(snapshot, snapshot.getPackageStateInternal(packageName)); removedAppId = mPm.mSettings.removePackageLPw(packageName); - outInfo.mRemovedAppId = removedAppId; - + outInfo.mIsAppIdRemoved = true; if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) { // If we don't have a disabled system package to reinstall, the package is // really gone and its permission state should be removed. diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index cfbaae3d0f30616bf734d9c024f4dd9b7dbf7306..d6952262d82ef3b642eab00382cc0ef225d61cb6 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -51,6 +51,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.Signature; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; +import android.content.pm.UserPackage; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.overlay.OverlayPaths; import android.net.Uri; @@ -1956,7 +1957,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile ArchiveState archiveState = null; int packageDepth = parser.getDepth(); - ArrayMap<String, SuspendParams> suspendParamsMap = null; + ArrayMap<UserPackage, SuspendParams> suspendParamsMap = null; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > packageDepth)) { @@ -1983,18 +1984,15 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile parser); break; case TAG_SUSPEND_PARAMS: - final String suspendingPackage = parser.getAttributeValue(null, - ATTR_SUSPENDING_PACKAGE); - if (suspendingPackage == null) { - Slog.wtf(TAG, "No suspendingPackage found inside tag " - + TAG_SUSPEND_PARAMS); + Map.Entry<UserPackage, SuspendParams> entry = + readSuspensionParamsLPr(userId, parser); + if (entry == null) { continue; } if (suspendParamsMap == null) { suspendParamsMap = new ArrayMap<>(); } - suspendParamsMap.put(suspendingPackage, - SuspendParams.restoreFromXml(parser)); + suspendParamsMap.put(entry.getKey(), entry.getValue()); break; case TAG_ARCHIVE_STATE: archiveState = parseArchiveState(parser); @@ -2016,7 +2014,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile oldSuspendedLauncherExtras, false /* quarantined */); suspendParamsMap = new ArrayMap<>(); - suspendParamsMap.put(oldSuspendingPackage, suspendParams); + suspendParamsMap.put( + UserPackage.of(userId, oldSuspendingPackage), suspendParams); } if (blockUninstall) { @@ -2058,6 +2057,20 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } } + @Nullable + private static Map.Entry<UserPackage, SuspendParams> readSuspensionParamsLPr( + int userId, TypedXmlPullParser parser) throws IOException { + final String suspendingPackage = parser.getAttributeValue(null, ATTR_SUSPENDING_PACKAGE); + if (suspendingPackage == null) { + Slog.wtf(TAG, "No suspendingPackage found inside tag " + TAG_SUSPEND_PARAMS); + return null; + } + final int suspendingUserId = userId; + return Map.entry( + UserPackage.of(suspendingUserId, suspendingPackage), + SuspendParams.restoreFromXml(parser)); + } + private static ArchiveState parseArchiveState(TypedXmlPullParser parser) throws XmlPullParserException, IOException { String installerTitle = parser.getAttributeValue(null, @@ -2414,10 +2427,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } if (ustate.isSuspended()) { for (int i = 0; i < ustate.getSuspendParams().size(); i++) { - final String suspendingPackage = ustate.getSuspendParams().keyAt(i); + final UserPackage suspendingPackage = + ustate.getSuspendParams().keyAt(i); serializer.startTag(null, TAG_SUSPEND_PARAMS); serializer.attribute(null, ATTR_SUSPENDING_PACKAGE, - suspendingPackage); + suspendingPackage.packageName); final SuspendParams params = ustate.getSuspendParams().valueAt(i); if (params != null) { diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index c2a960a9539436dd0b484f9f78e339c5e0c43548..4e70cc52ef90ee298da4fac19d34d24b34735bef 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -27,10 +27,10 @@ import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.content.Intent; import android.content.pm.SuspendDialogInfo; +import android.content.pm.UserPackage; import android.os.Binder; import android.os.Bundle; import android.os.PersistableBundle; -import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.util.ArrayMap; @@ -88,8 +88,8 @@ public final class SuspendPackageHelper { * @param dialogInfo An optional {@link SuspendDialogInfo} object describing the dialog that * should be shown to the user when they try to launch a suspended app. * Ignored if {@code suspended} is false. - * @param callingPackage The caller's package name. - * @param userId The user where packages reside. + * @param suspendingPackage The caller's package name. + * @param targetUserId The user where packages reside. * @param callingUid The caller's uid. * @return The names of failed packages. */ @@ -97,14 +97,14 @@ public final class SuspendPackageHelper { String[] setPackagesSuspended(@NonNull Computer snapshot, @Nullable String[] packageNames, boolean suspended, @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras, @Nullable SuspendDialogInfo dialogInfo, - @NonNull String callingPackage, @UserIdInt int userId, int callingUid, + @NonNull UserPackage suspendingPackage, @UserIdInt int targetUserId, int callingUid, boolean quarantined) { if (ArrayUtils.isEmpty(packageNames)) { return packageNames; } - if (suspended && !quarantined && !isSuspendAllowedForUser(snapshot, userId, - callingUid)) { - Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId); + if (suspended && !quarantined + && !isSuspendAllowedForUser(snapshot, targetUserId, callingUid)) { + Slog.w(TAG, "Cannot suspend due to restrictions on user " + targetUserId); return packageNames; } @@ -119,19 +119,21 @@ public final class SuspendPackageHelper { final IntArray changedUids = new IntArray(packageNames.length); final boolean[] canSuspend = suspended - ? canSuspendPackageForUser(snapshot, packageNames, userId, callingUid) + ? canSuspendPackageForUser(snapshot, packageNames, targetUserId, callingUid) : null; for (int i = 0; i < packageNames.length; i++) { final String packageName = packageNames[i]; - if (callingPackage.equals(packageName)) { - Slog.w(TAG, "Calling package: " + callingPackage + " trying to " + if (suspendingPackage.packageName.equals(packageName) + && suspendingPackage.userId == targetUserId) { + Slog.w(TAG, "Suspending package: " + suspendingPackage + " trying to " + (suspended ? "" : "un") + "suspend itself. Ignoring"); unmodifiablePackages.add(packageName); continue; } final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName); - if (packageState == null || !packageState.getUserStateOrDefault(userId).isInstalled() - || snapshot.shouldFilterApplication(packageState, callingUid, userId)) { + if (packageState == null + || !packageState.getUserStateOrDefault(targetUserId).isInstalled() + || snapshot.shouldFilterApplication(packageState, callingUid, targetUserId)) { Slog.w(TAG, "Could not find package setting for package: " + packageName + ". Skipping suspending/un-suspending."); unmodifiablePackages.add(packageName); @@ -142,34 +144,34 @@ public final class SuspendPackageHelper { continue; } - final WatchedArrayMap<String, SuspendParams> suspendParamsMap = - packageState.getUserStateOrDefault(userId).getSuspendParams(); + final WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap = + packageState.getUserStateOrDefault(targetUserId).getSuspendParams(); final SuspendParams oldSuspendParams = suspendParamsMap == null - ? null : suspendParamsMap.get(callingPackage); + ? null : suspendParamsMap.get(suspendingPackage); boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams); if (suspended && !changed) { // Carried over API behavior, must notify change even if no change notifyPackagesList.add(packageName); notifyUids.add( - UserHandle.getUid(userId, packageState.getAppId())); + UserHandle.getUid(targetUserId, packageState.getAppId())); continue; } - // If only the callingPackage is suspending this package, + // If only the suspendingPackage is suspending this package, // it will be unsuspended when this change is committed boolean packageUnsuspended = !suspended && CollectionUtils.size(suspendParamsMap) == 1 - && suspendParamsMap.containsKey(callingPackage); + && suspendParamsMap.containsKey(suspendingPackage); if (suspended || packageUnsuspended) { // Always notify of a suspend call + notify when fully unsuspended notifyPackagesList.add(packageName); - notifyUids.add(UserHandle.getUid(userId, packageState.getAppId())); + notifyUids.add(UserHandle.getUid(targetUserId, packageState.getAppId())); } if (changed) { changedPackagesList.add(packageName); - changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); + changedUids.add(UserHandle.getUid(targetUserId, packageState.getAppId())); } else { Slog.w(TAG, "No change is needed for package: " + packageName + ". Skipping suspending/un-suspending."); @@ -181,11 +183,11 @@ public final class SuspendPackageHelper { for (int index = 0; index < size; index++) { final String packageName = changedPackagesList.valueAt(index); final PackageUserStateWrite userState = mutator.forPackage(packageName) - .userState(userId); + .userState(targetUserId); if (suspended) { - userState.putSuspendParams(callingPackage, newSuspendParams); + userState.putSuspendParams(suspendingPackage, newSuspendParams); } else { - userState.removeSuspension(callingPackage); + userState.removeSuspension(suspendingPackage); } } }); @@ -197,17 +199,17 @@ public final class SuspendPackageHelper { mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot, suspended ? Intent.ACTION_PACKAGES_SUSPENDED : Intent.ACTION_PACKAGES_UNSUSPENDED, - changedPackages, notifyUids.toArray(), quarantined, userId); + changedPackages, notifyUids.toArray(), quarantined, targetUserId); mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, changedPackages, - suspended, userId); - mPm.scheduleWritePackageRestrictions(userId); + suspended, targetUserId); + mPm.scheduleWritePackageRestrictions(targetUserId); } // Send the suspension changed broadcast to ensure suspension state is not stale. if (!changedPackagesList.isEmpty()) { mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, changedPackagesList.toArray(new String[0]), changedUids.toArray(), quarantined, - userId); + targetUserId); } return unmodifiablePackages.toArray(new String[0]); } @@ -216,19 +218,19 @@ public final class SuspendPackageHelper { * Returns the names in the {@code packageNames} which can not be suspended by the caller. * * @param packageNames The names of packages to check. - * @param userId The user where packages reside. + * @param targetUserId The user where packages reside. * @param callingUid The caller's uid. * @return The names of packages which are Unsuspendable. */ @NonNull String[] getUnsuspendablePackagesForUser(@NonNull Computer snapshot, - @NonNull String[] packageNames, @UserIdInt int userId, int callingUid) { - if (!isSuspendAllowedForUser(snapshot, userId, callingUid)) { - Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId); + @NonNull String[] packageNames, @UserIdInt int targetUserId, int callingUid) { + if (!isSuspendAllowedForUser(snapshot, targetUserId, callingUid)) { + Slog.w(TAG, "Cannot suspend due to restrictions on user " + targetUserId); return packageNames; } final ArraySet<String> unactionablePackages = new ArraySet<>(); - final boolean[] canSuspend = canSuspendPackageForUser(snapshot, packageNames, userId, + final boolean[] canSuspend = canSuspendPackageForUser(snapshot, packageNames, targetUserId, callingUid); for (int i = 0; i < packageNames.length; i++) { if (!canSuspend[i]) { @@ -237,7 +239,7 @@ public final class SuspendPackageHelper { } final PackageStateInternal packageState = snapshot.getPackageStateForInstalledAndFiltered( - packageNames[i], callingUid, userId); + packageNames[i], callingUid, targetUserId); if (packageState == null) { Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]); unactionablePackages.add(packageNames[i]); @@ -285,30 +287,31 @@ public final class SuspendPackageHelper { * @param packagesToChange The packages on which the suspension are to be removed. * @param suspendingPackagePredicate A predicate identifying the suspending packages whose * suspensions will be removed. - * @param userId The user for which the changes are taking place. + * @param targetUserId The user for which the changes are taking place. */ void removeSuspensionsBySuspendingPackage(@NonNull Computer snapshot, @NonNull String[] packagesToChange, - @NonNull Predicate<String> suspendingPackagePredicate, int userId) { + @NonNull Predicate<UserPackage> suspendingPackagePredicate, int targetUserId) { final List<String> unsuspendedPackages = new ArrayList<>(); final IntArray unsuspendedUids = new IntArray(); - final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>(); + final ArrayMap<String, ArraySet<UserPackage>> pkgToSuspendingPkgsToCommit = + new ArrayMap<>(); for (String packageName : packagesToChange) { final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName); final PackageUserStateInternal packageUserState = packageState == null - ? null : packageState.getUserStateOrDefault(userId); + ? null : packageState.getUserStateOrDefault(targetUserId); if (packageUserState == null || !packageUserState.isSuspended()) { continue; } - WatchedArrayMap<String, SuspendParams> suspendParamsMap = + WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap = packageUserState.getSuspendParams(); int countRemoved = 0; for (int index = 0; index < suspendParamsMap.size(); index++) { - String suspendingPackage = suspendParamsMap.keyAt(index); + UserPackage suspendingPackage = suspendParamsMap.keyAt(index); if (suspendingPackagePredicate.test(suspendingPackage)) { - ArraySet<String> suspendingPkgsToCommit = + ArraySet<UserPackage> suspendingPkgsToCommit = pkgToSuspendingPkgsToCommit.get(packageName); if (suspendingPkgsToCommit == null) { suspendingPkgsToCommit = new ArraySet<>(); @@ -322,31 +325,33 @@ public final class SuspendPackageHelper { // Everything would be removed and package unsuspended if (countRemoved == suspendParamsMap.size()) { unsuspendedPackages.add(packageState.getPackageName()); - unsuspendedUids.add(UserHandle.getUid(userId, packageState.getAppId())); + unsuspendedUids.add(UserHandle.getUid(targetUserId, packageState.getAppId())); } } mPm.commitPackageStateMutation(null, mutator -> { for (int mapIndex = 0; mapIndex < pkgToSuspendingPkgsToCommit.size(); mapIndex++) { String packageName = pkgToSuspendingPkgsToCommit.keyAt(mapIndex); - ArraySet<String> packagesToRemove = pkgToSuspendingPkgsToCommit.valueAt(mapIndex); - PackageUserStateWrite userState = mutator.forPackage(packageName).userState(userId); + ArraySet<UserPackage> packagesToRemove = + pkgToSuspendingPkgsToCommit.valueAt(mapIndex); + PackageUserStateWrite userState = + mutator.forPackage(packageName).userState(targetUserId); for (int setIndex = 0; setIndex < packagesToRemove.size(); setIndex++) { userState.removeSuspension(packagesToRemove.valueAt(setIndex)); } } }); - mPm.scheduleWritePackageRestrictions(userId); + mPm.scheduleWritePackageRestrictions(targetUserId); final Computer newSnapshot = mPm.snapshotComputer(); if (!unsuspendedPackages.isEmpty()) { final String[] packageArray = unsuspendedPackages.toArray( new String[unsuspendedPackages.size()]); mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, packageArray, - false, userId); + false, targetUserId); mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_UNSUSPENDED, - packageArray, unsuspendedUids.toArray(), false, userId); + packageArray, unsuspendedUids.toArray(), false, targetUserId); } } @@ -404,7 +409,7 @@ public final class SuspendPackageHelper { * @return The name of suspending package. */ @Nullable - String getSuspendingPackage(@NonNull Computer snapshot, @NonNull String suspendedPackage, + UserPackage getSuspendingPackage(@NonNull Computer snapshot, @NonNull String suspendedPackage, int userId, int callingUid) { final PackageStateInternal packageState = snapshot.getPackageStateInternal( suspendedPackage, callingUid); @@ -417,13 +422,13 @@ public final class SuspendPackageHelper { return null; } - String suspendingPackage = null; - String suspendedBySystem = null; - String qasPackage = null; + UserPackage suspendingPackage = null; + UserPackage suspendedBySystem = null; + UserPackage qasPackage = null; for (int i = 0; i < userState.getSuspendParams().size(); i++) { suspendingPackage = userState.getSuspendParams().keyAt(i); var suspendParams = userState.getSuspendParams().valueAt(i); - if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) { + if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) { suspendedBySystem = suspendingPackage; } if (suspendParams.isQuarantined() && qasPackage == null) { @@ -451,7 +456,7 @@ public final class SuspendPackageHelper { */ @Nullable SuspendDialogInfo getSuspendedDialogInfo(@NonNull Computer snapshot, - @NonNull String suspendedPackage, @NonNull String suspendingPackage, int userId, + @NonNull String suspendedPackage, @NonNull UserPackage suspendingPackage, int userId, int callingUid) { final PackageStateInternal packageState = snapshot.getPackageStateInternal( suspendedPackage, callingUid); @@ -464,7 +469,7 @@ public final class SuspendPackageHelper { return null; } - final WatchedArrayMap<String, SuspendParams> suspendParamsMap = + final WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap = userState.getSuspendParams(); if (suspendParamsMap == null) { return null; @@ -493,34 +498,36 @@ public final class SuspendPackageHelper { * be suspended or not. * * @param packageNames The package names to check suspendability for. - * @param userId The user to check in + * @param targetUserId The user to check in * @param callingUid The caller's uid. * @return An array containing results of the checks */ @NonNull boolean[] canSuspendPackageForUser(@NonNull Computer snapshot, @NonNull String[] packageNames, - int userId, int callingUid) { + int targetUserId, int callingUid) { final boolean[] canSuspend = new boolean[packageNames.length]; - final boolean isCallerOwner = isCallerDeviceOrProfileOwner(snapshot, userId, callingUid); + final boolean isCallerOwner = + isCallerDeviceOrProfileOwner(snapshot, targetUserId, callingUid); final long token = Binder.clearCallingIdentity(); try { final DefaultAppProvider defaultAppProvider = mInjector.getDefaultAppProvider(); - final String activeLauncherPackageName = defaultAppProvider.getDefaultHome(userId); - final String dialerPackageName = defaultAppProvider.getDefaultDialer(userId); + final String activeLauncherPackageName = + defaultAppProvider.getDefaultHome(targetUserId); + final String dialerPackageName = defaultAppProvider.getDefaultDialer(targetUserId); final String requiredInstallerPackage = - getKnownPackageName(snapshot, KnownPackages.PACKAGE_INSTALLER, userId); + getKnownPackageName(snapshot, KnownPackages.PACKAGE_INSTALLER, targetUserId); final String requiredUninstallerPackage = - getKnownPackageName(snapshot, KnownPackages.PACKAGE_UNINSTALLER, userId); + getKnownPackageName(snapshot, KnownPackages.PACKAGE_UNINSTALLER, targetUserId); final String requiredVerifierPackage = - getKnownPackageName(snapshot, KnownPackages.PACKAGE_VERIFIER, userId); + getKnownPackageName(snapshot, KnownPackages.PACKAGE_VERIFIER, targetUserId); final String requiredPermissionControllerPackage = getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER, - userId); + targetUserId); for (int i = 0; i < packageNames.length; i++) { canSuspend[i] = false; final String packageName = packageNames[i]; - if (mPm.isPackageDeviceAdmin(packageName, userId)) { + if (mPm.isPackageDeviceAdmin(packageName, targetUserId)) { Slog.w(TAG, "Cannot suspend package \"" + packageName + "\": has an active device admin"); continue; @@ -555,12 +562,12 @@ public final class SuspendPackageHelper { + "\": required for permissions management"); continue; } - if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { + if (mProtectedPackages.isPackageStateProtected(targetUserId, packageName)) { Slog.w(TAG, "Cannot suspend package \"" + packageName + "\": protected package"); continue; } - if (!isCallerOwner && snapshot.getBlockUninstall(userId, packageName)) { + if (!isCallerOwner && snapshot.getBlockUninstall(targetUserId, packageName)) { Slog.w(TAG, "Cannot suspend package \"" + packageName + "\": blocked by admin"); continue; @@ -572,7 +579,7 @@ public final class SuspendPackageHelper { PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName); AndroidPackage pkg = packageState == null ? null : packageState.getPkg(); if (pkg != null) { - final int uid = UserHandle.getUid(userId, packageState.getAppId()); + final int uid = UserHandle.getUid(targetUserId, packageState.getAppId()); // Cannot suspend SDK libs as they are controlled by SDK manager. if (pkg.isSdkLibrary()) { Slog.w(TAG, "Cannot suspend package: " + packageName @@ -614,20 +621,6 @@ public final class SuspendPackageHelper { == AppOpsManager.MODE_ALLOWED; } - /** - * Suspends packages on behalf of an admin. - * - * @return array of packages that are unsuspendable, either because admin is not allowed to - * suspend them (e.g. current dialer) or there was other problem (e.g. package not found). - */ - public String[] setPackagesSuspendedByAdmin( - Computer snapshot, int userId, String[] packageNames, boolean suspend) { - return setPackagesSuspended(snapshot, packageNames, suspend, - null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */, - PackageManagerService.PLATFORM_PACKAGE_NAME, userId, Process.SYSTEM_UID, - false /* quarantined */); - } - private String getKnownPackageName(@NonNull Computer snapshot, @KnownPackages.KnownPackage int knownPackage, int userId) { final String[] knownPackages = @@ -635,14 +628,15 @@ public final class SuspendPackageHelper { return knownPackages.length > 0 ? knownPackages[0] : null; } - private boolean isCallerDeviceOrProfileOwner(@NonNull Computer snapshot, int userId, + private boolean isCallerDeviceOrProfileOwner(@NonNull Computer snapshot, int targetUserId, int callingUid) { if (callingUid == SYSTEM_UID) { return true; } - final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId); + final String ownerPackage = + mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(targetUserId); if (ownerPackage != null) { - return callingUid == snapshot.getPackageUidInternal(ownerPackage, 0, userId, + return callingUid == snapshot.getPackageUidInternal(ownerPackage, 0, targetUserId, callingUid); } return false; diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 7386301bdd6e60fee68d569faf2253be9fa4e728..14db70e5f72e729ab4cf741c4a9027a809be9c64 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -306,7 +306,6 @@ public final class UserTypeFactory { .setDarkThemeBadgeColors( R.color.white) .setDefaultRestrictions(getDefaultProfileRestrictions()) - .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings()) .setDefaultUserProperties(new UserProperties.Builder() .setStartWithParent(true) .setCredentialShareableWithParent(true) diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index fa54f0ba18cd0a6f652ded6458e382a5a1b832ef..d0fe9647618a9dfce381cea093144003ad3ce1ae 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -28,6 +28,7 @@ import android.content.pm.ConfigurationInfo; import android.content.pm.FallbackCategoryProvider; import android.content.pm.FeatureGroupInfo; import android.content.pm.FeatureInfo; +import android.content.pm.Flags; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; @@ -474,7 +475,34 @@ public class PackageInfoUtils { } info.sharedLibraryFiles = usesLibraryFiles.isEmpty() ? null : usesLibraryFiles.toArray(new String[0]); - info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos; + + + if (!Flags.sdkLibIndependence()) { + info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos; + info.optionalSharedLibraryInfos = null; + } else { + // sharedLibraryInfos contains all shared libraries that the app depends on (including + // the optional sdk libraries) + info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos; + String[] libsNames = pkgSetting.getUsesSdkLibraries(); + boolean[] libsOptional = pkgSetting.getUsesSdkLibrariesOptional(); + List<SharedLibraryInfo> optionalSdkLibraries = null; + if (!ArrayUtils.isEmpty(libsOptional) && !ArrayUtils.isEmpty(libsNames) + && libsNames.length == libsOptional.length) { + for (SharedLibraryInfo info1 : usesLibraryInfos) { + if (info1.getType() == SharedLibraryInfo.TYPE_SDK_PACKAGE) { + int index = ArrayUtils.indexOf(libsNames, info1.getName()); + if (index >= 0 && libsOptional[index]) { + if (optionalSdkLibraries == null) { + optionalSdkLibraries = new ArrayList<>(); + } + optionalSdkLibraries.add(info1); + } + } + } + } + info.optionalSharedLibraryInfos = optionalSdkLibraries; + } if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) { info.category = pkgSetting.getCategoryOverride(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java index 2f4ad2d8fcc62ee17536482901f21d6ce384494a..15b693cf72f8c294eb4b59d193293fd8e90b9b6a 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager; +import android.content.pm.UserPackage; import android.content.pm.overlay.OverlayPaths; import android.util.ArraySet; import android.util.Pair; @@ -173,7 +174,7 @@ class PackageUserStateDefault implements PackageUserStateInternal { @Nullable @Override - public WatchedArrayMap<String, SuspendParams> getSuspendParams() { + public WatchedArrayMap<UserPackage, SuspendParams> getSuspendParams() { return null; } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index c5ef5257ddd5405f4deee14da3e63c75e898a81f..7a5a14d8d3c21f81af26893d9561531e2db290ab 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager; +import android.content.pm.UserPackage; import android.content.pm.overlay.OverlayPaths; import android.text.TextUtils; import android.util.ArrayMap; @@ -121,7 +122,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt * Suspending package to suspend params */ @Nullable - private WatchedArrayMap<String, SuspendParams> mSuspendParams; + private WatchedArrayMap<UserPackage, SuspendParams> mSuspendParams; @Nullable private WatchedArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap; @@ -369,7 +370,10 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt return !CollectionUtils.isEmpty(mSuspendParams); } - public PackageUserStateImpl putSuspendParams(@NonNull String suspendingPackage, + /** + * Adds or updates suspension params by the given package. + */ + public PackageUserStateImpl putSuspendParams(@NonNull UserPackage suspendingPackage, @Nullable SuspendParams suspendParams) { if (mSuspendParams == null) { mSuspendParams = new WatchedArrayMap<>(); @@ -384,7 +388,10 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt return this; } - public PackageUserStateImpl removeSuspension(@NonNull String suspendingPackage) { + /** + * Removes suspension by the given package. + */ + public PackageUserStateImpl removeSuspension(@NonNull UserPackage suspendingPackage) { if (mSuspendParams != null) { mSuspendParams.remove(suspendingPackage); onChanged(); @@ -565,7 +572,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt * Suspending package to suspend params */ public @NonNull PackageUserStateImpl setSuspendParams( - @NonNull ArrayMap<String, SuspendParams> value) { + @NonNull ArrayMap<UserPackage, SuspendParams> value) { if (value == null) { return this; } @@ -778,7 +785,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt * Suspending package to suspend params */ @DataClass.Generated.Member - public @Nullable WatchedArrayMap<String,SuspendParams> getSuspendParams() { + public @Nullable WatchedArrayMap<UserPackage,SuspendParams> getSuspendParams() { return mSuspendParams; } @@ -830,7 +837,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt * Suspending package to suspend params */ @DataClass.Generated.Member - public @NonNull PackageUserStateImpl setSuspendParams(@NonNull WatchedArrayMap<String,SuspendParams> value) { + public @NonNull PackageUserStateImpl setSuspendParams(@NonNull WatchedArrayMap<UserPackage,SuspendParams> value) { mSuspendParams = value; return this; } @@ -909,10 +916,10 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated( - time = 1701470095849L, + time = 1701864813354L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java", - inputSignatures = "private int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate long mDeDataInode\nprivate int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final int INSTALLED\nprivate static final int STOPPED\nprivate static final int NOT_LAUNCHED\nprivate static final int HIDDEN\nprivate static final int INSTANT_APP\nprivate static final int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") + inputSignatures = "private int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate long mDeDataInode\nprivate int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(android.content.pm.UserPackage)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final int INSTALLED\nprivate static final int STOPPED\nprivate static final int NOT_LAUNCHED\nprivate static final int HIDDEN\nprivate static final int INSTANT_APP\nprivate static final int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java index 46cc830130efcde5ddcd01bd452ae45f52bd8bbe..f8d745cb7fbfedf5e9f8e7d379d673374dbefe22 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java @@ -19,6 +19,7 @@ package com.android.server.pm.pkg; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; +import android.content.pm.UserPackage; import android.content.pm.pkg.FrameworkPackageUserState; import android.util.Pair; @@ -38,7 +39,7 @@ public interface PackageUserStateInternal extends PackageUserState, FrameworkPac // TODO: Make non-null with emptyMap() @Nullable - WatchedArrayMap<String, SuspendParams> getSuspendParams(); + WatchedArrayMap<UserPackage, SuspendParams> getSuspendParams(); @Nullable WatchedArraySet<String> getDisabledComponentsNoCopy(); diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java index 8430cf7a0d11f1a9b059cb7b3a5e3e9267bfebcf..253eb400612289d772f5a343bb32c5528efd4b31 100644 --- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java +++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.UserPackage; import android.content.pm.overlay.OverlayPaths; import android.util.ArraySet; @@ -349,7 +350,7 @@ public class PackageStateMutator { @NonNull @Override - public PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage, + public PackageUserStateWrite putSuspendParams(@NonNull UserPackage suspendingPackage, @Nullable SuspendParams suspendParams) { if (mUserState != null) { mUserState.putSuspendParams(suspendingPackage, suspendParams); @@ -359,7 +360,7 @@ public class PackageStateMutator { @NonNull @Override - public PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage) { + public PackageUserStateWrite removeSuspension(@NonNull UserPackage suspendingPackage) { if (mUserState != null) { mUserState.removeSuspension(suspendingPackage); } diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java index 0c6c6723b79bb301b60c82a8151a9072175603ca..f6b21045a8bb3e43bee59f9c91b24918b8f754ae 100644 --- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java +++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager; +import android.content.pm.UserPackage; import android.content.pm.overlay.OverlayPaths; import com.android.server.pm.pkg.PackageUserStateImpl; @@ -38,11 +39,11 @@ public interface PackageUserStateWrite { @PackageManager.DistractionRestriction int restrictionFlags); @NonNull - PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage, + PackageUserStateWrite putSuspendParams(@NonNull UserPackage suspendingPackage, @Nullable SuspendParams suspendParams); @NonNull - PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage); + PackageUserStateWrite removeSuspension(@NonNull UserPackage suspendingPackage); @NonNull PackageUserStateWrite setHidden(boolean hidden); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 6f27507670948e58021767673f59069b0d112064..d903ad4d9f0d03f97e880d8cfdbb663861088f06 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -41,7 +41,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; -import android.content.res.Resources; import android.graphics.Rect; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; @@ -2080,6 +2079,45 @@ public final class TvInputManagerService extends SystemService { } } + @Override + public void stopPlayback(IBinder sessionToken, int mode, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "stopPlayback"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).stopPlayback( + mode); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in stopPlayback(mode)", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void startPlayback(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "stopPlayback"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).startPlayback(); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in startPlayback()", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) { final int callingUid = Binder.getCallingUid(); diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index f9d344bd7e31e4a557fe47faad2596d8c35b43b3..1b45c1b4f3f105cd7c9ac4a57ea5e2ded1909ddb 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -48,6 +48,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; +import android.content.pm.UserPackage; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -335,19 +336,19 @@ class ActivityStartInterceptor { return false; } final String suspendedPackage = mAInfo.applicationInfo.packageName; - final String suspendingPackage = pmi.getSuspendingPackage(suspendedPackage, mUserId); - if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) { + final UserPackage suspender = pmi.getSuspendingPackage(suspendedPackage, mUserId); + if (suspender != null && PLATFORM_PACKAGE_NAME.equals(suspender.packageName)) { return interceptSuspendedByAdminPackage(); } final SuspendDialogInfo dialogInfo = pmi.getSuspendedDialogInfo(suspendedPackage, - suspendingPackage, mUserId); + suspender, mUserId); final Bundle crossProfileOptions = hasCrossProfileAnimation() ? ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle() : null; final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid, FLAG_IMMUTABLE); mIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(suspendedPackage, - suspendingPackage, dialogInfo, crossProfileOptions, target, mUserId); + suspender, dialogInfo, crossProfileOptions, target, mUserId); mCallingPid = mRealCallingPid; mCallingUid = mRealCallingUid; mResolvedType = null; diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java index 0d15fc932e68938ae27a1222c7f957b8353daf81..2b841fdad9c0a56b7afd2d7a64d909a2953fa576 100644 --- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java +++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH; import static com.android.internal.R.bool.config_unfoldTransitionEnabled; import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; @@ -110,15 +111,6 @@ public class PhysicalDisplaySwitchTransitionLauncher { return; } - final TransitionRequestInfo.DisplayChange displayChange = - new TransitionRequestInfo.DisplayChange(displayId); - - final Rect startAbsBounds = new Rect(0, 0, oldDisplayWidth, oldDisplayHeight); - displayChange.setStartAbsBounds(startAbsBounds); - final Rect endAbsBounds = new Rect(0, 0, newDisplayWidth, newDisplayHeight); - displayChange.setEndAbsBounds(endAbsBounds); - displayChange.setPhysicalDisplayChanged(true); - mTransition = null; if (mTransitionController.isCollecting()) { @@ -128,10 +120,20 @@ public class PhysicalDisplaySwitchTransitionLauncher { // Make sure that transition is not ready until we finish the remote display change mTransition.setReady(mDisplayContent, false); + mTransition.addFlag(TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH); ProtoLog.d(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Adding display switch to existing collecting transition"); } else { + final TransitionRequestInfo.DisplayChange displayChange = + new TransitionRequestInfo.DisplayChange(displayId); + + final Rect startAbsBounds = new Rect(0, 0, oldDisplayWidth, oldDisplayHeight); + displayChange.setStartAbsBounds(startAbsBounds); + final Rect endAbsBounds = new Rect(0, 0, newDisplayWidth, newDisplayHeight); + displayChange.setEndAbsBounds(endAbsBounds); + displayChange.setPhysicalDisplayChanged(true); + mTransition = mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE, 0 /* flags */, mDisplayContent, mDisplayContent, null /* remoteTransition */, diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp index d66b9b956071d31a868d5bbb85a10b2ef1e240d4..2a0f1e2ede55a582773fd8cd2b2644d89870ba91 100644 --- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp +++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp @@ -468,7 +468,6 @@ private: borrowed_fd incomingFd, bool waitOnEof, std::vector<char>* buffer, std::vector<IncFsDataBlock>* blocks) { IncFsSize remaining = size; - IncFsSize totalSize = 0; IncFsBlockIndex blockIdx = 0; while (remaining > 0) { constexpr auto capacity = BUFFER_SIZE; @@ -502,7 +501,6 @@ private: buffer->resize(size + read); remaining -= read; - totalSize += read; } if (!buffer->empty() && !flashToIncFs(incfsFd, kind, true, &blockIdx, buffer, blocks)) { return false; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e0a2f30b18310ebf75a62ba31330367d676ecd10..a490013303e95ae68500c08fe97882167d1376c5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -2469,7 +2469,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void migratePersonalAppSuspensionLocked( int doUserId, int poUserId, ActiveAdmin poAdmin) { final PackageManagerInternal pmi = mInjector.getPackageManagerInternal(); - if (!pmi.isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, doUserId)) { + if (!pmi.isAdminSuspendingAnyPackages(doUserId)) { Slogf.i(LOG_TAG, "DO is not suspending any apps."); return; } @@ -2480,7 +2480,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { poAdmin.mSuspendPersonalApps = true; } else { Slogf.i(LOG_TAG, "PO isn't targeting R+, unsuspending personal apps."); - pmi.unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, doUserId); + pmi.unsuspendAdminSuspendedPackages(doUserId); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 6570ce1cd500c6ce1df177cc389714ea97d8e734..506dbe8c48c4adc8f65719ccf74684296340d3ad 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -16,8 +16,6 @@ package com.android.server.devicepolicy; -import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppGlobals; @@ -287,7 +285,7 @@ final class PolicyEnforcerCallbacks { suspendPersonalAppsInPackageManager(context, userId); } else { LocalServices.getService(PackageManagerInternal.class) - .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, userId); + .unsuspendAdminSuspendedPackages(userId); } }); return true; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index b396cf498a6794c7f06ce893afbded7a4b18d9e8..40d3d5ca9fd9d2bcd5ec4233725654a0ff238efb 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -45,15 +45,19 @@ import android.annotation.NonNull; import android.app.PropertyInvalidatedCache; import android.content.ComponentName; import android.content.pm.ApplicationInfo; +import android.content.pm.Flags; import android.content.pm.PackageManager; +import android.content.pm.SharedLibraryInfo; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; +import android.content.pm.UserPackage; import android.os.BaseBundle; import android.os.Message; import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -67,6 +71,7 @@ import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.permission.persistence.RuntimePermissionsPersistence; import com.android.server.LocalServices; +import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.permission.LegacyPermissionDataProvider; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.ArchiveState; @@ -85,6 +90,7 @@ import com.google.common.truth.Truth; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -109,6 +115,9 @@ import java.util.concurrent.CountDownLatch; @RunWith(AndroidJUnit4.class) @SmallTest public class PackageManagerSettingsTests { + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final String PACKAGE_NAME_1 = "com.android.app1"; private static final String PACKAGE_NAME_2 = "com.android.app2"; private static final String PACKAGE_NAME_3 = "com.android.app3"; @@ -140,6 +149,7 @@ public class PackageManagerSettingsTests { public void setup() { // Disable binder caches in this process. PropertyInvalidatedCache.disableForTestMode(); + } @Before @@ -161,6 +171,107 @@ public class PackageManagerSettingsTests { deleteFolder(InstrumentationRegistry.getContext().getFilesDir()); } + @Test + public void testApplicationInfoForUseSdkOptionalEnabled() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_SDK_LIB_INDEPENDENCE); + + // Create basic information for SDK lib + final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1); + ps1.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()) + .setUid(ps1.getAppId()) + .setSystem(true) + .hideAsFinal()); + ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" }); + ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 }); + ps1.setUsesSdkLibrariesOptional(new boolean[] {true}); + ps1.addUsesLibraryInfo(new SharedLibraryInfo("path1", + "packageName1", + Collections.emptyList(), + "com.example.sdk.one", + 12 /*version*/, + SharedLibraryInfo.TYPE_SDK_PACKAGE, + null /*declaringPackage*/, + null /*dependentPackages*/, + null /*dependencies*/, + false /*isNative*/)); + ps1.addUsesLibraryInfo(new SharedLibraryInfo("path11", + "packageName11", + Collections.emptyList(), + "com.example.sdk.oneone", + 1212 /*version*/, + SharedLibraryInfo.TYPE_STATIC, + null /*declaringPackage*/, + null /*dependentPackages*/, + null /*dependencies*/, + false /*isNative*/)); + ApplicationInfo appInfo1 = PackageInfoUtils.generateApplicationInfo(ps1.getAndroidPackage(), + 0 /*flags*/, ps1.getUserStateOrDefault(0), 0 /*userId*/, + ps1); + assertThat(appInfo1, notNullValue()); + assertThat(appInfo1.sharedLibraryInfos, notNullValue()); + assertThat(appInfo1.optionalSharedLibraryInfos, notNullValue()); + assertEquals(appInfo1.sharedLibraryInfos.get(0).getName(), "com.example.sdk.one"); + assertEquals(appInfo1.optionalSharedLibraryInfos.get(0).getName(), "com.example.sdk.one"); + + final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2); + ps2.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_2).hideAsParsed()) + .setUid(ps2.getAppId()) + .setSystem(true) + .hideAsFinal()); + ps2.setUsesSdkLibraries(new String[] { "com.example.sdk.two" }); + ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 }); + ps2.setUsesSdkLibrariesOptional(new boolean[] {false}); + ps2.addUsesLibraryInfo(new SharedLibraryInfo("path2", + "packageName2", + Collections.emptyList(), + "com.example.sdk.two", + 34 /*version*/, + SharedLibraryInfo.TYPE_SDK_PACKAGE, + null /*declaringPackage*/, + null /*dependentPackages*/, + null /*dependencies*/, + false /*isNative*/)); + ApplicationInfo appInfo2 = PackageInfoUtils.generateApplicationInfo(ps2.getAndroidPackage(), + 0 /*flags*/, ps2.getUserStateOrDefault(0), 0 /*userId*/, + ps2); + assertThat(appInfo2, notNullValue()); + assertThat(appInfo2.sharedLibraryInfos, notNullValue()); + assertThat(appInfo2.optionalSharedLibraryInfos, nullValue()); + assertEquals(appInfo2.sharedLibraryInfos.get(0).getName(), "com.example.sdk.two"); + } + + @Test + public void testApplicationInfoForUseSdkOptionalDisabled() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_SDK_LIB_INDEPENDENCE); + + // Create basic information for SDK lib + final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1); + ps1.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()) + .setUid(ps1.getAppId()) + .setSystem(true) + .hideAsFinal()); + ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" }); + ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 }); + ps1.setUsesSdkLibrariesOptional(new boolean[] {true}); + ps1.addUsesLibraryInfo(new SharedLibraryInfo("path1", + "packageName1", + Collections.emptyList(), + "com.example.sdk.one", + 12 /*version*/, + SharedLibraryInfo.TYPE_SDK_PACKAGE, + null /*declaringPackage*/, + null /*dependentPackages*/, + null /*dependencies*/, + false /*isNative*/)); + ApplicationInfo appInfo1 = PackageInfoUtils.generateApplicationInfo(ps1.getAndroidPackage(), + 0 /*flags*/, ps1.getUserStateOrDefault(0), 0 /*userId*/, + ps1); + assertThat(appInfo1, notNullValue()); + assertThat(appInfo1.sharedLibraryInfos, notNullValue()); + assertThat(appInfo1.optionalSharedLibraryInfos, nullValue()); + assertEquals(appInfo1.sharedLibraryInfos.get(0).getName(), "com.example.sdk.one"); + } + /** make sure our initialized KeySetManagerService metadata matches packages.xml */ @Test public void testReadKeySetSettings() throws Exception { @@ -315,7 +426,7 @@ public class PackageManagerSettingsTests { PackageUserStateInternal packageUserState1 = ps1.readUserState(0); assertThat(packageUserState1.isSuspended(), is(true)); assertThat(packageUserState1.getSuspendParams().size(), is(1)); - assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android")); + assertThat(packageUserState1.getSuspendParams().keyAt(0), is(UserPackage.of(0, "android"))); assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue())); assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(), is(nullValue())); @@ -327,7 +438,7 @@ public class PackageManagerSettingsTests { packageUserState1 = ps1.readUserState(0); assertThat(packageUserState1.isSuspended(), is(true)); assertThat(packageUserState1.getSuspendParams().size(), is(1)); - assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android")); + assertThat(packageUserState1.getSuspendParams().keyAt(0), is(UserPackage.of(0, "android"))); assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue())); assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(), is(nullValue())); @@ -362,7 +473,8 @@ public class PackageManagerSettingsTests { watcher.verifyNoChangeReported("readUserState"); assertThat(packageUserState1.isSuspended(), is(true)); assertThat(packageUserState1.getSuspendParams().size(), is(1)); - assertThat(packageUserState1.getSuspendParams().keyAt(0), is(PACKAGE_NAME_3)); + assertThat(packageUserState1.getSuspendParams().keyAt(0), + is(UserPackage.of(0, PACKAGE_NAME_3))); final SuspendParams params = packageUserState1.getSuspendParams().valueAt(0); watcher.verifyNoChangeReported("fetch user state"); assertThat(params, is(notNullValue())); @@ -413,19 +525,24 @@ public class PackageManagerSettingsTests { .setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND) .build(); - ps1.modifyUserState(0).putSuspendParams("suspendingPackage1", + UserPackage suspender1 = UserPackage.of(0, "suspendingPackage1"); + UserPackage suspender2 = UserPackage.of(0, "suspendingPackage2"); + UserPackage suspender3 = UserPackage.of(0, "suspendingPackage3"); + UserPackage irrelevantSuspender = UserPackage.of(0, "irrelevant"); + + ps1.modifyUserState(0).putSuspendParams(suspender1, new SuspendParams(dialogInfo1, appExtras1, launcherExtras1)); - ps1.modifyUserState(0).putSuspendParams("suspendingPackage2", + ps1.modifyUserState(0).putSuspendParams(suspender2, new SuspendParams(dialogInfo2, appExtras2, launcherExtras2)); settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1); watcher.verifyChangeReported("put package 1"); - ps2.modifyUserState(0).putSuspendParams("suspendingPackage3", + ps2.modifyUserState(0).putSuspendParams(suspender3, new SuspendParams(null, appExtras1, null)); settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2); watcher.verifyChangeReported("put package 2"); - ps3.modifyUserState(0).removeSuspension("irrelevant"); + ps3.modifyUserState(0).removeSuspension(irrelevantSuspender); settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3); watcher.verifyChangeReported("put package 3"); @@ -450,7 +567,7 @@ public class PackageManagerSettingsTests { assertThat(readPus1.getSuspendParams().size(), is(2)); watcher.verifyNoChangeReported("read package param"); - assertThat(readPus1.getSuspendParams().keyAt(0), is("suspendingPackage1")); + assertThat(readPus1.getSuspendParams().keyAt(0), is(suspender1)); final SuspendParams params11 = readPus1.getSuspendParams().valueAt(0); watcher.verifyNoChangeReported("read package param"); assertThat(params11, is(notNullValue())); @@ -460,7 +577,7 @@ public class PackageManagerSettingsTests { is(true)); watcher.verifyNoChangeReported("read package param"); - assertThat(readPus1.getSuspendParams().keyAt(1), is("suspendingPackage2")); + assertThat(readPus1.getSuspendParams().keyAt(1), is(suspender2)); final SuspendParams params12 = readPus1.getSuspendParams().valueAt(1); assertThat(params12, is(notNullValue())); assertThat(params12.getDialogInfo(), is(dialogInfo2)); @@ -473,7 +590,7 @@ public class PackageManagerSettingsTests { .readUserState(0); assertThat(readPus2.isSuspended(), is(true)); assertThat(readPus2.getSuspendParams().size(), is(1)); - assertThat(readPus2.getSuspendParams().keyAt(0), is("suspendingPackage3")); + assertThat(readPus2.getSuspendParams().keyAt(0), is(suspender3)); final SuspendParams params21 = readPus2.getSuspendParams().valueAt(0); assertThat(params21, is(notNullValue())); assertThat(params21.getDialogInfo(), is(nullValue())); @@ -1024,7 +1141,8 @@ public class PackageManagerSettingsTests { .setNeutralButtonText(0x11220003) .setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS) .build(); - origPkgSetting01.modifyUserState(0).putSuspendParams("suspendingPackage1", + origPkgSetting01.modifyUserState(0).putSuspendParams( + UserPackage.of(0, "suspendingPackage1"), new SuspendParams(dialogInfo1, appExtras1, launcherExtras1)); origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01)); final PackageSetting testPkgSetting01 = new PackageSetting( diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java index c0c70321c79b5be73f27a5362a4d20e125792950..978044045ab3aa33322a6e07d6d0b288c39f7787 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertTrue; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; +import android.content.pm.UserPackage; import android.content.pm.overlay.OverlayPaths; import android.os.PersistableBundle; import android.platform.test.annotations.Presubmit; @@ -89,7 +90,7 @@ public class PackageUserStateTest { assertThat(testUserState.equals(oldUserState), is(false)); oldUserState = new PackageUserStateImpl(); - oldUserState.putSuspendParams("suspendingPackage", + oldUserState.putSuspendParams(UserPackage.of(0, "suspendingPackage"), new SuspendParams(null, new PersistableBundle(), null)); assertThat(testUserState.equals(oldUserState), is(false)); @@ -220,6 +221,8 @@ public class PackageUserStateTest { final PersistableBundle launcherExtras2 = createPersistableBundle(null, 0, "name", "launcherExtras2", null, 0); + final int suspendingUser1 = 0; + final int suspendingUser2 = 10; final String suspendingPackage1 = "package1"; final String suspendingPackage2 = "package2"; @@ -230,12 +233,12 @@ public class PackageUserStateTest { .setMessage("dialogMessage2") .build(); - final ArrayMap<String, SuspendParams> paramsMap1 = new ArrayMap<>(); - paramsMap1.put(suspendingPackage1, createSuspendParams(dialogInfo1, appExtras1, - launcherExtras1)); - final ArrayMap<String, SuspendParams> paramsMap2 = new ArrayMap<>(); - paramsMap2.put(suspendingPackage2, createSuspendParams(dialogInfo2, - appExtras2, launcherExtras2)); + final ArrayMap<UserPackage, SuspendParams> paramsMap1 = new ArrayMap<>(); + paramsMap1.put(UserPackage.of(suspendingUser1, suspendingPackage1), + createSuspendParams(dialogInfo1, appExtras1, launcherExtras1)); + final ArrayMap<UserPackage, SuspendParams> paramsMap2 = new ArrayMap<>(); + paramsMap2.put(UserPackage.of(suspendingUser2, suspendingPackage2), + createSuspendParams(dialogInfo2, appExtras2, launcherExtras2)); final PackageUserStateImpl testUserState1 = new PackageUserStateImpl(); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt index ae53e707a7cc5244289648aecb9594e6a121c59e..7444403f88c8712845934675684617cd498e1b99 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt @@ -19,6 +19,7 @@ package com.android.server.pm import android.app.AppOpsManager import android.content.Intent import android.content.pm.SuspendDialogInfo +import android.content.pm.UserPackage import android.os.Binder import android.os.PersistableBundle import com.android.server.testutils.any @@ -41,12 +42,18 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { .thenReturn(AppOpsManager.MODE_DEFAULT) } + companion object { + val doUserPackage = UserPackage.of(TEST_USER_ID, DEVICE_OWNER_PACKAGE) + val platformUserPackage = UserPackage.of(TEST_USER_ID, PLATFORM_PACKAGE_NAME) + val testUserPackage1 = UserPackage.of(TEST_USER_ID, TEST_PACKAGE_1) + } + @Test fun setPackagesSuspended() { val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), targetPackages, true /* suspended */, null /* appExtras */, - null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, + null /* launcherExtras */, null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) testHandler.flush() @@ -63,14 +70,14 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { fun setPackagesSuspended_emptyPackageName() { var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), null /* packageNames */, true /* suspended */, null /* appExtras */, - null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, + null /* launcherExtras */, null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) assertThat(failedNames).isNull() failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), arrayOfNulls(0), true /* suspended */, null /* appExtras */, - null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, + null /* launcherExtras */, null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) assertThat(failedNames).isEmpty() @@ -80,7 +87,8 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { fun setPackagesSuspended_callerIsNotAllowed() { val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, - null /* launcherExtras */, null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, + null /* launcherExtras */, null /* dialogInfo */, + testUserPackage1, TEST_USER_ID, Binder.getCallingUid(), false /* quarantined */) assertThat(failedNames).asList().hasSize(1) @@ -91,7 +99,7 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { fun setPackagesSuspended_callerSuspendItself() { val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), arrayOf(DEVICE_OWNER_PACKAGE), true /* suspended */, null /* appExtras */, - null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, + null /* launcherExtras */, null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) assertThat(failedNames).asList().hasSize(1) @@ -102,7 +110,7 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { fun setPackagesSuspended_nonexistentPackage() { val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), arrayOf(NONEXISTENT_PACKAGE), true /* suspended */, null /* appExtras */, - null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, + null /* launcherExtras */, null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) assertThat(failedNames).asList().hasSize(1) @@ -115,7 +123,7 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE) val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), knownPackages, true /* suspended */, null /* appExtras */, - null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, + null /* launcherExtras */, null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */)!! assertThat(failedNames.size).isEqualTo(knownPackages.size) @@ -129,14 +137,14 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), targetPackages, true /* suspended */, null /* appExtras */, - null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, + null /* launcherExtras */, null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) testHandler.flush() Mockito.clearInvocations(broadcastHelper) assertThat(failedNames).isEmpty() failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), targetPackages, false /* suspended */, null /* appExtras */, - null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, + null /* launcherExtras */, null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) testHandler.flush() @@ -184,7 +192,7 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_1) var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), arrayOf(TEST_PACKAGE_1), true /* suspended */, appExtras, null /* launcherExtras */, - null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid, + null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) testHandler.flush() assertThat(failedNames).isEmpty() @@ -202,22 +210,22 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), targetPackages, true /* suspended */, appExtras, null /* launcherExtras */, - null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid, + null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) testHandler.flush() Mockito.clearInvocations(broadcastHelper) assertThat(failedNames).isEmpty() assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), - TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage) assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), - TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage) assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNotNull() assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull() suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(), - targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, + targetPackages, { suspender -> suspender.packageName == DEVICE_OWNER_PACKAGE }, TEST_USER_ID) testHandler.flush() @@ -243,7 +251,7 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2) var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras, - null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid, + null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) testHandler.flush() assertThat(failedNames).isEmpty() @@ -258,7 +266,7 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { fun isPackageSuspended() { var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */, - null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, + null /* launcherExtras */, null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) testHandler.flush() assertThat(failedNames).isEmpty() @@ -273,13 +281,13 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2) var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras, - null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid, + null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) testHandler.flush() assertThat(failedNames).isEmpty() assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), - TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage) } @Test @@ -290,57 +298,57 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { // Suspend. var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), targetPackages, true /* suspended */, null /* appExtras */, launcherExtras, - null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid, + null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) assertThat(failedNames).isEmpty() testHandler.flush() assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), - TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage) // Suspend by system. failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), targetPackages, true /* suspended */, null /* appExtras */, launcherExtras, - null /* dialogInfo */, PLATFORM_PACKAGE_NAME, TEST_USER_ID, deviceOwnerUid, + null /* dialogInfo */, platformUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) assertThat(failedNames).isEmpty() testHandler.flush() assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), - TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME) + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(platformUserPackage) // QAS by package1. failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), targetPackages, true /* suspended */, null /* appExtras */, launcherExtras, - null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid, + null /* dialogInfo */, testUserPackage1, TEST_USER_ID, deviceOwnerUid, true /* quarantined */) assertThat(failedNames).isEmpty() testHandler.flush() assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), - TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(TEST_PACKAGE_1) + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(testUserPackage1) // Un-QAS by package1. suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(), - targetPackages, { suspendingPackage -> suspendingPackage == TEST_PACKAGE_1 }, + targetPackages, { suspendingPackage -> suspendingPackage == testUserPackage1 }, TEST_USER_ID) testHandler.flush() assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), - TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME) + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(platformUserPackage) // Un-suspend by system. suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(), - targetPackages, { suspendingPackage -> suspendingPackage == PLATFORM_PACKAGE_NAME }, + targetPackages, { suspender -> suspender.packageName == PLATFORM_PACKAGE_NAME }, TEST_USER_ID) testHandler.flush() assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), - TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage) // Unsuspend. suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(), - targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, + targetPackages, { suspendingPackage -> suspendingPackage == doUserPackage }, TEST_USER_ID) testHandler.flush() @@ -354,13 +362,13 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { .setTitle(TEST_PACKAGE_1).build() var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */, - null /* launcherExtras */, dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID, + null /* launcherExtras */, dialogInfo, doUserPackage, TEST_USER_ID, deviceOwnerUid, false /* quarantined */) testHandler.flush() assertThat(failedNames).isEmpty() val result = suspendPackageHelper.getSuspendedDialogInfo(pms.snapshotComputer(), - TEST_PACKAGE_1, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!! + TEST_PACKAGE_1, doUserPackage, TEST_USER_ID, deviceOwnerUid)!! assertThat(result.title).isEqualTo(TEST_PACKAGE_1) } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index a78f2dcf2ab272c8c42c7cea2791bd394520f8f2..3b5cae328b3ca5ae15d21abf69443094ea1c7093 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -23,6 +23,8 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS; +import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR; + import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertThrows; @@ -41,6 +43,7 @@ import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; @@ -108,6 +111,7 @@ public class AuthServiceTest { @Mock IFaceService mFaceService; @Mock + AppOpsManager mAppOpsManager; @Mock private VirtualDeviceManagerInternal mVdmInternal; @@ -404,6 +408,23 @@ public class AuthServiceTest { eq(TEST_OP_PACKAGE_NAME)); } + @Test + public void testRegisterAuthenticationStateListener_callsFingerprintService() + throws Exception { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); + setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */); + + mAuthService = new AuthService(mContext, mInjector); + mAuthService.onStart(); + + final AuthenticationStateListener listener = mock(AuthenticationStateListener.class); + + mAuthService.mImpl.registerAuthenticationStateListener(listener); + + waitForIdle(); + verify(mFingerprintService).registerAuthenticationStateListener( + eq(listener)); + } @Test public void testRegisterKeyguardCallback_callsBiometricServiceRegisterKeyguardCallback() diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java index 5012335b533fa77b90710f3eb41fe927fa2df30c..94cb860ae7107805a1a7f751419006cacc2803bb 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors; +import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -23,10 +25,11 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; import androidx.test.filters.SmallTest; @@ -40,6 +43,7 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; +@RequiresFlagsDisabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) @Presubmit @SmallTest public class SensorOverlaysTest { @@ -97,7 +101,7 @@ public class SensorOverlaysTest { private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps) throws Exception { final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps); - final int reason = BiometricOverlayConstants.REASON_UNKNOWN; + final int reason = BiometricRequestConstants.REASON_UNKNOWN; sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient); if (udfps != null) { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 79a528c59f497bd04b1dea4a09a5d615ee1f71a1..c24227fcd1f76e816464014e94e46d8b97288846 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -18,6 +18,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED; +import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -56,6 +58,7 @@ import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.TestableContext; import androidx.test.filters.SmallTest; @@ -68,6 +71,7 @@ import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutTracker; @@ -91,6 +95,8 @@ import java.util.function.Consumer; @SmallTest public class FingerprintAuthenticationClientTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final int SENSOR_ID = 4; private static final int USER_ID = 8; private static final long OP_ID = 7; @@ -128,6 +134,8 @@ public class FingerprintAuthenticationClientTest { @Mock private ISidefpsController mSideFpsController; @Mock + private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock private FingerprintSensorPropertiesInternal mSensorProps; @Mock private ClientMonitorCallback mCallback; @@ -384,6 +392,7 @@ public class FingerprintAuthenticationClientTest { private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block) throws RemoteException { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); final FingerprintAuthenticationClient client = createClient(); client.start(mCallback); @@ -397,6 +406,49 @@ public class FingerprintAuthenticationClientTest { verify(mSideFpsController).hide(anyInt()); } + @Test + public void showHideOverlay_cancel_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.cancel()); + } + + @Test + public void showHideOverlay_stop_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.stopHalOperation()); + } + + @Test + public void showHideOverlay_error_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onError(0, 0)); + verify(mCallback).onClientFinished(any(), eq(false)); + } + + @Test + public void showHideOverlay_lockout_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onLockoutTimed(5000)); + } + + @Test + public void showHideOverlay_lockoutPerm_sidefpsControllerRemovalRefactor() + throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onLockoutPermanent()); + } + + private void showHideOverlay_sidefpsControllerRemovalRefactor( + Consumer<FingerprintAuthenticationClient> block) throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); + final FingerprintAuthenticationClient client = createClient(); + + client.start(mCallback); + + verify(mUdfpsOverlayController).showUdfpsOverlay(eq(REQUEST_ID), anyInt(), anyInt(), any()); + verify(mAuthenticationStateListeners).onAuthenticationStarted(anyInt()); + + block.accept(client); + + verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt()); + verify(mAuthenticationStateListeners).onAuthenticationStopped(); + } + @Test public void cancelsAuthWhenNotInForeground() throws Exception { final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo(); @@ -502,7 +554,8 @@ public class FingerprintAuthenticationClientTest { mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, null /* taskStackListener */, - mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, + mUdfpsOverlayController, mSideFpsController, mAuthenticationStateListeners, + allowBackgroundAuthentication, mSensorProps, new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock, lockoutTracker) { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index c7eb1db3e74bb9c9f46687c104329a9d4e44d10e..e7d4a2e463f7dc325cd2bc5f3dfa4511289ebdce 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR; + import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; @@ -38,6 +40,7 @@ import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.TestableContext; import androidx.test.filters.SmallTest; @@ -48,6 +51,7 @@ import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.log.Probe; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -68,6 +72,8 @@ import java.util.function.Consumer; @SmallTest public class FingerprintEnrollClientTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final byte[] HAT = new byte[69]; private static final int USER_ID = 8; private static final long REQUEST_ID = 9; @@ -98,6 +104,8 @@ public class FingerprintEnrollClientTest { @Mock private ISidefpsController mSideFpsController; @Mock + private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock private FingerprintSensorPropertiesInternal mSensorProps; @Mock private ClientMonitorCallback mCallback; @@ -271,6 +279,7 @@ public class FingerprintEnrollClientTest { private void showHideOverlay(Consumer<FingerprintEnrollClient> block) throws RemoteException { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); final FingerprintEnrollClient client = createClient(); client.start(mCallback); @@ -284,6 +293,44 @@ public class FingerprintEnrollClientTest { verify(mSideFpsController).hide(anyInt()); } + @Test + public void showHideOverlay_cancel_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.cancel()); + } + + @Test + public void showHideOverlay_stop_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.stopHalOperation()); + } + + @Test + public void showHideOverlay_error_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onError(0, 0)); + verify(mCallback).onClientFinished(any(), eq(false)); + } + + @Test + public void showHideOverlay_result_sidefpsControllerRemovalRefactor() throws RemoteException { + showHideOverlay_sidefpsControllerRemovalRefactor( + c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0)); + } + + private void showHideOverlay_sidefpsControllerRemovalRefactor( + Consumer<FingerprintEnrollClient> block) throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); + final FingerprintEnrollClient client = createClient(); + + client.start(mCallback); + + verify(mUdfpsOverlayController).showUdfpsOverlay(eq(REQUEST_ID), anyInt(), anyInt(), any()); + verify(mAuthenticationStateListeners).onAuthenticationStarted(anyInt()); + + block.accept(client); + + verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt()); + verify(mAuthenticationStateListeners).onAuthenticationStopped(); + } + private FingerprintEnrollClient createClient() throws RemoteException { return createClient(500); } @@ -296,6 +343,7 @@ public class FingerprintEnrollClientTest { mClientMonitorCallbackConverter, 0 /* userId */, HAT, "owner", mBiometricUtils, 8 /* sensorId */, mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController, - mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL); + mSideFpsController, mAuthenticationStateListeners, 6 /* maxTemplatesPerUser */, + FingerprintManager.ENROLL_ENROLL); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java index 8f6efffcbff8a30d79bb8b7e721b4808b9a8a3f1..4cfb83fa1c69fc051c962a4f7799962a3c9cceaa 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java @@ -43,6 +43,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; @@ -74,6 +75,8 @@ public class FingerprintProviderTest { @Mock private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; @Mock + private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock private BiometricStateCallback mBiometricStateCallback; @Mock private BiometricContext mBiometricContext; @@ -110,8 +113,9 @@ public class FingerprintProviderTest { mLockoutResetDispatcher = new LockoutResetDispatcher(mContext); mFingerprintProvider = new FingerprintProvider(mContext, - mBiometricStateCallback, mSensorProps, TAG, mLockoutResetDispatcher, - mGestureAvailabilityDispatcher, mBiometricContext, mDaemon); + mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG, + mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext, + mDaemon); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java index b32b89aaefb03c310519d5bac87df9737a99f8fa..0d3f1921c9475ea63b7d31797a1930a5360a9e2d 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java @@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -70,6 +71,8 @@ public class Fingerprint21Test { @Mock private BiometricScheduler mScheduler; @Mock + private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock private BiometricStateCallback mBiometricStateCallback; @Mock private BiometricContext mBiometricContext; @@ -102,9 +105,10 @@ public class Fingerprint21Test { componentInfo, FingerprintSensorProperties.TYPE_UNKNOWN, resetLockoutRequiresHardwareAuthToken); - mFingerprint21 = new TestableFingerprint21(mContext, mBiometricStateCallback, sensorProps, - mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, - mHalResultController, mBiometricContext); + mFingerprint21 = new TestableFingerprint21(mContext, mBiometricStateCallback, + mAuthenticationStateListeners, sensorProps, mScheduler, + new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, mHalResultController, + mBiometricContext); } @Test @@ -126,13 +130,14 @@ public class Fingerprint21Test { TestableFingerprint21(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull BiometricScheduler scheduler, @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull HalResultController controller, @NonNull BiometricContext biometricContext) { - super(context, biometricStateCallback, sensorProps, scheduler, handler, - lockoutResetDispatcher, controller, biometricContext); + super(context, biometricStateCallback, authenticationStateListeners, sensorProps, + scheduler, handler, lockoutResetDispatcher, controller, biometricContext); } @Override diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java index 943a9c4759c42cbfbb95baa5b0a474c1a8ae998a..1dd64ffa5ddedfa846d3e7a05c41057ea50d5551 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -18,7 +18,6 @@ package com.android.server.devicepolicy; import static android.os.UserHandle.USER_SYSTEM; import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile; -import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -222,21 +221,21 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R); // Pretend some packages are suspended. - when(getServices().packageManagerInternal.isSuspendingAnyPackages( - PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true); + when(getServices().packageManagerInternal.isAdminSuspendingAnyPackages( + USER_SYSTEM)).thenReturn(true); final DevicePolicyManagerServiceTestable dpms = bootDpmsUp(); verify(getServices().packageManagerInternal, never()) - .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM); + .unsuspendAdminSuspendedPackages(USER_SYSTEM); sendBroadcastWithUser(dpms, Intent.ACTION_USER_STARTED, USER_SYSTEM); // Verify that actual package suspension state is not modified after user start verify(getServices().packageManagerInternal, never()) - .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM); + .unsuspendAdminSuspendedPackages(USER_SYSTEM); verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser( - any(), anyBoolean(), any(), any(), any(), anyInt(), any(), anyInt()); + any(), anyBoolean(), any(), any(), any(), anyInt(), any(), anyInt(), anyInt()); final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext); poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID); @@ -255,14 +254,14 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q); // Pretend some packages are suspended. - when(getServices().packageManagerInternal.isSuspendingAnyPackages( - PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true); + when(getServices().packageManagerInternal.isAdminSuspendingAnyPackages( + USER_SYSTEM)).thenReturn(true); final DevicePolicyManagerServiceTestable dpms = bootDpmsUp(); // Verify that apps get unsuspended. verify(getServices().packageManagerInternal) - .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM); + .unsuspendAdminSuspendedPackages(USER_SYSTEM); final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext); poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index f4dac2c10d0fc999cbf5648d8e433e99b8180b01..24704034ae0c6437e93fd7ba0055a454d0b81209 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -63,7 +63,6 @@ import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_PROFILE_OFF_DEADLINE; import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_TURN_PROFILE_ON_NOTIFICATION; import static com.android.server.devicepolicy.DpmMockContext.CALLER_USER_HANDLE; -import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.testutils.TestUtils.assertExpectException; import static com.google.common.truth.Truth.assertThat; @@ -5080,7 +5079,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().iwindowManager).refreshScreenCaptureDisabled(); // Unsuspend personal apps verify(getServices().packageManagerInternal) - .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, UserHandle.USER_SYSTEM); + .unsuspendAdminSuspendedPackages(UserHandle.USER_SYSTEM); verify(getServices().subscriptionManager).setSubscriptionUserHandle(0, null); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "false", false); @@ -7535,7 +7534,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { .cancel(eq(SystemMessageProto.SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED)); // Verify that the apps are NOT unsuspeded. verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser( - any(), eq(false), any(), any(), any(), anyInt(), any(), anyInt()); + any(), eq(false), any(), any(), any(), anyInt(), any(), anyInt(), anyInt()); // Verify that DPC is invoked to check policy compliance. verify(mContext.spiedContext).startActivityAsUser( MockUtils.checkIntentAction(ACTION_CHECK_POLICY_COMPLIANCE), diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java index fe37f4241d8e5cdd80d542da5010565c126a2447..b3d25f2eef25c7ec5cf2a31cb118716a5f718e7e 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java @@ -47,6 +47,10 @@ import java.util.Map; @Presubmit @RunWith(AndroidJUnit4.class) public final class OverrideRequestControllerTest { + + private static final DeviceState TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0); + private static final DeviceState TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0); + private TestStatusChangeListener mStatusListener; private OverrideRequestController mController; @@ -59,7 +63,7 @@ public final class OverrideRequestControllerTest { @Test public void addRequest() { OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(request)); mController.addRequest(request); @@ -69,14 +73,14 @@ public final class OverrideRequestControllerTest { @Test public void addRequest_cancelExistingRequestThroughNewRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(firstRequest)); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(secondRequest)); mController.addRequest(secondRequest); @@ -87,7 +91,7 @@ public final class OverrideRequestControllerTest { @Test public void addRequest_cancelActiveRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); mController.addRequest(firstRequest); @@ -101,7 +105,7 @@ public final class OverrideRequestControllerTest { @Test public void addBaseStateRequest() { OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); assertNull(mStatusListener.getLastStatus(request)); mController.addBaseStateRequest(request); @@ -111,14 +115,14 @@ public final class OverrideRequestControllerTest { @Test public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); assertNull(mStatusListener.getLastStatus(firstRequest)); mController.addBaseStateRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); assertNull(mStatusListener.getLastStatus(secondRequest)); mController.addBaseStateRequest(secondRequest); @@ -129,7 +133,7 @@ public final class OverrideRequestControllerTest { @Test public void addBaseStateRequest_cancelActiveBaseStateRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addBaseStateRequest(firstRequest); @@ -143,13 +147,13 @@ public final class OverrideRequestControllerTest { @Test public void handleBaseStateChanged() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, + TEST_DEVICE_STATE_ZERO, DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); @@ -169,11 +173,11 @@ public final class OverrideRequestControllerTest { @Test public void handleProcessDied() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); @@ -192,11 +196,11 @@ public final class OverrideRequestControllerTest { mController.setStickyRequestsAllowed(true); OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); @@ -215,11 +219,11 @@ public final class OverrideRequestControllerTest { @Test public void handleNewSupportedStates() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); @@ -242,7 +246,7 @@ public final class OverrideRequestControllerTest { @Test public void cancelOverrideRequestsTest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java index fb785749451520bfb3648c9aac4247f1a93adcc4..0d25faf2bcf18e49072019a3078869ba789238c3 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java @@ -60,6 +60,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.inputmethod.StartInputFlags; +import com.google.common.truth.Truth; + import org.junit.Test; import org.junit.runner.RunWith; @@ -1336,6 +1338,54 @@ public class InputMethodUtilsTest { } } + private static void verifySplitEnabledImeStr(@NonNull String enabledImeStr, + @NonNull String... expected) { + final ArrayList<String> actual = new ArrayList<>(); + InputMethodUtils.splitEnabledImeStr(enabledImeStr, actual::add); + if (expected.length == 0) { + Truth.assertThat(actual).isEmpty(); + } else { + Truth.assertThat(actual).containsExactlyElementsIn(expected); + } + } + + @Test + public void testSplitEnabledImeStr() { + verifySplitEnabledImeStr(""); + verifySplitEnabledImeStr("com.android/.ime1", "com.android/.ime1"); + verifySplitEnabledImeStr("com.android/.ime1;1;2;3", "com.android/.ime1"); + verifySplitEnabledImeStr("com.android/.ime1;1;2;3:com.android/.ime2", + "com.android/.ime1", "com.android/.ime2"); + verifySplitEnabledImeStr("com.android/.ime1:com.android/.ime2", + "com.android/.ime1", "com.android/.ime2"); + verifySplitEnabledImeStr("com.android/.ime1:com.android/.ime2:com.android/.ime3", + "com.android/.ime1", "com.android/.ime2", "com.android/.ime3"); + verifySplitEnabledImeStr("com.android/.ime1;1:com.android/.ime2;1:com.android/.ime3;1", + "com.android/.ime1", "com.android/.ime2", "com.android/.ime3"); + } + + @Test + public void testConcatEnabledImeIds() { + Truth.assertThat(InputMethodUtils.concatEnabledImeIds("")).isEmpty(); + Truth.assertThat(InputMethodUtils.concatEnabledImeIds("", "com.android/.ime1")) + .isEqualTo("com.android/.ime1"); + Truth.assertThat(InputMethodUtils.concatEnabledImeIds( + "com.android/.ime1", "com.android/.ime1")) + .isEqualTo("com.android/.ime1"); + Truth.assertThat(InputMethodUtils.concatEnabledImeIds( + "com.android/.ime1", "com.android/.ime2")) + .isEqualTo("com.android/.ime1:com.android/.ime2"); + Truth.assertThat(InputMethodUtils.concatEnabledImeIds( + "com.android/.ime1", "com.android/.ime2", "com.android/.ime3")) + .isEqualTo("com.android/.ime1:com.android/.ime2:com.android/.ime3"); + Truth.assertThat(InputMethodUtils.concatEnabledImeIds( + "com.android/.ime1:com.android/.ime2", "com.android/.ime1")) + .isEqualTo("com.android/.ime1:com.android/.ime2"); + Truth.assertThat(InputMethodUtils.concatEnabledImeIds( + "com.android/.ime1:com.android/.ime2", "com.android/.ime3")) + .isEqualTo("com.android/.ime1:com.android/.ime2:com.android/.ime3"); + } + @Test public void updateEnabledImeStringTest() { // No change cases 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 14b551ae0b220d53c3b6b384fdb5095f6d7f6b46..776189eeb7c3bef7f47a8902a77ca28c307f1103 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -219,6 +219,7 @@ import android.provider.MediaStore; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.ConversationChannelWrapper; +import android.service.notification.DeviceEffectsApplier; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationRankingUpdate; @@ -635,6 +636,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } private void initNMS() throws Exception { + initNMS(SystemService.PHASE_BOOT_COMPLETED); + } + + private void initNMS(int upToBootPhase) throws Exception { mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger, mNotificationInstanceIdSequence); @@ -662,13 +667,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class), mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager, mPowerManager, mPostNotificationTrackerFactory); + // Return first true for RoleObserver main-thread check when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false); - mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper); + + if (upToBootPhase >= SystemService.PHASE_SYSTEM_SERVICES_READY) { + mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper); + } + Mockito.reset(mHistoryManager); verify(mHistoryManager, never()).onBootPhaseAppsCanStart(); - mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper); - verify(mHistoryManager).onBootPhaseAppsCanStart(); + + if (upToBootPhase >= SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { + mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper); + verify(mHistoryManager).onBootPhaseAppsCanStart(); + } // TODO b/291907312: remove feature flag if (Flags.refactorAttentionHelper()) { @@ -914,7 +927,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mTestNotificationChannel.setAllowBubbles(channelEnabled); } - private void setUpPrefsForHistory(int uid, boolean globalEnabled) { + private void setUpPrefsForHistory(int uid, boolean globalEnabled) throws Exception { + initNMS(SystemService.PHASE_ACTIVITY_MANAGER_READY); + // Sets NOTIFICATION_HISTORY_ENABLED setting for calling process uid Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, uid); @@ -10090,7 +10105,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testHandleOnPackageRemoved_ClearsHistory() throws RemoteException { + public void testHandleOnPackageRemoved_ClearsHistory() throws Exception { // Enables Notification History setting setUpPrefsForHistory(mUid, true /* =enabled */); @@ -13208,6 +13223,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mSnoozeHelper).clearData(anyInt()); } + @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void setDeviceEffectsApplier_succeeds() throws Exception { + initNMS(SystemService.PHASE_SYSTEM_SERVICES_READY); + + mInternalService.setDeviceEffectsApplier(mock(DeviceEffectsApplier.class)); + // No exception! + } + + @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void setDeviceEffectsApplier_tooLate_throws() throws Exception { + initNMS(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START); + + assertThrows(IllegalStateException.class, () -> + mInternalService.setDeviceEffectsApplier(mock(DeviceEffectsApplier.class))); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void setDeviceEffectsApplier_calledTwice_throws() throws Exception { + initNMS(SystemService.PHASE_SYSTEM_SERVICES_READY); + + mInternalService.setDeviceEffectsApplier(mock(DeviceEffectsApplier.class)); + assertThrows(IllegalStateException.class, () -> + mInternalService.setDeviceEffectsApplier(mock(DeviceEffectsApplier.class))); + } + @Test @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) public void setNotificationPolicy_mappedToImplicitRule() throws RemoteException { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java index 526201f9c1c6528084698591d3db5f55c5677151..670f9f697a5cef00cd966ea5114c8e73218a289c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -49,6 +49,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; +import android.content.pm.UserPackage; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -195,7 +196,7 @@ public class ActivityStartInterceptorTest { mAInfo.applicationInfo.flags = FLAG_SUSPENDED; when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID)) - .thenReturn(PLATFORM_PACKAGE_NAME); + .thenReturn(UserPackage.of(TEST_USER_ID, PLATFORM_PACKAGE_NAME)); // THEN calling intercept returns true assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null)); @@ -227,9 +228,10 @@ public class ActivityStartInterceptorTest { .setMessage("Test Message") .setIcon(0x11110001) .build(); + UserPackage suspender = UserPackage.of(TEST_USER_ID, suspendingPackage); when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID)) - .thenReturn(suspendingPackage); - when(mPackageManagerInternal.getSuspendedDialogInfo(TEST_PACKAGE_NAME, suspendingPackage, + .thenReturn(suspender); + when(mPackageManagerInternal.getSuspendedDialogInfo(TEST_PACKAGE_NAME, suspender, TEST_USER_ID)).thenReturn(dialogInfo); return dialogInfo; } diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java index eac4d1682aa9114ea4a11780230040b87e7f9129..cc768bc00250013179dc2363ec6f21088894d477 100644 --- a/telephony/java/android/telephony/CarrierRestrictionRules.java +++ b/telephony/java/android/telephony/CarrierRestrictionRules.java @@ -16,12 +16,16 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.service.carrier.CarrierIdentifier; +import android.telephony.TelephonyManager.CarrierRestrictionStatus; + +import com.android.internal.telephony.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -104,7 +108,7 @@ public final class CarrierRestrictionRules implements Parcelable { private int mCarrierRestrictionDefault; @MultiSimPolicy private int mMultiSimPolicy; - @TelephonyManager.CarrierRestrictionStatus + @CarrierRestrictionStatus private int mCarrierRestrictionStatus; private CarrierRestrictionRules() { @@ -293,8 +297,22 @@ public final class CarrierRestrictionRules implements Parcelable { return true; } - /** @hide */ - public int getCarrierRestrictionStatus() { + /** + * Get the carrier restriction status of the device. + * The return value of the API is as follows. + * <ul> + * <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER} + * if the caller and the device locked by the network are same</li> + * <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_RESTRICTED} if the + * caller and the device locked by the network are different</li> + * <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED} if the + * device is not locked</li> + * <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_UNKNOWN} if the device + * locking state is unavailable or radio does not supports the feature</li> + * </ul> + */ + @FlaggedApi(Flags.FLAG_CARRIER_RESTRICTION_STATUS) + public @CarrierRestrictionStatus int getCarrierRestrictionStatus() { return mCarrierRestrictionStatus; } diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 3e8787281f852992deff80652a21c07c81009c8b..8679bd4baf2e3ca27bfc1131d341425c518450e7 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -1866,7 +1866,7 @@ public class ApnSetting implements Parcelable { private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT; private boolean mAlwaysOn; - private int mInfrastructureBitmask = INFRASTRUCTURE_CELLULAR; + private int mInfrastructureBitmask = INFRASTRUCTURE_CELLULAR | INFRASTRUCTURE_SATELLITE; private boolean mEsimBootstrapProvisioning; /** diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt index 12a57d52491a15319d1be3d53d7adccdf1032f72..c8cac8fa2c829b0eea1903b5e0623c91e3ed2f0a 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt @@ -25,6 +25,7 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper @@ -142,7 +143,7 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) : } /** During the transition Secondary Activity shrinks to the bottom right corner. */ - @Presubmit + @FlakyTest(bugId = 315605409) @Test fun secondaryLayerShrinks() { flicker.assertLayers {