diff options
209 files changed, 3588 insertions, 1803 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 806283229d98..7e382870b016 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -518,7 +518,9 @@ public abstract class AccessibilityService extends Service { public static final int GLOBAL_ACTION_POWER_DIALOG = 6; /** - * Action to toggle docking the current app's window + * Action to toggle docking the current app's window. + * <p> + * <strong>Note:</strong> It is effective only if it appears in {@link #getSystemActions()}. */ public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index edcab29dec94..0ff9f6655b8a 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -431,7 +431,7 @@ public class ActivityOptions { private boolean mOverrideTaskTransition; private String mSplashScreenThemeResName; @SplashScreen.SplashScreenStyle - private int mSplashScreenStyle; + private int mSplashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED; private boolean mRemoveWithTaskOrganizer; private boolean mLaunchedFromBubble; private boolean mTransientLaunch; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 45df0d9a8a9f..76e392d6cead 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -321,7 +321,8 @@ public final class ActivityThread extends ClientTransactionHandler @UnsupportedAppUsage private ContextImpl mSystemContext; - private final SparseArray<ContextImpl> mDisplaySystemUiContexts = new SparseArray<>(); + @GuardedBy("this") + private SparseArray<ContextImpl> mDisplaySystemUiContexts; @UnsupportedAppUsage static volatile IPackageManager sPackageManager; @@ -2650,7 +2651,6 @@ public final class ActivityThread extends ClientTransactionHandler } } - @Override @NonNull public ContextImpl getSystemUiContext() { return getSystemUiContext(DEFAULT_DISPLAY); @@ -2664,6 +2664,9 @@ public final class ActivityThread extends ClientTransactionHandler @NonNull public ContextImpl getSystemUiContext(int displayId) { synchronized (this) { + if (mDisplaySystemUiContexts == null) { + mDisplaySystemUiContexts = new SparseArray<>(); + } ContextImpl systemUiContext = mDisplaySystemUiContexts.get(displayId); if (systemUiContext == null) { systemUiContext = ContextImpl.createSystemUiContext(getSystemContext(), displayId); @@ -2673,6 +2676,15 @@ public final class ActivityThread extends ClientTransactionHandler } } + @Nullable + @Override + public ContextImpl getSystemUiContextNoCreate() { + synchronized (this) { + if (mDisplaySystemUiContexts == null) return null; + return mDisplaySystemUiContexts.get(DEFAULT_DISPLAY); + } + } + public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { synchronized (this) { getSystemContext().installSystemApplicationInfo(info, classLoader); diff --git a/core/java/android/app/ActivityThreadInternal.java b/core/java/android/app/ActivityThreadInternal.java index bc698f657305..b9ad5c337813 100644 --- a/core/java/android/app/ActivityThreadInternal.java +++ b/core/java/android/app/ActivityThreadInternal.java @@ -28,7 +28,7 @@ import java.util.ArrayList; interface ActivityThreadInternal { ContextImpl getSystemContext(); - ContextImpl getSystemUiContext(); + ContextImpl getSystemUiContextNoCreate(); boolean isInDensityCompatMode(); diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java index 8637e31eb122..58f60a6a59a7 100644 --- a/core/java/android/app/ConfigurationController.java +++ b/core/java/android/app/ConfigurationController.java @@ -154,9 +154,12 @@ class ConfigurationController { int configDiff; boolean equivalent; + // Get theme outside of synchronization to avoid nested lock. + final Resources.Theme systemTheme = mActivityThread.getSystemContext().getTheme(); + final ContextImpl systemUiContext = mActivityThread.getSystemUiContextNoCreate(); + final Resources.Theme systemUiTheme = + systemUiContext != null ? systemUiContext.getTheme() : null; synchronized (mResourcesManager) { - final Resources.Theme systemTheme = mActivityThread.getSystemContext().getTheme(); - final Resources.Theme systemUiTheme = mActivityThread.getSystemUiContext().getTheme(); if (mPendingConfiguration != null) { if (!mPendingConfiguration.isOtherSeqNewer(config)) { config = mPendingConfiguration; @@ -207,7 +210,8 @@ class ConfigurationController { systemTheme.rebase(); } - if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) { + if (systemUiTheme != null + && (systemUiTheme.getChangingConfigurations() & configDiff) != 0) { systemUiTheme.rebase(); } } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index fca4c698c49c..3712caeddaf5 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -1488,18 +1488,27 @@ public class WallpaperManager { mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; - boolean ok = false; + final Bitmap tmp = BitmapFactory.decodeStream(resources.openRawResource(resid)); try { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - copyStreamToWallpaperFile(resources.openRawResource(resid), fos); - // The 'close()' is the trigger for any server-side image manipulation, - // so we must do that before waiting for completion. - fos.close(); - completion.waitForCompletion(); + // If the stream can't be decoded, treat it as an invalid input. + if (tmp != null) { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + tmp.compress(Bitmap.CompressFormat.PNG, 100, fos); + // The 'close()' is the trigger for any server-side image manipulation, + // so we must do that before waiting for completion. + fos.close(); + completion.waitForCompletion(); + } else { + throw new IllegalArgumentException( + "Resource 0x" + Integer.toHexString(resid) + " is invalid"); + } } finally { // Might be redundant but completion shouldn't wait unless the write // succeeded; this is a fallback if it threw past the close+wait. IoUtils.closeQuietly(fos); + if (tmp != null) { + tmp.recycle(); + } } } } catch (RemoteException e) { @@ -1741,13 +1750,22 @@ public class WallpaperManager { result, which, completion, mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; + final Bitmap tmp = BitmapFactory.decodeStream(bitmapData); try { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - copyStreamToWallpaperFile(bitmapData, fos); - fos.close(); - completion.waitForCompletion(); + // If the stream can't be decoded, treat it as an invalid input. + if (tmp != null) { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + tmp.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.close(); + completion.waitForCompletion(); + } else { + throw new IllegalArgumentException("InputStream is invalid"); + } } finally { IoUtils.closeQuietly(fos); + if (tmp != null) { + tmp.recycle(); + } } } } catch (RemoteException e) { diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index b5298fc9e1c7..d1ef5917ba47 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1010,12 +1010,12 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * This change id restricts treatments that force a given min aspect ratio to activities * whose orientation is fixed to portrait. * - * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled. + * This treatment is enabled by default and only takes effect if OVERRIDE_MIN_ASPECT_RATIO is + * also enabled. * @hide */ @ChangeId @Overridable - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S_V2) @TestApi public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // buganizer id @@ -1367,18 +1367,18 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * Returns if the activity should never be sandboxed to the activity window bounds. * @hide */ - public boolean neverSandboxDisplayApis() { + public boolean neverSandboxDisplayApis(ConstrainDisplayApisConfig constrainDisplayApisConfig) { return isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS) - || ConstrainDisplayApisConfig.neverConstrainDisplayApis(applicationInfo); + || constrainDisplayApisConfig.getNeverConstrainDisplayApis(applicationInfo); } /** * Returns if the activity should always be sandboxed to the activity window bounds. * @hide */ - public boolean alwaysSandboxDisplayApis() { + public boolean alwaysSandboxDisplayApis(ConstrainDisplayApisConfig constrainDisplayApisConfig) { return isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS) - || ConstrainDisplayApisConfig.alwaysConstrainDisplayApis(applicationInfo); + || constrainDisplayApisConfig.getAlwaysConstrainDisplayApis(applicationInfo); } /** @hide */ diff --git a/core/java/android/content/pm/ConstrainDisplayApisConfig.java b/core/java/android/content/pm/ConstrainDisplayApisConfig.java index 11ba3d4ba9a2..98b73aa8860a 100644 --- a/core/java/android/content/pm/ConstrainDisplayApisConfig.java +++ b/core/java/android/content/pm/ConstrainDisplayApisConfig.java @@ -19,10 +19,15 @@ package android.content.pm; import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; import android.provider.DeviceConfig; +import android.util.ArrayMap; +import android.util.Pair; import android.util.Slog; +import com.android.internal.os.BackgroundThread; + import java.util.Arrays; import java.util.List; +import java.util.Map; /** * Class for processing flags in the Device Config namespace 'constrain_display_apis'. @@ -55,19 +60,45 @@ public final class ConstrainDisplayApisConfig { "always_constrain_display_apis"; /** + * Indicates that display APIs should never be constrained to the activity window bounds for all + * packages. + */ + private boolean mNeverConstrainDisplayApisAllPackages; + + /** + * Indicates that display APIs should never be constrained to the activity window bounds for + * a set of defined packages. Map keys are package names, and entries are a + * 'Pair(<min-version-code>, <max-version-code>)'. + */ + private ArrayMap<String, Pair<Long, Long>> mNeverConstrainConfigMap; + + /** + * Indicates that display APIs should always be constrained to the activity window bounds for + * a set of defined packages. Map keys are package names, and entries are a + * 'Pair(<min-version-code>, <max-version-code>)'. + */ + private ArrayMap<String, Pair<Long, Long>> mAlwaysConstrainConfigMap; + + public ConstrainDisplayApisConfig() { + updateCache(); + + DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_CONSTRAIN_DISPLAY_APIS, + BackgroundThread.getExecutor(), properties -> updateCache()); + } + + /** * Returns true if either the flag 'never_constrain_display_apis_all_packages' is true or the * flag 'never_constrain_display_apis' contains a package entry that matches the given {@code * applicationInfo}. * * @param applicationInfo Information about the application/package. */ - public static boolean neverConstrainDisplayApis(ApplicationInfo applicationInfo) { - if (DeviceConfig.getBoolean(NAMESPACE_CONSTRAIN_DISPLAY_APIS, - FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false)) { + public boolean getNeverConstrainDisplayApis(ApplicationInfo applicationInfo) { + if (mNeverConstrainDisplayApisAllPackages) { return true; } - return flagHasMatchingPackageEntry(FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, applicationInfo); + return flagHasMatchingPackageEntry(mNeverConstrainConfigMap, applicationInfo); } /** @@ -76,73 +107,106 @@ public final class ConstrainDisplayApisConfig { * * @param applicationInfo Information about the application/package. */ - public static boolean alwaysConstrainDisplayApis(ApplicationInfo applicationInfo) { - return flagHasMatchingPackageEntry(FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS, applicationInfo); + public boolean getAlwaysConstrainDisplayApis(ApplicationInfo applicationInfo) { + return flagHasMatchingPackageEntry(mAlwaysConstrainConfigMap, applicationInfo); } + /** - * Returns true if the flag with the given {@code flagName} contains a package entry that - * matches the given {@code applicationInfo}. - * - * @param applicationInfo Information about the application/package. + * Updates {@link #mNeverConstrainDisplayApisAllPackages}, {@link #mNeverConstrainConfigMap}, + * and {@link #mAlwaysConstrainConfigMap} from the {@link DeviceConfig}. */ - private static boolean flagHasMatchingPackageEntry(String flagName, - ApplicationInfo applicationInfo) { - String configStr = DeviceConfig.getString(NAMESPACE_CONSTRAIN_DISPLAY_APIS, - flagName, /* defaultValue= */ ""); + private void updateCache() { + mNeverConstrainDisplayApisAllPackages = DeviceConfig.getBoolean( + NAMESPACE_CONSTRAIN_DISPLAY_APIS, + FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false); + + final String neverConstrainConfigStr = DeviceConfig.getString( + NAMESPACE_CONSTRAIN_DISPLAY_APIS, + FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ ""); + mNeverConstrainConfigMap = buildConfigMap(neverConstrainConfigStr); + + final String alwaysConstrainConfigStr = DeviceConfig.getString( + NAMESPACE_CONSTRAIN_DISPLAY_APIS, + FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ ""); + mAlwaysConstrainConfigMap = buildConfigMap(alwaysConstrainConfigStr); + } + /** + * Processes the configuration string into a map of version codes, for the given + * configuration to be applied to the specified packages. If the given package + * entry string is invalid, then the map will not contain an entry for the package. + * + * @param configStr A configuration string expected to be in the format of a list of package + * entries separated by ','. A package entry expected to be in the format + * '<package-name>:<min-version-code>?:<max-version-code>?'. + * @return a map of configuration entries, where each key is a package name. Each value is + * a pair of version codes, in the format 'Pair(<min-version-code>, <max-version-code>)'. + */ + private static ArrayMap<String, Pair<Long, Long>> buildConfigMap(String configStr) { + ArrayMap<String, Pair<Long, Long>> configMap = new ArrayMap<>(); // String#split returns a non-empty array given an empty string. if (configStr.isEmpty()) { - return false; + return configMap; } - for (String packageEntryString : configStr.split(",")) { - if (matchesApplicationInfo(packageEntryString, applicationInfo)) { - return true; + List<String> packageAndVersions = Arrays.asList(packageEntryString.split(":", 3)); + if (packageAndVersions.size() != 3) { + Slog.w(TAG, "Invalid package entry in flag 'never/always_constrain_display_apis': " + + packageEntryString); + // Skip this entry. + continue; + } + String packageName = packageAndVersions.get(0); + String minVersionCodeStr = packageAndVersions.get(1); + String maxVersionCodeStr = packageAndVersions.get(2); + try { + final long minVersion = + minVersionCodeStr.isEmpty() ? Long.MIN_VALUE : Long.parseLong( + minVersionCodeStr); + final long maxVersion = + maxVersionCodeStr.isEmpty() ? Long.MAX_VALUE : Long.parseLong( + maxVersionCodeStr); + Pair<Long, Long> minMaxVersionCodes = new Pair<>(minVersion, maxVersion); + configMap.put(packageName, minMaxVersionCodes); + } catch (NumberFormatException e) { + Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryString); + // Skip this entry. } } - - return false; + return configMap; } /** - * Parses the given {@code packageEntryString} and returns true if {@code - * applicationInfo.packageName} matches the package name in the config and {@code - * applicationInfo.longVersionCode} is within the version range in the config. - * - * <p>Logs a warning and returns false in case the given {@code packageEntryString} is invalid. + * Returns true if the flag with the given {@code flagName} contains a package entry that + * matches the given {@code applicationInfo}. * - * @param packageEntryStr A package entry expected to be in the format - * '<package-name>:<min-version-code>?:<max-version-code>?'. + * @param configMap the map representing the current configuration value to examine * @param applicationInfo Information about the application/package. */ - private static boolean matchesApplicationInfo(String packageEntryStr, + private static boolean flagHasMatchingPackageEntry(Map<String, Pair<Long, Long>> configMap, ApplicationInfo applicationInfo) { - List<String> packageAndVersions = Arrays.asList(packageEntryStr.split(":", 3)); - if (packageAndVersions.size() != 3) { - Slog.w(TAG, "Invalid package entry in flag 'never_constrain_display_apis': " - + packageEntryStr); + if (configMap.isEmpty()) { return false; } - String packageName = packageAndVersions.get(0); - String minVersionCodeStr = packageAndVersions.get(1); - String maxVersionCodeStr = packageAndVersions.get(2); - - if (!packageName.equals(applicationInfo.packageName)) { + if (!configMap.containsKey(applicationInfo.packageName)) { return false; } - long version = applicationInfo.longVersionCode; - try { - if (!minVersionCodeStr.isEmpty() && version < Long.parseLong(minVersionCodeStr)) { - return false; - } - if (!maxVersionCodeStr.isEmpty() && version > Long.parseLong(maxVersionCodeStr)) { - return false; - } - } catch (NumberFormatException e) { - Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryStr); - return false; - } - return true; + return matchesApplicationInfo(configMap.get(applicationInfo.packageName), applicationInfo); + } + + /** + * Parses the given {@code minMaxVersionCodes} and returns true if {@code + * applicationInfo.longVersionCode} is within the version range in the pair. + * Returns false otherwise. + * + * @param minMaxVersionCodes A pair expected to be in the format + * 'Pair(<min-version-code>, <max-version-code>)'. + * @param applicationInfo Information about the application/package. + */ + private static boolean matchesApplicationInfo(Pair<Long, Long> minMaxVersionCodes, + ApplicationInfo applicationInfo) { + return applicationInfo.longVersionCode >= minMaxVersionCodes.first + && applicationInfo.longVersionCode <= minMaxVersionCodes.second; } } diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 43ef33e1f420..28046c56b9f8 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -151,6 +151,12 @@ public interface BiometricConstants { int BIOMETRIC_ERROR_RE_ENROLL = 16; /** + * The privacy setting has been enabled and will block use of the sensor. + * @hide + */ + int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18; + + /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. * @hide diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index fe43c83d17f1..fd46f243874b 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -69,7 +69,7 @@ public interface BiometricFaceConstants { BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, BIOMETRIC_ERROR_RE_ENROLL, - FACE_ERROR_UNKNOWN + FACE_ERROR_UNKNOWN, }) @Retention(RetentionPolicy.SOURCE) @interface FaceError {} diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 9f77a7e72e70..0b02a919b2d9 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -2632,7 +2632,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * </tbody> * </table> * <p>For applications targeting SDK version 31 or newer, if the mobile device declares to be - * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS media performance class} S, + * media performance class 12 or higher by setting + * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger, * the primary camera devices (first rear/front camera in the camera ID list) will not * support JPEG sizes smaller than 1080p. If the application configures a JPEG stream * smaller than 1080p, the camera device will round up the JPEG image size to at least @@ -2705,9 +2706,11 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * </tbody> * </table> * <p>For applications targeting SDK version 31 or newer, if the mobile device doesn't declare - * to be media performance class S, or if the camera device isn't a primary rear/front - * camera, the minimum required output stream configurations are the same as for applications - * targeting SDK version older than 31.</p> + * to be media performance class 12 or better by setting + * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger, + * or if the camera device isn't a primary rear/front camera, the minimum required output + * stream configurations are the same as for applications targeting SDK version older than + * 31.</p> * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} for additional * mandatory stream configurations on a per-capability basis.</p> * <p>Exception on 176x144 (QCIF) resolution: camera devices usually have a fixed capability for diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java index 74b814ea4159..c8b4226ecae0 100644 --- a/core/java/android/os/AppZygote.java +++ b/core/java/android/os/AppZygote.java @@ -45,6 +45,8 @@ public class AppZygote { // Last UID/GID of the range the AppZygote can setuid()/setgid() to private final int mZygoteUidGidMax; + private final int mZygoteRuntimeFlags; + private final Object mLock = new Object(); /** @@ -56,11 +58,13 @@ public class AppZygote { private final ApplicationInfo mAppInfo; - public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax) { + public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax, + int runtimeFlags) { mAppInfo = appInfo; mZygoteUid = zygoteUid; mZygoteUidGidMin = uidGidMin; mZygoteUidGidMax = uidGidMax; + mZygoteRuntimeFlags = runtimeFlags; } /** @@ -110,7 +114,7 @@ public class AppZygote { mZygoteUid, mZygoteUid, null, // gids - 0, // runtimeFlags + mZygoteRuntimeFlags, // runtimeFlags "app_zygote", // seInfo abi, // abi abi, // acceptedAbiList diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fdd3836473ba..07feb44efea9 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -953,6 +953,22 @@ public final class Settings { public static final String ACTION_LOCKSCREEN_SETTINGS = "android.settings.LOCK_SCREEN_SETTINGS"; /** + * Activity Action: Show settings to allow pairing bluetooth devices. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_BLUETOOTH_PAIRING_SETTINGS = + "android.settings.BLUETOOTH_PAIRING_SETTINGS"; + + /** * Activity Action: Show settings to configure input methods, in particular * allowing the user to enable input methods. * <p> diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 4d0fc1642874..ee32ce43821c 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -1750,8 +1750,9 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall /** * Called when there has been a failure transferring the {@link AssistStructure} to * the assistant. This may happen, for example, if the data is too large and results - * in an out of memory exception, or the client has provided corrupt data. This will - * be called immediately before {@link #onHandleAssist} and the AssistStructure supplied + * in an out of memory exception, the data has been cleared during transferring due to + * the new incoming assist data, or the client has provided corrupt data. This will be + * called immediately before {@link #onHandleAssist} and the AssistStructure supplied * there afterwards will be null. * * @param failure The failure exception that was thrown when building the @@ -1789,7 +1790,8 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall * Called to receive data from the application that the user was currently viewing when * an assist session is started. If the original show request did not specify * {@link #SHOW_WITH_ASSIST}, {@link AssistState} parameter will only provide - * {@link ActivityId}. + * {@link ActivityId}. If there was a failure to write the assist data to + * {@link AssistStructure}, the {@link AssistState#getAssistStructure()} will return null. * * <p>This method is called for all activities along with an index and count that indicates * which activity the data is for. {@code index} will be between 0 and {@code count}-1 and @@ -2214,7 +2216,8 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall * @return If available, the structure definition of all windows currently * displayed by the app. May be null if assist data has been disabled by the user * or device policy; will be null if the original show request did not specify - * {@link #SHOW_WITH_ASSIST}; will be an empty stub if the application has disabled assist + * {@link #SHOW_WITH_ASSIST} or the assist data has been corrupt when writing the data to + * {@link AssistStructure}; will be an empty stub if the application has disabled assist * by marking its window as secure. */ public @Nullable AssistStructure getAssistStructure() { diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index 046232a8afaf..2dac81c66d2a 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -129,7 +129,11 @@ public class RemoteAnimationTarget implements Parcelable { * The index of the element in the tree in prefix order. This should be used for z-layering * to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to * happen. + * @deprecated WindowManager may set a z-order different from the prefix order, and has set the + * correct layer for the animation leash already, so this should not be used for + * layer any more. */ + @Deprecated @UnsupportedAppUsage public final int prefixOrderIndex; diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 3d4d9eca6b16..dfd853acaf0d 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -3696,18 +3696,21 @@ public class RemoteViews implements Parcelable, Filter { } private void initializeFrom(@NonNull RemoteViews src, @Nullable RemoteViews hierarchyRoot) { + if (hierarchyRoot == null) { + mBitmapCache = src.mBitmapCache; + mApplicationInfoCache = src.mApplicationInfoCache; + } else { + mBitmapCache = hierarchyRoot.mBitmapCache; + mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache; + } if (hierarchyRoot == null || src.mIsRoot) { // If there's no provided root, or if src was itself a root, then this RemoteViews is // the root of the new hierarchy. mIsRoot = true; - mBitmapCache = new BitmapCache(); - mApplicationInfoCache = new ApplicationInfoCache(); hierarchyRoot = this; } else { // Otherwise, we're a descendant in the hierarchy. mIsRoot = false; - mBitmapCache = hierarchyRoot.mBitmapCache; - mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache; } mApplication = src.mApplication; mLayoutId = src.mLayoutId; diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index 1ad0452dd837..4399207fcc27 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -44,4 +44,10 @@ interface ITaskFragmentOrganizerController { * Unregisters remote animations per transition type for the organizer. */ void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer); + + /** + * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and + * only occupies a portion of Task bounds. + */ + boolean isActivityEmbedded(in IBinder activityToken); } diff --git a/core/java/android/window/PictureInPictureSurfaceTransaction.java b/core/java/android/window/PictureInPictureSurfaceTransaction.java index dbf7eb34e273..2bf2f3193789 100644 --- a/core/java/android/window/PictureInPictureSurfaceTransaction.java +++ b/core/java/android/window/PictureInPictureSurfaceTransaction.java @@ -19,6 +19,7 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Matrix; +import android.graphics.PointF; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; @@ -34,9 +35,10 @@ import java.util.Objects; * @hide */ public final class PictureInPictureSurfaceTransaction implements Parcelable { + private static final float NOT_SET = -1f; - public final float mPositionX; - public final float mPositionY; + public final float mAlpha; + public final PointF mPosition; public final float[] mFloat9; @@ -45,33 +47,37 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { public final float mCornerRadius; - private final Rect mWindowCrop = new Rect(); + private final Rect mWindowCrop; - public PictureInPictureSurfaceTransaction(Parcel in) { - mPositionX = in.readFloat(); - mPositionY = in.readFloat(); + private PictureInPictureSurfaceTransaction(Parcel in) { + mAlpha = in.readFloat(); + mPosition = in.readTypedObject(PointF.CREATOR); mFloat9 = new float[9]; in.readFloatArray(mFloat9); mRotation = in.readFloat(); mCornerRadius = in.readFloat(); - mWindowCrop.set(Objects.requireNonNull(in.readTypedObject(Rect.CREATOR))); + mWindowCrop = in.readTypedObject(Rect.CREATOR); } - public PictureInPictureSurfaceTransaction(float positionX, float positionY, - float[] float9, float rotation, float cornerRadius, + private PictureInPictureSurfaceTransaction(float alpha, @Nullable PointF position, + @Nullable float[] float9, float rotation, float cornerRadius, @Nullable Rect windowCrop) { - mPositionX = positionX; - mPositionY = positionY; - mFloat9 = Arrays.copyOf(float9, 9); - mRotation = rotation; - mCornerRadius = cornerRadius; - if (windowCrop != null) { - mWindowCrop.set(windowCrop); + mAlpha = alpha; + mPosition = position; + if (float9 == null) { + mFloat9 = new float[9]; + Matrix.IDENTITY_MATRIX.getValues(mFloat9); + mRotation = 0; + } else { + mFloat9 = Arrays.copyOf(float9, 9); + mRotation = rotation; } + mCornerRadius = cornerRadius; + mWindowCrop = (windowCrop == null) ? null : new Rect(windowCrop); } public PictureInPictureSurfaceTransaction(PictureInPictureSurfaceTransaction other) { - this(other.mPositionX, other.mPositionY, + this(other.mAlpha, other.mPosition, other.mFloat9, other.mRotation, other.mCornerRadius, other.mWindowCrop); } @@ -82,13 +88,18 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { return matrix; } + /** @return {@code true} if this transaction contains setting corner radius. */ + public boolean hasCornerRadiusSet() { + return mCornerRadius > 0; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PictureInPictureSurfaceTransaction)) return false; PictureInPictureSurfaceTransaction that = (PictureInPictureSurfaceTransaction) o; - return Objects.equals(mPositionX, that.mPositionX) - && Objects.equals(mPositionY, that.mPositionY) + return Objects.equals(mAlpha, that.mAlpha) + && Objects.equals(mPosition, that.mPosition) && Arrays.equals(mFloat9, that.mFloat9) && Objects.equals(mRotation, that.mRotation) && Objects.equals(mCornerRadius, that.mCornerRadius) @@ -97,7 +108,7 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { @Override public int hashCode() { - return Objects.hash(mPositionX, mPositionY, Arrays.hashCode(mFloat9), + return Objects.hash(mAlpha, mPosition, Arrays.hashCode(mFloat9), mRotation, mCornerRadius, mWindowCrop); } @@ -108,8 +119,8 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeFloat(mPositionX); - out.writeFloat(mPositionY); + out.writeFloat(mAlpha); + out.writeTypedObject(mPosition, 0 /* flags */); out.writeFloatArray(mFloat9); out.writeFloat(mRotation); out.writeFloat(mCornerRadius); @@ -120,8 +131,8 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { public String toString() { final Matrix matrix = getMatrix(); return "PictureInPictureSurfaceTransaction(" - + " posX=" + mPositionX - + " posY=" + mPositionY + + " alpha=" + mAlpha + + " position=" + mPosition + " matrix=" + matrix.toShortString() + " rotation=" + mRotation + " cornerRadius=" + mCornerRadius @@ -134,11 +145,20 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { @NonNull SurfaceControl surfaceControl, @NonNull SurfaceControl.Transaction tx) { final Matrix matrix = surfaceTransaction.getMatrix(); - tx.setMatrix(surfaceControl, matrix, new float[9]) - .setPosition(surfaceControl, - surfaceTransaction.mPositionX, surfaceTransaction.mPositionY) - .setWindowCrop(surfaceControl, surfaceTransaction.mWindowCrop) - .setCornerRadius(surfaceControl, surfaceTransaction.mCornerRadius); + tx.setMatrix(surfaceControl, matrix, new float[9]); + if (surfaceTransaction.mPosition != null) { + tx.setPosition(surfaceControl, + surfaceTransaction.mPosition.x, surfaceTransaction.mPosition.y); + } + if (surfaceTransaction.mWindowCrop != null) { + tx.setWindowCrop(surfaceControl, surfaceTransaction.mWindowCrop); + } + if (surfaceTransaction.hasCornerRadiusSet()) { + tx.setCornerRadius(surfaceControl, surfaceTransaction.mCornerRadius); + } + if (surfaceTransaction.mAlpha != NOT_SET) { + tx.setAlpha(surfaceControl, surfaceTransaction.mAlpha); + } } public static final @android.annotation.NonNull Creator<PictureInPictureSurfaceTransaction> @@ -151,4 +171,44 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { return new PictureInPictureSurfaceTransaction[size]; } }; + + public static class Builder { + private float mAlpha = NOT_SET; + private PointF mPosition; + private float[] mFloat9; + private float mRotation; + private float mCornerRadius = NOT_SET; + private Rect mWindowCrop; + + public Builder setAlpha(float alpha) { + mAlpha = alpha; + return this; + } + + public Builder setPosition(float x, float y) { + mPosition = new PointF(x, y); + return this; + } + + public Builder setTransform(@NonNull float[] float9, float rotation) { + mFloat9 = Arrays.copyOf(float9, 9); + mRotation = rotation; + return this; + } + + public Builder setCornerRadius(float cornerRadius) { + mCornerRadius = cornerRadius; + return this; + } + + public Builder setWindowCrop(@NonNull Rect windowCrop) { + mWindowCrop = new Rect(windowCrop); + return this; + } + + public PictureInPictureSurfaceTransaction build() { + return new PictureInPictureSurfaceTransaction(mAlpha, mPosition, + mFloat9, mRotation, mCornerRadius, mWindowCrop); + } + } } diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index b9bf009fb2bb..090dbff488e9 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -43,6 +43,11 @@ import java.util.ArrayList; */ public interface SplashScreen { /** + * The splash screen style is not defined. + * @hide + */ + int SPLASH_SCREEN_STYLE_UNDEFINED = -1; + /** * Force splash screen to be empty. * @hide */ @@ -55,6 +60,7 @@ public interface SplashScreen { /** @hide */ @IntDef(prefix = { "SPLASH_SCREEN_STYLE_" }, value = { + SPLASH_SCREEN_STYLE_UNDEFINED, SPLASH_SCREEN_STYLE_EMPTY, SPLASH_SCREEN_STYLE_ICON }) diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 7e7d37083b5b..9c2fde04e4d2 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -216,4 +216,17 @@ public class TaskFragmentOrganizer extends WindowOrganizer { return null; } } + + /** + * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and + * only occupies a portion of Task bounds. + * @hide + */ + public boolean isActivityEmbedded(@NonNull IBinder activityToken) { + try { + return getController().isActivityEmbedded(activityToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 786af5f0823e..7bb1ed8abc0a 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -163,9 +163,6 @@ public class ChooserActivity extends ResolverActivity implements private AppPredictor mWorkAppPredictor; private boolean mShouldDisplayLandscape; - private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4; - private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8; - @UnsupportedAppUsage public ChooserActivity() { } @@ -275,6 +272,7 @@ public class ChooserActivity extends ResolverActivity implements private int mCurrAvailableWidth = 0; private int mLastNumberOfChildren = -1; + private int mMaxTargetsPerRow = 1; private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment"; @@ -741,8 +739,9 @@ public class ChooserActivity extends ResolverActivity implements mCallerChooserTargets = targets; } - mShouldDisplayLandscape = shouldDisplayLandscape( - getResources().getConfiguration().orientation); + mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row); + mShouldDisplayLandscape = + shouldDisplayLandscape(getResources().getConfiguration().orientation); setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false)); super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, null, false); @@ -916,7 +915,7 @@ public class ChooserActivity extends ResolverActivity implements adapter, getPersonalProfileUserHandle(), /* workProfileUserHandle= */ null, - isSendAction(getTargetIntent()), getMaxTargetsPerRow()); + isSendAction(getTargetIntent()), mMaxTargetsPerRow); } private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles( @@ -945,7 +944,7 @@ public class ChooserActivity extends ResolverActivity implements selectedProfile, getPersonalProfileUserHandle(), getWorkProfileUserHandle(), - isSendAction(getTargetIntent()), getMaxTargetsPerRow()); + isSendAction(getTargetIntent()), mMaxTargetsPerRow); } private int findSelectedProfile() { @@ -1107,6 +1106,7 @@ public class ChooserActivity extends ResolverActivity implements } mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation); + mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row); adjustPreviewWidth(newConfig.orientation, null); updateStickyContentPreview(); } @@ -2690,7 +2690,7 @@ public class ChooserActivity extends ResolverActivity implements // and b/150936654 recyclerView.setAdapter(gridAdapter); ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount( - getMaxTargetsPerRow()); + mMaxTargetsPerRow); } UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle(); @@ -2855,7 +2855,7 @@ public class ChooserActivity extends ResolverActivity implements @Override // ChooserListCommunicator public int getMaxRankedTargets() { - return getMaxTargetsPerRow(); + return mMaxTargetsPerRow; } @Override // ChooserListCommunicator @@ -3203,13 +3203,6 @@ public class ChooserActivity extends ResolverActivity implements } } - int getMaxTargetsPerRow() { - int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT; - if (mShouldDisplayLandscape) { - maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE; - } - return maxTargets; - } /** * Adapter for all types of items and targets in ShareSheet. * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the @@ -3277,7 +3270,11 @@ public class ChooserActivity extends ResolverActivity implements return false; } - int newWidth = width / getMaxTargetsPerRow(); + // Limit width to the maximum width of the chooser activity + int maxWidth = getResources().getDimensionPixelSize(R.dimen.chooser_width); + width = Math.min(maxWidth, width); + + int newWidth = width / mMaxTargetsPerRow; if (newWidth != mChooserTargetWidth) { mChooserTargetWidth = newWidth; return true; @@ -3312,7 +3309,7 @@ public class ChooserActivity extends ResolverActivity implements + getAzLabelRowCount() + Math.ceil( (float) mChooserListAdapter.getAlphaTargetCount() - / getMaxTargetsPerRow()) + / mMaxTargetsPerRow) ); } @@ -3352,7 +3349,7 @@ public class ChooserActivity extends ResolverActivity implements public int getCallerAndRankedTargetRowCount() { return (int) Math.ceil( ((float) mChooserListAdapter.getCallerTargetCount() - + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow()); + + mChooserListAdapter.getRankedTargetCount()) / mMaxTargetsPerRow); } // There can be at most one row in the listview, that is internally @@ -3551,7 +3548,7 @@ public class ChooserActivity extends ResolverActivity implements parentGroup.addView(row2); mDirectShareViewHolder = new DirectShareViewHolder(parentGroup, - Lists.newArrayList(row1, row2), getMaxTargetsPerRow(), viewType); + Lists.newArrayList(row1, row2), mMaxTargetsPerRow, viewType); loadViewsIntoGroup(mDirectShareViewHolder); return mDirectShareViewHolder; @@ -3559,7 +3556,7 @@ public class ChooserActivity extends ResolverActivity implements ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent, false); ItemGroupViewHolder holder = - new SingleRowViewHolder(row, getMaxTargetsPerRow(), viewType); + new SingleRowViewHolder(row, mMaxTargetsPerRow, viewType); loadViewsIntoGroup(holder); return holder; @@ -3651,7 +3648,7 @@ public class ChooserActivity extends ResolverActivity implements final int serviceCount = mChooserListAdapter.getServiceTargetCount(); final int serviceRows = (int) Math.ceil((float) serviceCount / getMaxRankedTargets()); if (position < serviceRows) { - return position * getMaxTargetsPerRow(); + return position * mMaxTargetsPerRow; } position -= serviceRows; @@ -3660,7 +3657,7 @@ public class ChooserActivity extends ResolverActivity implements + mChooserListAdapter.getRankedTargetCount(); final int callerAndRankedRows = getCallerAndRankedTargetRowCount(); if (position < callerAndRankedRows) { - return serviceCount + position * getMaxTargetsPerRow(); + return serviceCount + position * mMaxTargetsPerRow; } position -= getAzLabelRowCount() + callerAndRankedRows; @@ -3673,7 +3670,7 @@ public class ChooserActivity extends ResolverActivity implements if (mDirectShareViewHolder != null && canExpandDirectShare) { mDirectShareViewHolder.handleScroll( mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy, - getMaxTargetsPerRow()); + mMaxTargetsPerRow); } } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 424632fe82eb..6541b14b9070 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -270,8 +270,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Drawable mCaptionBackgroundDrawable; private Drawable mUserCaptionBackgroundDrawable; - private float mAvailableWidth; - String mLogTag = TAG; private final Rect mFloatingInsets = new Rect(); private boolean mApplyFloatingVerticalInsets = false; @@ -315,8 +313,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mSemiTransparentBarColor = context.getResources().getColor( R.color.system_bar_background_semi_transparent, null /* theme */); - updateAvailableWidth(); - setWindow(window); updateLogTag(params); @@ -697,7 +693,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); + final Resources res = getContext().getResources(); + final DisplayMetrics metrics = res.getDisplayMetrics(); final boolean isPortrait = getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; @@ -767,17 +764,19 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (!fixedWidth && widthMode == AT_MOST) { final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor; + final float availableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + res.getConfiguration().screenWidthDp, metrics); if (tv.type != TypedValue.TYPE_NULL) { final int min; if (tv.type == TypedValue.TYPE_DIMENSION) { - min = (int)tv.getDimension(metrics); + min = (int) tv.getDimension(metrics); } else if (tv.type == TypedValue.TYPE_FRACTION) { - min = (int)tv.getFraction(mAvailableWidth, mAvailableWidth); + min = (int) tv.getFraction(availableWidth, availableWidth); } else { min = 0; } if (DEBUG_MEASURE) Log.d(mLogTag, "Adjust for min width: " + min + ", value::" - + tv.coerceToString() + ", mAvailableWidth=" + mAvailableWidth); + + tv.coerceToString() + ", mAvailableWidth=" + availableWidth); if (width < min) { widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY); @@ -2144,7 +2143,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind updateDecorCaptionStatus(newConfig); - updateAvailableWidth(); initializeElevation(); } @@ -2616,12 +2614,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mLogTag = TAG + "[" + getTitleSuffix(params) + "]"; } - private void updateAvailableWidth() { - Resources res = getResources(); - mAvailableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - res.getConfiguration().screenWidthDp, res.getDisplayMetrics()); - } - /** * @hide */ diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index c750eadf845a..6faa04690473 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -366,6 +366,7 @@ message ActivityRecordProto { optional bool pip_auto_enter_enabled = 31; optional bool in_size_compat_mode = 32; optional float min_aspect_ratio = 33; + optional bool provides_max_bounds = 34; } /* represents WindowToken */ diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml index 90caaccfbff4..933b4d243df9 100644 --- a/core/res/res/layout/chooser_grid.xml +++ b/core/res/res/layout/chooser_grid.xml @@ -20,8 +20,10 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_gravity="center" android:maxCollapsedHeight="0dp" android:maxCollapsedHeightSmall="56dp" + android:maxWidth="@dimen/chooser_width" android:id="@id/contentPanel"> <RelativeLayout diff --git a/core/res/res/layout/chooser_grid_preview_image.xml b/core/res/res/layout/chooser_grid_preview_image.xml index 0d04d7f319b8..52692b03a44d 100644 --- a/core/res/res/layout/chooser_grid_preview_image.xml +++ b/core/res/res/layout/chooser_grid_preview_image.xml @@ -34,7 +34,7 @@ <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView" android:id="@+id/content_preview_image_1_large" android:layout_width="120dp" - android:layout_height="140dp" + android:layout_height="104dp" android:layout_alignParentTop="true" android:adjustViewBounds="true" android:gravity="center" @@ -44,7 +44,7 @@ android:id="@+id/content_preview_image_2_large" android:visibility="gone" android:layout_width="120dp" - android:layout_height="140dp" + android:layout_height="104dp" android:layout_alignParentTop="true" android:layout_toRightOf="@id/content_preview_image_1_large" android:layout_marginLeft="10dp" diff --git a/core/res/res/values-sw600dp/config.xml b/core/res/res/values-sw600dp/config.xml index 861e329f2de9..624581aba7dd 100644 --- a/core/res/res/values-sw600dp/config.xml +++ b/core/res/res/values-sw600dp/config.xml @@ -56,5 +56,7 @@ to be aligned to one side of the screen when in landscape mode. --> <bool name="config_enableDynamicKeyguardPositioning">true</bool> + <integer name="config_chooser_max_targets_per_row">6</integer> + </resources> diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml index 02ed848fc7f1..e8f15fd022d7 100644 --- a/core/res/res/values-sw600dp/dimens.xml +++ b/core/res/res/values-sw600dp/dimens.xml @@ -110,4 +110,7 @@ <dimen name="immersive_mode_cling_width">380dp</dimen> <dimen name="floating_toolbar_preferred_width">544dp</dimen> + + <dimen name="chooser_width">624dp</dimen> + </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 4d851644903e..1c31b1b76f85 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5284,4 +5284,6 @@ <string name="config_work_badge_path_24" translatable="false"> M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2z </string> + + <integer name="config_chooser_max_targets_per_row">4</integer> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 0706d8a8e4c6..f331f1ab720f 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -900,7 +900,8 @@ <dimen name="seekbar_thumb_exclusion_max_size">48dp</dimen> <!-- chooser/resolver (sharesheet) spacing --> - <dimen name="chooser_corner_radius">16dp</dimen> + <dimen name="chooser_width">412dp</dimen> + <dimen name="chooser_corner_radius">28dp</dimen> <dimen name="chooser_row_text_option_translate">25dp</dimen> <dimen name="chooser_view_spacing">18dp</dimen> <dimen name="chooser_edge_margin_thin">16dp</dimen> @@ -917,7 +918,7 @@ <dimen name="resolver_icon_size">32dp</dimen> <dimen name="resolver_button_bar_spacing">0dp</dimen> <dimen name="resolver_badge_size">18dp</dimen> - <dimen name="resolver_icon_margin">16dp</dimen> + <dimen name="resolver_icon_margin">8dp</dimen> <dimen name="resolver_small_margin">18dp</dimen> <dimen name="resolver_edge_margin">24dp</dimen> <dimen name="resolver_elevation">1dp</dimen> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3c7f0267d943..166d6abd1809 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1666,6 +1666,8 @@ <string name="face_setup_notification_title">Set up Face Unlock</string> <!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] --> <string name="face_setup_notification_content">Unlock your phone by looking at it</string> + <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] --> + <string name="face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string> <!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] --> <string name="fingerprint_setup_notification_title">Set up more ways to unlock</string> <!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a23816e8174a..b017a30cb5f2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -275,6 +275,7 @@ <java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" /> <java-symbol type="bool" name="config_avoidGfxAccel" /> <java-symbol type="bool" name="config_bluetooth_address_validation" /> + <java-symbol type="integer" name="config_chooser_max_targets_per_row" /> <java-symbol type="bool" name="config_flipToScreenOffEnabled" /> <java-symbol type="integer" name="config_flipToScreenOffMaxLatencyMicros" /> <java-symbol type="bool" name="config_bluetooth_sco_off_call" /> @@ -2570,6 +2571,7 @@ <java-symbol type="string" name="face_recalibrate_notification_name" /> <java-symbol type="string" name="face_recalibrate_notification_title" /> <java-symbol type="string" name="face_recalibrate_notification_content" /> + <java-symbol type="string" name="face_sensor_privacy_enabled" /> <java-symbol type="string" name="face_error_unable_to_process" /> <java-symbol type="string" name="face_error_hw_not_available" /> <java-symbol type="string" name="face_error_no_space" /> @@ -2843,6 +2845,7 @@ <java-symbol type="layout" name="date_picker_month_item_material" /> <java-symbol type="id" name="month_view" /> <java-symbol type="integer" name="config_zen_repeat_callers_threshold" /> + <java-symbol type="dimen" name="chooser_width" /> <java-symbol type="dimen" name="chooser_corner_radius" /> <java-symbol type="string" name="chooser_no_direct_share_targets" /> <java-symbol type="drawable" name="chooser_row_layer_list" /> diff --git a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java index 0456029f45a0..98485c024a59 100644 --- a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java +++ b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java @@ -18,8 +18,7 @@ package android.content.pm; import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import android.annotation.Nullable; import android.platform.test.annotations.Presubmit; @@ -146,24 +145,17 @@ public final class ConstrainDisplayApisConfigTest { private static void testNeverConstrainDisplayApis(String packageName, long version, boolean expected) { - boolean result = ConstrainDisplayApisConfig.neverConstrainDisplayApis( - buildApplicationInfo(packageName, version)); - if (expected) { - assertTrue(result); - } else { - assertFalse(result); - } + ConstrainDisplayApisConfig config = new ConstrainDisplayApisConfig(); + assertEquals(expected, + config.getNeverConstrainDisplayApis(buildApplicationInfo(packageName, version))); } private static void testAlwaysConstrainDisplayApis(String packageName, long version, boolean expected) { - boolean result = ConstrainDisplayApisConfig.alwaysConstrainDisplayApis( - buildApplicationInfo(packageName, version)); - if (expected) { - assertTrue(result); - } else { - assertFalse(result); - } + ConstrainDisplayApisConfig config = new ConstrainDisplayApisConfig(); + + assertEquals(expected, + config.getAlwaysConstrainDisplayApis(buildApplicationInfo(packageName, version))); } private static ApplicationInfo buildApplicationInfo(String packageName, long version) { diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 756425eedabb..0b8dc3fe0dec 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -180,6 +180,7 @@ <assign-permission name="android.permission.WATCH_APPOPS" uid="cameraserver" /> <assign-permission name="android.permission.MANAGE_APP_OPS_MODES" uid="cameraserver" /> <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="cameraserver" /> + <assign-permission name="android.permission.REAL_GET_TASKS" uid="cameraserver" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" /> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index fe6c7ba3b24c..af19bd0a79ac 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -91,11 +91,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent, @Nullable Bundle options, @NonNull SplitRule sideRule, - @NonNull Consumer<Exception> failureCallback) { + @Nullable Consumer<Exception> failureCallback) { try { mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule); } catch (Exception e) { - failureCallback.accept(e); + if (failureCallback != null) { + failureCallback.accept(e); + } } } @@ -858,4 +860,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen launchingContainer.getTaskFragmentToken()); } } + + /** + * Checks if an activity is embedded and its presentation is customized by a + * {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds. + */ + public boolean isActivityEmbedded(@NonNull Activity activity) { + return mPresenter.isActivityEmbedded(activity.getActivityToken()); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index 8c8ef92b80dc..46bdf6d0e689 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -16,6 +16,7 @@ package androidx.window.extensions.embedding; +import static android.os.Process.THREAD_PRIORITY_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; @@ -29,7 +30,7 @@ import android.animation.Animator; import android.animation.ValueAnimator; import android.graphics.Rect; import android.os.Handler; -import android.os.Looper; +import android.os.HandlerThread; import android.os.RemoteException; import android.util.Log; import android.view.IRemoteAnimationFinishedCallback; @@ -50,10 +51,14 @@ import java.util.function.BiFunction; class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { private static final String TAG = "TaskFragAnimationRunner"; - private final Handler mHandler = new Handler(Looper.myLooper()); + private final Handler mHandler; private final TaskFragmentAnimationSpec mAnimationSpec; TaskFragmentAnimationRunner() { + HandlerThread animationThread = new HandlerThread( + "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY); + animationThread.start(); + mHandler = animationThread.getThreadHandler(); mAnimationSpec = new TaskFragmentAnimationSpec(mHandler); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index a1a53bc93781..4d2d0551d828 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -103,7 +103,7 @@ class TaskFragmentContainer { ActivityThread activityThread = ActivityThread.currentActivityThread(); for (IBinder token : mInfo.getActivities()) { Activity activity = activityThread.getActivity(token); - if (activity != null && !allActivities.contains(activity)) { + if (activity != null && !activity.isFinishing() && !allActivities.contains(activity)) { allActivities.add(activity); } } diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex d6678bf9b320..f54ab08d8a8a 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index eb512afa644d..101a55d8d367 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -191,6 +191,7 @@ public class PhonePipMenuController implements PipMenuController { mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), 0, SHELL_ROOT_LAYER_PIP); + setShellRootAccessibilityWindow(); } private void detachPipMenuView() { @@ -546,6 +547,10 @@ public class PhonePipMenuController implements PipMenuController { mListeners.forEach(l -> l.onPipMenuStateChangeFinish(menuState)); } mMenuState = menuState; + setShellRootAccessibilityWindow(); + } + + private void setShellRootAccessibilityWindow() { switch (mMenuState) { case MENU_STATE_NONE: mSystemWindows.setShellRootAccessibilityWindow(0, SHELL_ROOT_LAYER_PIP, null); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java index 0fbdf90fd9d5..92a359891003 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java @@ -120,6 +120,11 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge); mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height); + if (mTargetViewContainer != null) { + // init can be called multiple times, remove the old one from view hierarchy first. + cleanUpDismissTarget(); + } + mTargetView = new DismissCircleView(mContext); mTargetViewContainer = new FrameLayout(mContext); mTargetViewContainer.setBackgroundDrawable( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 82e827398bb7..da4bbe81a3e6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -189,7 +189,7 @@ public class PipMenuView extends FrameLayout { mEnterSplitButton = findViewById(R.id.enter_split); mEnterSplitButton.setAlpha(0); mEnterSplitButton.setOnClickListener(v -> { - if (mMenuContainer.getAlpha() != 0) { + if (mEnterSplitButton.getAlpha() != 0) { enterSplit(); } }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 05552aad2303..cf4e56e128bf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -102,6 +102,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, static final int EXIT_REASON_ROOT_TASK_VANISHED = 6; static final int EXIT_REASON_SCREEN_LOCKED = 7; static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8; + static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9; @IntDef(value = { EXIT_REASON_UNKNOWN, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, @@ -112,6 +113,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, EXIT_REASON_ROOT_TASK_VANISHED, EXIT_REASON_SCREEN_LOCKED, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP, + EXIT_REASON_CHILD_TASK_ENTER_PIP, }) @Retention(RetentionPolicy.SOURCE) @interface ExitReason{} @@ -406,6 +408,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return "APP_FINISHED"; case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW: return "APP_DOES_NOT_SUPPORT_MULTIWINDOW"; + case EXIT_REASON_CHILD_TASK_ENTER_PIP: + return "CHILD_TASK_ENTER_PIP"; default: return "unknown reason, reason int = " + exitReason; } 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 e30e6c537c93..dd538dc4602c 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 @@ -35,6 +35,7 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME; @@ -629,8 +630,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, }); mShouldUpdateRecents = false; - mSideStage.removeAllTasks(wct, childrenToTop == mSideStage); - mMainStage.deactivate(wct, childrenToTop == mMainStage); + // When the exit split-screen is caused by one of the task enters auto pip, + // we want the tasks to be put to bottom instead of top, otherwise it will end up + // a fullscreen plus a pinned task instead of pinned only at the end of the transition. + final boolean fromEnteringPip = exitReason == EXIT_REASON_CHILD_TASK_ENTER_PIP; + mSideStage.removeAllTasks(wct, !fromEnteringPip && childrenToTop == mSideStage); + mMainStage.deactivate(wct, !fromEnteringPip && childrenToTop == mMainStage); mTaskOrganizer.applyTransaction(wct); mSyncQueue.runInSync(t -> t .setWindowCrop(mMainStage.mRootLeash, null) @@ -660,6 +665,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, case EXIT_REASON_DRAG_DIVIDER: // Either of the split apps have finished case EXIT_REASON_APP_FINISHED: + // One of the children enters PiP + case EXIT_REASON_CHILD_TASK_ENTER_PIP: return true; default: return false; @@ -749,6 +756,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + private void onStageChildTaskEnterPip(StageListenerImpl stageListener, int taskId) { + exitSplitScreen(stageListener == mMainStageListener ? mMainStage : mSideStage, + EXIT_REASON_CHILD_TASK_ENTER_PIP); + } + private void updateRecentTasksSplitPair() { if (!mShouldUpdateRecents) { return; @@ -1437,6 +1449,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override + public void onChildTaskEnterPip(int taskId) { + StageCoordinator.this.onStageChildTaskEnterPip(this, taskId); + } + + @Override public void onRootTaskVanished() { reset(); StageCoordinator.this.onStageRootTaskVanished(this); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index cd10b9fde444..2c853c1c474c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; @@ -73,6 +74,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); + void onChildTaskEnterPip(int taskId); + void onRootTaskVanished(); void onNoLongerSupportMultiWindow(); @@ -256,6 +259,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { mChildrenTaskInfo.remove(taskId); mChildrenLeashes.remove(taskId); mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible); + if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { + mCallbacks.onChildTaskEnterPip(taskId); + } if (ENABLE_SHELL_TRANSITIONS) { // Status is managed/synchronized by the transition lifecycle. return; diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index d17c32817994..13aff3812767 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -384,7 +384,16 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::FindEntryByName( return base::unexpected(IOError::PAGES_MISSING); } - auto offset = dtohl(entry_offset_ptr.value()); + uint32_t offset; + uint16_t res_idx; + if (type->flags & ResTable_type::FLAG_SPARSE) { + auto sparse_entry = entry_offset_ptr.convert<ResTable_sparseTypeEntry>(); + offset = dtohs(sparse_entry->offset) * 4u; + res_idx = dtohs(sparse_entry->idx); + } else { + offset = dtohl(entry_offset_ptr.value()); + res_idx = entry_idx; + } if (offset != ResTable_type::NO_ENTRY) { auto entry = type.offset(dtohl(type->entriesStart) + offset).convert<ResTable_entry>(); if (!entry) { @@ -394,7 +403,7 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::FindEntryByName( if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) { // The package ID will be overridden by the caller (due to runtime assignment of package // IDs for shared libraries). - return make_resid(0x00, *type_idx + type_id_offset_ + 1, entry_idx); + return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx); } } } @@ -686,6 +695,12 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, std::unordered_set<uint32_t> finalized_ids; const auto lib_alias = child_chunk.header<ResTable_staged_alias_header>(); if (!lib_alias) { + LOG(ERROR) << "RES_TABLE_STAGED_ALIAS_TYPE is too small."; + return {}; + } + if ((child_chunk.data_size() / sizeof(ResTable_staged_alias_entry)) + < dtohl(lib_alias->count)) { + LOG(ERROR) << "RES_TABLE_STAGED_ALIAS_TYPE is too small to hold entries."; return {}; } const auto entry_begin = child_chunk.data_ptr().convert<ResTable_staged_alias_entry>(); diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index f356c8130080..d214e2dfef7b 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -95,6 +95,38 @@ TEST(LoadedArscTest, LoadSparseEntryApp) { ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value()); } +TEST(LoadedArscTest, FindSparseEntryApp) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc", + &contents)); + + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), + contents.length()); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_v26)); + ASSERT_THAT(package, NotNull()); + + const uint8_t type_index = get_type_id(sparse::R::string::only_v26) - 1; + const uint16_t entry_index = get_entry_id(sparse::R::string::only_v26); + + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); + + // Ensure that AAPT2 sparsely encoded the v26 config as expected. + auto type_entry = std::find_if( + type_spec->type_entries.begin(), type_spec->type_entries.end(), + [](const TypeSpec::TypeEntry& x) { return x.config.sdkVersion == 26; }); + ASSERT_NE(type_entry, type_spec->type_entries.end()); + ASSERT_NE(type_entry->type->flags & ResTable_type::FLAG_SPARSE, 0); + + // Test fetching a resource with only sparsely encoded configs by name. + auto id = package->FindEntryByName(u"string", u"only_v26"); + ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_v26, 0)); +} + TEST(LoadedArscTest, LoadSharedLibrary) { std::string contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc", diff --git a/libs/androidfw/tests/data/sparse/R.h b/libs/androidfw/tests/data/sparse/R.h index 243e74fac65a..2492dbf33f4a 100644 --- a/libs/androidfw/tests/data/sparse/R.h +++ b/libs/androidfw/tests/data/sparse/R.h @@ -27,21 +27,22 @@ struct R { struct integer { enum : uint32_t { foo_0 = 0x7f010000, - foo_1 = 0x7f010000, - foo_2 = 0x7f010000, - foo_3 = 0x7f010000, - foo_4 = 0x7f010000, - foo_5 = 0x7f010000, - foo_6 = 0x7f010000, - foo_7 = 0x7f010000, - foo_8 = 0x7f010000, - foo_9 = 0x7f010000, + foo_1 = 0x7f010001, + foo_2 = 0x7f010002, + foo_3 = 0x7f010003, + foo_4 = 0x7f010004, + foo_5 = 0x7f010005, + foo_6 = 0x7f010006, + foo_7 = 0x7f010007, + foo_8 = 0x7f010008, + foo_9 = 0x7f010009, }; }; struct string { enum : uint32_t { foo_999 = 0x7f0203e7, + only_v26 = 0x7f0203e8 }; }; }; diff --git a/libs/androidfw/tests/data/sparse/gen_strings.sh b/libs/androidfw/tests/data/sparse/gen_strings.sh index e7e1d603ea4e..4ea5468c7df9 100755 --- a/libs/androidfw/tests/data/sparse/gen_strings.sh +++ b/libs/androidfw/tests/data/sparse/gen_strings.sh @@ -14,5 +14,7 @@ do fi done echo "</resources>" >> $OUTPUT_default + +echo " <string name=\"only_v26\">only v26</string>" >> $OUTPUT_v26 echo "</resources>" >> $OUTPUT_v26 diff --git a/libs/androidfw/tests/data/sparse/not_sparse.apk b/libs/androidfw/tests/data/sparse/not_sparse.apk Binary files differindex 599a370dbfb1..b08a621195c0 100644 --- a/libs/androidfw/tests/data/sparse/not_sparse.apk +++ b/libs/androidfw/tests/data/sparse/not_sparse.apk diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml b/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml index b6f82997d18b..d116087ec3c0 100644 --- a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml +++ b/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml @@ -333,4 +333,5 @@ <string name="foo_993">9930</string> <string name="foo_996">9960</string> <string name="foo_999">9990</string> + <string name="only_v26">only v26</string> </resources> diff --git a/libs/androidfw/tests/data/sparse/sparse.apk b/libs/androidfw/tests/data/sparse/sparse.apk Binary files differindex 1f9bba31b0a1..9fd01fbf2941 100644 --- a/libs/androidfw/tests/data/sparse/sparse.apk +++ b/libs/androidfw/tests/data/sparse/sparse.apk diff --git a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java index 36c2bda8b03a..7f17d26b156a 100644 --- a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java +++ b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java @@ -18,7 +18,6 @@ package com.android.settingslib.activityembedding; import android.content.Context; import android.content.Intent; -import android.content.pm.ResolveInfo; import com.android.settingslib.utils.BuildCompatUtils; @@ -37,11 +36,10 @@ public class ActivityEmbeddingUtils { if (BuildCompatUtils.isAtLeastS()) { final Intent intent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY); intent.setPackage(PACKAGE_NAME_SETTINGS); - final ResolveInfo resolveInfo = - context.getPackageManager().resolveActivity(intent, 0 /* flags */); - return resolveInfo != null - && resolveInfo.activityInfo != null - && resolveInfo.activityInfo.enabled; + final boolean isEmbeddingActivityEnabled = + intent.resolveActivity(context.getPackageManager()) != null; + + return isEmbeddingActivityEnabled; } return false; } diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml index 2272a375fb83..2624a419e4a4 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml @@ -21,10 +21,10 @@ <dimen name="settingslib_switchbar_margin">16dp</dimen> <!-- Size of layout margin left --> - <dimen name="settingslib_switchbar_padding_left">24dp</dimen> + <dimen name="settingslib_switchbar_padding_left">20dp</dimen> <!-- Size of layout margin right --> - <dimen name="settingslib_switchbar_padding_right">16dp</dimen> + <dimen name="settingslib_switchbar_padding_right">20dp</dimen> <!-- Minimum width of switch --> <dimen name="settingslib_min_switch_width">52dp</dimen> diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml index 6362882e2332..157a54e3573d 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml @@ -24,7 +24,7 @@ <dimen name="settingslib_restricted_icon_margin_end">16dp</dimen> <!-- Size of title margin --> - <dimen name="settingslib_switch_title_margin">16dp</dimen> + <dimen name="settingslib_switch_title_margin">24dp</dimen> <!-- SwitchBar sub settings margin start / end --> <dimen name="settingslib_switchbar_subsettings_margin_start">72dp</dimen> diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java index 8b17be1e8ec9..dee68948a16e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java @@ -85,6 +85,7 @@ public class EnableZenModeDialog { @VisibleForTesting protected Context mContext; private final int mThemeResId; + private final boolean mCancelIsNeutral; @VisibleForTesting protected TextView mZenAlarmWarning; @VisibleForTesting @@ -101,8 +102,13 @@ public class EnableZenModeDialog { } public EnableZenModeDialog(Context context, int themeResId) { + this(context, themeResId, false /* cancelIsNeutral */); + } + + public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral) { mContext = context; mThemeResId = themeResId; + mCancelIsNeutral = cancelIsNeutral; } public AlertDialog createDialog() { @@ -115,7 +121,6 @@ public class EnableZenModeDialog { final AlertDialog.Builder builder = new AlertDialog.Builder(mContext, mThemeResId) .setTitle(R.string.zen_mode_settings_turn_on_dialog_title) - .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.zen_mode_enable_dialog_turn_on, new DialogInterface.OnClickListener() { @Override @@ -145,6 +150,12 @@ public class EnableZenModeDialog { } }); + if (mCancelIsNeutral) { + builder.setNeutralButton(R.string.cancel, null); + } else { + builder.setNegativeButton(R.string.cancel, null); + } + View contentView = getContentView(); bindConditions(forever()); builder.setView(contentView); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index bf0dc7bce5f9..1343895ed93d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -261,8 +261,6 @@ public class WifiStatusTracker { private void updateWifiState() { state = mWifiManager.getWifiState(); enabled = state == WifiManager.WIFI_STATE_ENABLED; - isCarrierMerged = false; - subId = 0; } private void updateRssi(int newRssi) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index db301f698753..aca4fe456195 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -25,6 +25,7 @@ import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT; import static android.provider.Settings.SET_ALL_RESULT_DISABLED; import static android.provider.Settings.SET_ALL_RESULT_FAILURE; import static android.provider.Settings.SET_ALL_RESULT_SUCCESS; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER; import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; @@ -3585,7 +3586,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 204; + private static final int SETTINGS_VERSION = 205; private final int mUserId; @@ -5227,6 +5228,30 @@ public class SettingsProvider extends ContentProvider { currentVersion = 204; } + if (currentVersion == 204) { + // Version 204: Reset the + // Secure#ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT as enabled + // status for showing the tooltips. + final SettingsState secureSettings = getSecureSettingsLocked(userId); + final Setting accessibilityButtonMode = secureSettings.getSettingLocked( + Secure.ACCESSIBILITY_BUTTON_MODE); + if (!accessibilityButtonMode.isNull() + && accessibilityButtonMode.getValue().equals( + String.valueOf(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU))) { + if (isGestureNavigateEnabled() + && hasValueInA11yButtonTargets(secureSettings)) { + secureSettings.insertSettingLocked( + Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, + /* enabled */ "1", + /* tag= */ null, + /* makeDefault= */ false, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } + + currentVersion = 205; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml b/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml deleted file mode 100644 index 620dd4891b9b..000000000000 --- a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2021 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. ---> -<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" - android:pathData="M 0, 0 C 0.1217, 0.0462, 0.15, 0.4686, 0.1667, 0.66 C 0.1834, 0.8878, 0.1667, 1, 1, 1" />
\ No newline at end of file diff --git a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml b/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml deleted file mode 100644 index a268abce0c27..000000000000 --- a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2021 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. ---> -<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" - android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1" />
\ No newline at end of file diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 702060338359..a0d335db92d6 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -21,6 +21,7 @@ import android.app.ActivityTaskManager import android.app.PendingIntent import android.app.TaskInfo import android.graphics.Matrix +import android.graphics.Path import android.graphics.Rect import android.graphics.RectF import android.os.Looper @@ -34,6 +35,7 @@ import android.view.SyncRtSurfaceTransactionApplier import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.view.animation.Interpolator import android.view.animation.PathInterpolator import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils @@ -45,16 +47,46 @@ private const val TAG = "ActivityLaunchAnimator" * A class that allows activities to be started in a seamless way from a view that is transforming * nicely into the starting window. */ -class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { +class ActivityLaunchAnimator( + private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS) +) { companion object { + @JvmField + val TIMINGS = LaunchAnimator.Timings( + totalDuration = 500L, + contentBeforeFadeOutDelay = 0L, + contentBeforeFadeOutDuration = 150L, + contentAfterFadeInDelay = 150L, + contentAfterFadeInDuration = 183L + ) + + val INTERPOLATORS = LaunchAnimator.Interpolators( + positionInterpolator = Interpolators.EMPHASIZED, + positionXInterpolator = createPositionXInterpolator(), + contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN, + contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f) + ) + + /** Durations & interpolators for the navigation bar fading in & out. */ private const val ANIMATION_DURATION_NAV_FADE_IN = 266L private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L - private const val ANIMATION_DELAY_NAV_FADE_IN = - LaunchAnimator.ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN - private const val LAUNCH_TIMEOUT = 1000L + private val ANIMATION_DELAY_NAV_FADE_IN = + TIMINGS.totalDuration - ANIMATION_DURATION_NAV_FADE_IN - private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f) + private val NAV_FADE_IN_INTERPOLATOR = Interpolators.STANDARD_DECELERATE private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f) + + /** The time we wait before timing out the remote animation after starting the intent. */ + private const val LAUNCH_TIMEOUT = 1000L + + private fun createPositionXInterpolator(): Interpolator { + val path = Path().apply { + moveTo(0f, 0f) + cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f) + cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f) + } + return PathInterpolator(path) + } } /** @@ -107,8 +139,8 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { val animationAdapter = if (!hideKeyguardWithAnimation) { RemoteAnimationAdapter( runner, - LaunchAnimator.ANIMATION_DURATION, - LaunchAnimator.ANIMATION_DURATION - 150 /* statusBarTransitionDelay */ + TIMINGS.totalDuration, + TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */ ) } else { null @@ -436,7 +468,6 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { .withAlpha(1f) .withMatrix(matrix) .withWindowCrop(windowCrop) - .withLayer(window.prefixOrderIndex) .withCornerRadius(cornerRadius) .withVisibility(true) .build() @@ -449,7 +480,7 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { state: LaunchAnimator.State, linearProgress: Float ) { - val fadeInProgress = LaunchAnimator.getProgress(linearProgress, + val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT) val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash) @@ -464,7 +495,7 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { .withWindowCrop(windowCrop) .withVisibility(true) } else { - val fadeOutProgress = LaunchAnimator.getProgress(linearProgress, 0, + val fadeOutProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, 0, ANIMATION_DURATION_NAV_FADE_OUT) params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress)) } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index de82ebdc6b1c..066e169dcde3 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -20,7 +20,6 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.app.Dialog -import android.content.Context import android.graphics.Color import android.graphics.Rect import android.os.Looper @@ -28,10 +27,11 @@ import android.service.dreams.IDreamManager import android.util.Log import android.util.MathUtils import android.view.GhostView +import android.view.SurfaceControl import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewTreeObserver.OnPreDrawListener +import android.view.ViewRootImpl import android.view.WindowManager import android.widget.FrameLayout import kotlin.math.roundToInt @@ -42,12 +42,20 @@ private const val TAG = "DialogLaunchAnimator" * A class that allows dialogs to be started in a seamless way from a view that is transforming * nicely into the starting dialog. */ -class DialogLaunchAnimator( - private val context: Context, - private val launchAnimator: LaunchAnimator, - private val dreamManager: IDreamManager +class DialogLaunchAnimator @JvmOverloads constructor( + private val dreamManager: IDreamManager, + private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS), + private var isForTesting: Boolean = false ) { private companion object { + private val TIMINGS = ActivityLaunchAnimator.TIMINGS + + // We use the same interpolator for X and Y axis to make sure the dialog does not move out + // of the screen bounds during the animation. + private val INTERPOLATORS = ActivityLaunchAnimator.INTERPOLATORS.copy( + positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator + ) + private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running } @@ -96,14 +104,14 @@ class DialogLaunchAnimator( animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true) val animatedDialog = AnimatedDialog( - context, launchAnimator, dreamManager, animateFrom, onDialogDismissed = { openedDialogs.remove(it) }, dialog = dialog, animateBackgroundBoundsChange, - animatedParent + animatedParent, + isForTesting ) openedDialogs.add(animatedDialog) @@ -157,7 +165,6 @@ class DialogLaunchAnimator( } private class AnimatedDialog( - private val context: Context, private val launchAnimator: LaunchAnimator, private val dreamManager: IDreamManager, @@ -174,10 +181,16 @@ private class AnimatedDialog( val dialog: Dialog, /** Whether we should animate the dialog background when its bounds change. */ - private val animateBackgroundBoundsChange: Boolean, + animateBackgroundBoundsChange: Boolean, /** Launch animation corresponding to the parent [AnimatedDialog]. */ - private val parentAnimatedDialog: AnimatedDialog? = null + private val parentAnimatedDialog: AnimatedDialog? = null, + + /** + * Whether we are currently running in a test, in which case we need to disable + * synchronization. + */ + private val isForTesting: Boolean ) { /** * The DecorView of this dialog window. @@ -266,14 +279,14 @@ private class AnimatedDialog( // and the view that we added so that we can dismiss the dialog when this view is // clicked. This is necessary because DecorView overrides onTouchEvent and therefore we // can't set the click listener directly on the (now fullscreen) DecorView. - val fullscreenTransparentBackground = FrameLayout(context) + val fullscreenTransparentBackground = FrameLayout(dialog.context) decorView.addView( fullscreenTransparentBackground, 0 /* index */, FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) ) - val dialogContentWithBackground = FrameLayout(context) + val dialogContentWithBackground = FrameLayout(dialog.context) dialogContentWithBackground.background = decorView.background // Make the window background transparent. Note that setting the window (or DecorView) @@ -365,59 +378,77 @@ private class AnimatedDialog( // Show the dialog. dialog.show() - // Add a temporary touch surface ghost as soon as the window is ready to draw. This - // temporary ghost will be drawn together with the touch surface, but in the dialog - // window. Once it is drawn, we will make the touch surface invisible, and then start the - // animation. We do all this synchronization to avoid flicker that would occur if we made - // the touch surface invisible too early (before its ghost is drawn), leading to one or more - // frames with a hole instead of the touch surface (or its ghost). - decorView.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { - override fun onPreDraw(): Boolean { - decorView.viewTreeObserver.removeOnPreDrawListener(this) - addTemporaryTouchSurfaceGhost() - return true - } - }) - decorView.invalidate() + addTouchSurfaceGhost() } - private fun addTemporaryTouchSurfaceGhost() { + private fun addTouchSurfaceGhost() { + if (decorView.viewRootImpl == null) { + // Make sure that we have access to the dialog view root to synchronize the creation of + // the ghost. + decorView.post(::addTouchSurfaceGhost) + return + } + // Create a ghost of the touch surface (which will make the touch surface invisible) and add - // it to the dialog. We will wait for this ghost to be drawn before starting the animation. - val ghost = GhostView.addGhost(touchSurface, decorView) - - // The ghost of the touch surface was just created, so the touch surface was made invisible. - // We make it visible again until the ghost is actually drawn. - touchSurface.visibility = View.VISIBLE - - // Wait for the ghost to be drawn before continuing. - ghost.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { - override fun onPreDraw(): Boolean { - ghost.viewTreeObserver.removeOnPreDrawListener(this) - onTouchSurfaceGhostDrawn() - return true - } + // it to the host dialog. We trigger a one off synchronization to make sure that this is + // done in sync between the two different windows. + synchronizeNextDraw(then = { + isTouchSurfaceGhostDrawn = true + maybeStartLaunchAnimation() }) - ghost.invalidate() - } + GhostView.addGhost(touchSurface, decorView) - private fun onTouchSurfaceGhostDrawn() { - // Make the touch surface invisible and make sure that it stays invisible as long as the - // dialog is shown or animating. - touchSurface.visibility = View.INVISIBLE + // The ghost of the touch surface was just created, so the touch surface is currently + // invisible. We need to make sure that it stays invisible as long as the dialog is shown or + // animating. (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true) + } - // Add a pre draw listener to (maybe) start the animation once the touch surface is - // actually invisible. - touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { - override fun onPreDraw(): Boolean { - touchSurface.viewTreeObserver.removeOnPreDrawListener(this) - isTouchSurfaceGhostDrawn = true - maybeStartLaunchAnimation() - return true + /** + * Synchronize the next draw of the touch surface and dialog view roots so that they are + * performed at the same time, in the same transaction. This is necessary to make sure that the + * ghost of the touch surface is drawn at the same time as the touch surface is made invisible + * (or inversely, removed from the UI when the touch surface is made visible). + */ + private fun synchronizeNextDraw(then: () -> Unit) { + if (isForTesting || !touchSurface.isAttachedToWindow || touchSurface.viewRootImpl == null || + !decorView.isAttachedToWindow || decorView.viewRootImpl == null) { + // No need to synchronize if either the touch surface or dialog view is not attached + // to a window. + then() + return + } + + // Consume the next frames of both view roots to make sure the ghost view is drawn at + // exactly the same time as when the touch surface is made invisible. + var remainingTransactions = 0 + val mergedTransactions = SurfaceControl.Transaction() + + fun onTransaction(transaction: SurfaceControl.Transaction?) { + remainingTransactions-- + transaction?.let { mergedTransactions.merge(it) } + + if (remainingTransactions == 0) { + mergedTransactions.apply() + then() } - }) - touchSurface.invalidate() + } + + fun consumeNextDraw(viewRootImpl: ViewRootImpl) { + if (viewRootImpl.consumeNextDraw(::onTransaction)) { + remainingTransactions++ + + // Make sure we trigger a traversal. + viewRootImpl.view.invalidate() + } + } + + consumeNextDraw(touchSurface.viewRootImpl) + consumeNextDraw(decorView.viewRootImpl) + + if (remainingTransactions == 0) { + then() + } } private fun findFirstViewGroupWithBackground(view: View): ViewGroup? { @@ -483,7 +514,7 @@ private class AnimatedDialog( private fun onDialogDismissed() { if (Looper.myLooper() != Looper.getMainLooper()) { - context.mainExecutor.execute { onDialogDismissed() } + dialog.context.mainExecutor.execute { onDialogDismissed() } return } @@ -556,25 +587,12 @@ private class AnimatedDialog( .removeOnLayoutChangeListener(backgroundLayoutListener) } - // The animated ghost was just removed. We create a temporary ghost that will be - // removed only once we draw the touch surface, to avoid flickering that would - // happen when removing the ghost too early (before the touch surface is drawn). - GhostView.addGhost(touchSurface, decorView) - - touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { - override fun onPreDraw(): Boolean { - touchSurface.viewTreeObserver.removeOnPreDrawListener(this) - - // Now that the touch surface was drawn, we can remove the temporary ghost - // and instantly dismiss the dialog. - GhostView.removeGhost(touchSurface) - onAnimationFinished(true /* instantDismiss */) - onDialogDismissed(this@AnimatedDialog) - - return true - } + // Make sure that the removal of the ghost and making the touch surface visible is + // done at the same time. + synchronizeNextDraw(then = { + onAnimationFinished(true /* instantDismiss */) + onDialogDismissed(this@AnimatedDialog) }) - touchSurface.invalidate() } ) } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt index 3bf6c5ebd091..ebe96ebf2988 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt @@ -27,25 +27,19 @@ import android.util.Log import android.util.MathUtils import android.view.View import android.view.ViewGroup -import android.view.animation.AnimationUtils -import android.view.animation.PathInterpolator +import android.view.animation.Interpolator +import com.android.systemui.animation.Interpolators.LINEAR import kotlin.math.roundToInt private const val TAG = "LaunchAnimator" /** A base class to animate a window launch (activity or dialog) from a view . */ -class LaunchAnimator @JvmOverloads constructor( - context: Context, - private val isForTesting: Boolean = false +class LaunchAnimator( + private val timings: Timings, + private val interpolators: Interpolators ) { companion object { internal const val DEBUG = false - const val ANIMATION_DURATION = 500L - private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L - private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L - private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT - - private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f) private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC) /** @@ -53,23 +47,20 @@ class LaunchAnimator @JvmOverloads constructor( * sub-animation starting [delay] ms after the launch animation and that lasts [duration]. */ @JvmStatic - fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float { + fun getProgress( + timings: Timings, + linearProgress: Float, + delay: Long, + duration: Long + ): Float { return MathUtils.constrain( - (linearProgress * ANIMATION_DURATION - delay) / duration, + (linearProgress * timings.totalDuration - delay) / duration, 0.0f, 1.0f ) } } - /** The interpolator used for the width, height, Y position and corner radius. */ - private val animationInterpolator = AnimationUtils.loadInterpolator(context, - R.interpolator.launch_animation_interpolator_y) - - /** The interpolator used for the X position. */ - private val animationInterpolatorX = AnimationUtils.loadInterpolator(context, - R.interpolator.launch_animation_interpolator_x) - private val launchContainerLocation = IntArray(2) private val cornerRadii = FloatArray(8) @@ -159,6 +150,45 @@ class LaunchAnimator @JvmOverloads constructor( fun cancel() } + /** The timings (durations and delays) used by this animator. */ + class Timings( + /** The total duration of the animation. */ + val totalDuration: Long, + + /** The time to wait before fading out the expanding content. */ + val contentBeforeFadeOutDelay: Long, + + /** The duration of the expanding content fade out. */ + val contentBeforeFadeOutDuration: Long, + + /** + * The time to wait before fading in the expanded content (usually an activity or dialog + * window). + */ + val contentAfterFadeInDelay: Long, + + /** The duration of the expanded content fade in. */ + val contentAfterFadeInDuration: Long + ) + + /** The interpolators used by this animator. */ + data class Interpolators( + /** The interpolator used for the Y position, width, height and corner radius. */ + val positionInterpolator: Interpolator, + + /** + * The interpolator used for the X position. This can be different than + * [positionInterpolator] to create an arc-path during the animation. + */ + val positionXInterpolator: Interpolator = positionInterpolator, + + /** The interpolator used when fading out the expanding content. */ + val contentBeforeFadeOutInterpolator: Interpolator, + + /** The interpolator used when fading in the expanded content. */ + val contentAfterFadeInInterpolator: Interpolator + ) + /** * Start a launch animation controlled by [controller] towards [endState]. An intermediary * layer with [windowBackgroundColor] will fade in then fade out above the expanding view, and @@ -221,8 +251,8 @@ class LaunchAnimator @JvmOverloads constructor( // Update state. val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = if (isForTesting) 0 else ANIMATION_DURATION - animator.interpolator = Interpolators.LINEAR + animator.duration = timings.totalDuration + animator.interpolator = LINEAR val launchContainerOverlay = launchContainer.overlay var cancelled = false @@ -260,8 +290,8 @@ class LaunchAnimator @JvmOverloads constructor( // TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non // reversed animation. val linearProgress = animation.animatedFraction - val progress = animationInterpolator.getInterpolation(linearProgress) - val xProgress = animationInterpolatorX.getInterpolation(linearProgress) + val progress = interpolators.positionInterpolator.getInterpolation(linearProgress) + val xProgress = interpolators.positionXInterpolator.getInterpolation(linearProgress) val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress) val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f @@ -278,7 +308,12 @@ class LaunchAnimator @JvmOverloads constructor( // The expanding view can/should be hidden once it is completely covered by the opening // window. - state.visible = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1 + state.visible = getProgress( + timings, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration + ) < 1 applyStateToWindowBackgroundLayer( windowBackgroundLayer, @@ -337,14 +372,25 @@ class LaunchAnimator @JvmOverloads constructor( // We first fade in the background layer to hide the expanding view, then fade it out // with SRC mode to draw a hole punch in the status bar and reveal the opening window. - val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) + val fadeInProgress = getProgress( + timings, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration + ) if (fadeInProgress < 1) { - val alpha = Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(fadeInProgress) + val alpha = + interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress) drawable.alpha = (alpha * 0xFF).roundToInt() } else { val fadeOutProgress = getProgress( - linearProgress, ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW) - val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress) + timings, + linearProgress, + timings.contentAfterFadeInDelay, + timings.contentAfterFadeInDuration + ) + val alpha = + 1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress) drawable.alpha = (alpha * 0xFF).roundToInt() if (drawHole) { diff --git a/packages/SystemUI/res-keyguard/drawable/ic_unlocked_aod.xml b/packages/SystemUI/res-keyguard/drawable/ic_unlocked_aod.xml new file mode 100644 index 000000000000..230a25628c40 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_unlocked_aod.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46"> + <group android:name="_R_G_L_2_G" android:translateX="23" android:translateY="32.125"> + <path android:name="_R_G_L_2_G_D_0_P_0" + android:fillColor="#FF000000" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " /> + </group> + <group android:name="_R_G_L_1_G" android:translateX="23" android:translateY="32.125"> + <path android:name="_R_G_L_1_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:strokeAlpha="1" + android:pathData=" M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " /> + </group> + <group android:name="_R_G_L_0_G" android:translateX="14" android:translateY="13.5"> + <path android:name="_R_G_L_0_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:strokeAlpha="1" + android:pathData=" M21.25 14.88 C21.25,14.88 21.25,10.74 21.25,10.74 C21.25,8.59 19.5,7.29 17.44,7.21 C15.24,7.13 13.5,8.47 13.5,10.62 C13.5,10.62 13.5,15.75 13.5,15.75 " /> + </group> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml index c58e2e3266d0..67a70bb39964 100644 --- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml +++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml @@ -50,6 +50,11 @@ android:state_first="true" android:state_single="true" android:drawable="@drawable/ic_lock_aod" /> + <item + android:id="@+id/unlocked_aod" + android:state_last="true" + android:state_single="true" + android:drawable="@drawable/ic_unlocked_aod" /> <item android:id="@+id/no_icon" @@ -79,4 +84,14 @@ android:fromId="@id/locked" android:toId="@id/locked_aod" android:drawable="@drawable/lock_ls_to_aod" /> + + <transition + android:fromId="@id/unlocked_aod" + android:toId="@id/unlocked" + android:drawable="@drawable/unlocked_aod_to_ls" /> + + <transition + android:fromId="@id/unlocked" + android:toId="@id/unlocked_aod" + android:drawable="@drawable/unlocked_ls_to_aod" /> </animated-selector> diff --git a/packages/SystemUI/res-keyguard/drawable/unlocked_aod_to_ls.xml b/packages/SystemUI/res-keyguard/drawable/unlocked_aod_to_ls.xml new file mode 100644 index 000000000000..3b59ba8815b8 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/unlocked_aod_to_ls.xml @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2021 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. +--> +<animated-vector xmlns:aapt="http://schemas.android.com/aapt" + xmlns:android="http://schemas.android.com/apk/res/android"> + <aapt:attr name="android:drawable"> + <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46"> + <group android:name="_R_G"> + <group android:name="_R_G_L_2_G" android:translateX="23" android:translateY="32.125"> + <path android:name="_R_G_L_2_G_D_0_P_0" + android:fillColor="#FF000000" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " /> + </group> + <group android:name="_R_G_L_1_G" android:translateX="23" android:translateY="32.125"> + <path android:name="_R_G_L_1_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:strokeAlpha="1" + android:pathData=" M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " /> + </group> + <group android:name="_R_G_L_0_G" android:translateX="14" android:translateY="13.5"> + <path android:name="_R_G_L_0_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:strokeAlpha="1" + android:pathData=" M21.25 14.88 C21.25,14.88 21.25,10.74 21.25,10.74 C21.25,8.59 19.5,7.29 17.44,7.21 C15.24,7.13 13.5,8.47 13.5,10.62 C13.5,10.62 13.5,15.75 13.5,15.75 " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> + <target android:name="_R_G_L_2_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="333" android:startOffset="0" android:valueFrom="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " android:valueTo="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeWidth" + android:duration="333" + android:startOffset="0" + android:valueFrom="1.5" + android:valueTo="2" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.386,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" + android:startOffset="0" + android:valueFrom="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " android:valueTo="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeWidth" + android:duration="333" + android:startOffset="0" + android:valueFrom="1.5" + android:valueTo="2.5" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" + android:startOffset="0" + android:valueFrom="M21.25 14.88 C21.25,14.88 21.25,10.74 21.25,10.74 C21.25,8.59 19.5,7.29 17.44,7.21 C15.24,7.13 13.5,8.47 13.5,10.62 C13.5,10.62 13.5,15.75 13.5,15.75 " android:valueTo="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.347,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" + android:duration="517" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> +</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/drawable/unlocked_ls_to_aod.xml b/packages/SystemUI/res-keyguard/drawable/unlocked_ls_to_aod.xml new file mode 100644 index 000000000000..1c6d0b5193eb --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/unlocked_ls_to_aod.xml @@ -0,0 +1,136 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2021 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. +--> +<animated-vector xmlns:aapt="http://schemas.android.com/aapt" + xmlns:android="http://schemas.android.com/apk/res/android"> + <aapt:attr name="android:drawable"> + <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46"> + <group android:name="_R_G"> + <group android:name="_R_G_L_2_G" android:translateX="23" android:translateY="32.125"> + <path android:name="_R_G_L_2_G_D_0_P_0" + android:fillColor="#FF000000" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " /> + </group> + <group android:name="_R_G_L_1_G" android:translateX="23" android:translateY="32.125"> + <path android:name="_R_G_L_1_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:strokeAlpha="1" + android:pathData=" M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " /> + </group> + <group android:name="_R_G_L_0_G" android:translateX="14" android:translateY="13.5"> + <path android:name="_R_G_L_0_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2.5" + android:strokeAlpha="1" + android:pathData=" M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> + <target android:name="_R_G_L_2_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" + android:startOffset="0" + android:valueFrom="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " android:valueTo="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.347,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeWidth" + android:duration="333" + android:startOffset="0" + android:valueFrom="2" + android:valueTo="1.5" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.516,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" + android:startOffset="0" + android:valueFrom="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " android:valueTo="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.347,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeWidth" + android:duration="333" + android:startOffset="0" + android:valueFrom="2.5" + android:valueTo="1.5" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.516,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" + android:startOffset="0" + android:valueFrom="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " android:valueTo="M21.25 14.88 C21.25,14.88 21.25,10.74 21.25,10.74 C21.25,8.59 19.5,7.29 17.44,7.21 C15.24,7.13 13.5,8.47 13.5,10.62 C13.5,10.62 13.5,15.75 13.5,15.75 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.347,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" + android:duration="517" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> +</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml index e9bd638dfbf4..e80cfafdd71a 100644 --- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml @@ -25,6 +25,9 @@ <!-- Margin around the various security views --> <dimen name="keyguard_security_view_top_margin">12dp</dimen> + <!-- Padding for the lock icon on the keyguard --> + <dimen name="lock_icon_padding">16dp</dimen> + <!-- Overload default clock widget parameters --> <dimen name="widget_big_font_size">100dp</dimen> <dimen name="widget_label_font_size">18sp</dimen> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index 4fc38a813e8a..16010430df11 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -219,6 +219,9 @@ <!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] --> <string name="kg_face_not_recognized">Not recognized</string> + <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] --> + <string name="kg_face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string> + <!-- Instructions telling the user remaining times when enter SIM PIN view. --> <plurals name="kg_password_default_pin_message"> <item quantity="one">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining diff --git a/packages/SystemUI/res/drawable/ic_qs_drag_handle.xml b/packages/SystemUI/res/drawable/ic_qs_drag_handle.xml deleted file mode 100644 index 9a69b33fd591..000000000000 --- a/packages/SystemUI/res/drawable/ic_qs_drag_handle.xml +++ /dev/null @@ -1,24 +0,0 @@ -<!-- - ~ Copyright (C) 2021 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 - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="36dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="?android:attr/textColorPrimary" - android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17" /> -</vector> diff --git a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml deleted file mode 100644 index 50267fda0b25..000000000000 --- a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2021 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 - --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <stroke - android:color="?androidprv:attr/colorAccentPrimaryVariant" - android:width="1dp"/> - <corners android:radius="20dp"/> - <padding - android:left="8dp" - android:right="8dp" - android:top="4dp" - android:bottom="4dp" /> - <solid android:color="@android:color/transparent" /> -</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml index 363a022efdac..eb08434cbc54 100644 --- a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml +++ b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml @@ -13,17 +13,20 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" +<inset xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <stroke - android:color="?androidprv:attr/colorAccentPrimaryVariant" - android:width="1dp"/> - <corners android:radius="20dp"/> - <padding - android:left="16dp" - android:right="16dp" - android:top="8dp" - android:bottom="8dp" /> - <solid android:color="@android:color/transparent" /> -</shape> + android:insetBottom="6dp" + android:insetTop="6dp"> + <shape android:shape="rectangle"> + <stroke + android:color="?androidprv:attr/colorAccentPrimaryVariant" + android:width="1dp"/> + <corners android:radius="20dp"/> + <padding + android:left="16dp" + android:right="16dp" + android:top="8dp" + android:bottom="8dp"/> + <solid android:color="@android:color/transparent"/> + </shape> +</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml index 665b4564ebf1..a47299d6f854 100644 --- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml @@ -29,7 +29,7 @@ <shape android:shape="rectangle"> <corners android:radius="?android:attr/buttonCornerRadius"/> <solid android:color="@android:color/transparent"/> - <stroke android:color="?androidprv:attr/colorAccentPrimary" + <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant" android:width="1dp" /> <padding android:left="@dimen/dialog_button_horizontal_padding" diff --git a/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml b/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml deleted file mode 100644 index 59a31e8f6136..000000000000 --- a/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2021 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 - --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <stroke - android:color="?androidprv:attr/colorAccentPrimary" - android:width="1dp"/> - <corners android:radius="24dp"/> - <padding - android:left="16dp" - android:right="16dp" - android:top="8dp" - android:bottom="8dp" /> - <solid android:color="@android:color/transparent" /> -</shape> diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml index 3c9e44e2dba9..89690e8ff0ec 100644 --- a/packages/SystemUI/res/layout/auth_biometric_contents.xml +++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml @@ -73,6 +73,9 @@ android:accessibilityLiveRegion="polite" android:singleLine="true" android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" + android:scrollHorizontally="true" + android:fadingEdge="horizontal" android:textColor="@color/biometric_dialog_gray"/> <LinearLayout diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index e90a6446c47b..275e0a52ab45 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -37,7 +37,7 @@ android:ellipsize="end" android:gravity="center_vertical|center_horizontal" android:layout_width="wrap_content" - android:layout_height="32dp" + android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.InternetDialog" android:textSize="24sp"/> @@ -45,7 +45,7 @@ android:id="@+id/internet_dialog_subtitle" android:gravity="center_vertical|center_horizontal" android:layout_width="wrap_content" - android:layout_height="20dp" + android:layout_height="wrap_content" android:layout_marginTop="4dp" android:ellipsize="end" android:maxLines="1" @@ -67,7 +67,6 @@ <ProgressBar android:id="@+id/wifi_searching_progress" - android:indeterminate="true" android:layout_width="340dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" @@ -151,6 +150,7 @@ android:gravity="start|center_vertical"> <TextView android:id="@+id/mobile_title" + android:maxLines="1" style="@style/InternetDialog.NetworkTitle"/> <TextView android:id="@+id/mobile_summary" @@ -381,54 +381,44 @@ android:id="@+id/button_layout" android:orientation="horizontal" android:layout_width="match_parent" - android:layout_height="48dp" - android:layout_marginStart="24dp" - android:layout_marginEnd="24dp" + android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:layout_marginBottom="34dp" + android:layout_marginStart="@dimen/dialog_side_padding" + android:layout_marginEnd="@dimen/dialog_side_padding" + android:layout_marginBottom="@dimen/dialog_bottom_padding" android:clickable="false" android:focusable="false"> <FrameLayout android:id="@+id/apm_layout" android:layout_width="wrap_content" - android:layout_height="48dp" + android:layout_height="wrap_content" android:clickable="true" android:focusable="true" android:layout_gravity="start|center_vertical" android:orientation="vertical"> <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:text="@string/turn_off_airplane_mode" android:ellipsize="end" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="36dp" - android:layout_gravity="start|center_vertical" - android:textAppearance="@style/TextAppearance.InternetDialog" - android:textSize="14sp" - android:background="@drawable/internet_dialog_footer_background" + style="@style/Widget.Dialog.Button.BorderButton" android:clickable="false"/> </FrameLayout> <FrameLayout android:id="@+id/done_layout" android:layout_width="wrap_content" - android:layout_height="48dp" + android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:clickable="true" - android:focusable="true" android:layout_gravity="end|center_vertical" - android:orientation="vertical"> + android:clickable="true" + android:focusable="true"> <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:text="@string/inline_done_button" - android:ellipsize="end" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="67dp" - android:layout_height="36dp" - android:layout_gravity="end|center_vertical" - android:textAppearance="@style/TextAppearance.InternetDialog" - android:textSize="14sp" - android:background="@drawable/internet_dialog_footer_background" + style="@style/Widget.Dialog.Button" android:clickable="false"/> </FrameLayout> </FrameLayout> diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml index a64ef3ea1cc6..3a186d2cc8d2 100644 --- a/packages/SystemUI/res/layout/media_output_dialog.xml +++ b/packages/SystemUI/res/layout/media_output_dialog.xml @@ -24,9 +24,9 @@ <LinearLayout android:layout_width="match_parent" - android:layout_height="96dp" + android:layout_height="wrap_content" android:gravity="start|center_vertical" - android:paddingStart="16dp" + android:paddingStart="24dp" android:orientation="horizontal"> <ImageView android:id="@+id/header_icon" @@ -36,7 +36,7 @@ <LinearLayout android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:paddingStart="16dp" android:paddingTop="20dp" android:paddingBottom="24dp" @@ -59,7 +59,7 @@ android:gravity="center_vertical" android:ellipsize="end" android:maxLines="1" - android:textColor="?android:attr/textColorTertiary" + android:textColor="?android:attr/textColorSecondary" android:fontFamily="roboto-regular" android:textSize="16sp"/> </LinearLayout> @@ -89,17 +89,16 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" - android:layout_marginStart="24dp" - android:layout_marginBottom="18dp" - android:layout_marginEnd="24dp" + android:layout_marginStart="@dimen/dialog_side_padding" + android:layout_marginEnd="@dimen/dialog_side_padding" + android:layout_marginBottom="@dimen/dialog_bottom_padding" android:orientation="horizontal"> <Button android:id="@+id/stop" - style="@style/MediaOutputRoundedOutlinedButton" + style="@style/Widget.Dialog.Button.BorderButton" android:layout_width="wrap_content" - android:layout_height="36dp" - android:minWidth="0dp" + android:layout_height="wrap_content" android:text="@string/keyboard_key_media_stop" android:visibility="gone"/> @@ -110,10 +109,9 @@ <Button android:id="@+id/done" - style="@style/MediaOutputRoundedOutlinedButton" + style="@style/Widget.Dialog.Button" android:layout_width="wrap_content" - android:layout_height="36dp" - android:minWidth="0dp" + android:layout_height="wrap_content" android:text="@string/inline_done_button"/> </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index 2ac03c262edb..f5c6036a5a86 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -56,17 +56,4 @@ layout="@layout/qs_customize_panel" android:visibility="gone" /> - <ImageView - android:id="@+id/qs_drag_handle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginTop="24dp" - android:elevation="4dp" - android:importantForAccessibility="no" - android:scaleType="center" - android:src="@drawable/ic_qs_drag_handle" - android:tint="@color/qs_detail_button_white" - tools:ignore="UseAppTint" /> - </com.android.systemui.qs.QSContainerImpl> diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml index 6c5ad5060495..6012b58d0293 100644 --- a/packages/SystemUI/res/layout/screen_record_dialog.xml +++ b/packages/SystemUI/res/layout/screen_record_dialog.xml @@ -27,10 +27,10 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingStart="24dp" - android:paddingEnd="24dp" - android:paddingTop="26dp" - android:paddingBottom="30dp" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" + android:paddingTop="@dimen/dialog_top_padding" + android:paddingBottom="@dimen/dialog_bottom_padding" android:orientation="vertical"> <!-- Header --> @@ -108,10 +108,7 @@ android:layout_weight="0" android:layout_gravity="start" android:text="@string/cancel" - android:textColor="?android:textColorPrimary" - android:background="@drawable/screenrecord_button_background_outline" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="14sp"/> + style="@style/Widget.Dialog.Button.BorderButton" /> <Space android:layout_width="0dp" android:layout_height="match_parent" @@ -123,10 +120,7 @@ android:layout_weight="0" android:layout_gravity="end" android:text="@string/screenrecord_start" - android:textColor="@android:color/system_neutral1_900" - android:background="@drawable/screenrecord_button_background_solid" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="14sp"/> + style="@style/Widget.Dialog.Button" /> </LinearLayout> </LinearLayout> </ScrollView> diff --git a/packages/SystemUI/res/layout/status_bar_no_notifications.xml b/packages/SystemUI/res/layout/status_bar_no_notifications.xml index 332dc6ee8656..a2abdb211602 100644 --- a/packages/SystemUI/res/layout/status_bar_no_notifications.xml +++ b/packages/SystemUI/res/layout/status_bar_no_notifications.xml @@ -26,8 +26,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="64dp" - android:paddingTop="12dp" android:textAppearance="?android:attr/textAppearanceButton" - android:gravity="top|center_horizontal" + android:gravity="center" android:text="@string/empty_shade_text"/> </com.android.systemui.statusbar.EmptyShadeView> diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index b98694e2fae7..fcb369876cf2 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -77,7 +77,7 @@ <color name="biometric_dialog_error">#fff28b82</color> <!-- red 300 --> <!-- UDFPS colors --> - <color name="udfps_enroll_icon">#ffffff</color> <!-- 100% white --> + <color name="udfps_enroll_icon">#7DA7F1</color> <color name="GM2_green_500">#FF41Af6A</color> <color name="GM2_blue_500">#5195EA</color> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index 362e18d785ac..dabc3108458f 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -18,11 +18,14 @@ <!-- Max number of columns for quick controls area --> <integer name="controls_max_columns">2</integer> + <!-- The maximum number of rows in the QSPanel --> + <integer name="quick_settings_max_rows">3</integer> + <!-- The maximum number of rows in the QuickQSPanel --> - <integer name="quick_qs_panel_max_rows">4</integer> + <integer name="quick_qs_panel_max_rows">3</integer> <!-- The maximum number of tiles in the QuickQSPanel --> - <integer name="quick_qs_panel_max_tiles">8</integer> + <integer name="quick_qs_panel_max_tiles">6</integer> <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">true</bool> diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml index e4573c651039..e0b161456aa2 100644 --- a/packages/SystemUI/res/values-sw720dp-land/config.xml +++ b/packages/SystemUI/res/values-sw720dp-land/config.xml @@ -18,11 +18,14 @@ <!-- Max number of columns for quick controls area --> <integer name="controls_max_columns">2</integer> + <!-- The maximum number of rows in the QSPanel --> + <integer name="quick_settings_max_rows">3</integer> + <!-- The maximum number of rows in the QuickQSPanel --> - <integer name="quick_qs_panel_max_rows">4</integer> + <integer name="quick_qs_panel_max_rows">3</integer> <!-- The maximum number of tiles in the QuickQSPanel --> - <integer name="quick_qs_panel_max_tiles">8</integer> + <integer name="quick_qs_panel_max_tiles">6</integer> <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">true</bool> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 8a3bacae9243..e455aaa225be 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -134,10 +134,10 @@ <color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 --> <!-- UDFPS colors --> - <color name="udfps_enroll_icon">#000000</color> <!-- 100% black --> - <color name="udfps_moving_target_fill">#cc4285f4</color> <!-- 80% blue --> - <color name="udfps_enroll_progress">#ff669DF6</color> <!-- blue 400 --> - <color name="udfps_enroll_progress_help">#ffEE675C</color> <!-- red 400 --> + <color name="udfps_enroll_icon">#7DA7F1</color> + <color name="udfps_moving_target_fill">#475670</color> + <color name="udfps_enroll_progress">#7DA7F1</color> + <color name="udfps_enroll_progress_help">#ffEE675C</color> <!-- Global screenshot actions --> <color name="global_screenshot_button_ripple">#1f000000</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 475f70fa9fdc..e00b9410a8a7 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -595,17 +595,6 @@ 280 </integer> - <!-- Haptic feedback intensity for ticks used for the udfps dwell time --> - <item name="config_udfpsTickIntensity" translatable="false" format="float" - type="dimen">.5</item> - - <!-- Haptic feedback delay between ticks used for udfps dwell time --> - <integer name="config_udfpsTickDelay" translatable="false">25</integer> - - <!-- Haptic feedback tick type - if true, uses VibrationEffect.Composition.PRIMITIVE_LOW_TICK - else uses VibrationEffect.Composition.PRIMITIVE_TICK --> - <bool name="config_udfpsUseLowTick">true</bool> - <!-- package name of a built-in camera app to use to restrict implicit intent resolution when the double-press power gesture is used. Ignored if empty. --> <string translatable="false" name="config_cameraGesturePackage"></string> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4dc52e7540be..e1afd3fe6a30 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -630,7 +630,7 @@ <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] --> <string name="quick_settings_brightness_dialog_title">Brightness</string> <!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] --> - <string name="quick_settings_inversion_label">Invert colors</string> + <string name="quick_settings_inversion_label">Color inversion</string> <!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] --> <!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] --> <string name="quick_settings_more_settings">More settings</string> @@ -2037,8 +2037,8 @@ <string name="magnification_mode_switch_click_label">Switch</string> <!-- Accessibility floating menu strings --> - <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user the accessibility gesture had been replaced by accessibility floating button. [CHAR LIMIT=100] --> - <string name="accessibility_floating_button_migration_tooltip">Accessibility button replaced the accessibility gesture\n\n<annotation id="link">View settings</annotation></string> + <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user they could customize or replace the floating button in Settings. [CHAR LIMIT=100] --> + <string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string> <!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] --> <string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string> <!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 92985ded8a91..051a30ccb524 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -450,10 +450,6 @@ <item name="android:background">@drawable/btn_borderless_rect</item> </style> - <style name="MediaOutputRoundedOutlinedButton" parent="@android:style/Widget.Material.Button"> - <item name="android:background">@drawable/media_output_dialog_button_background</item> - </style> - <style name="TunerSettings" parent="@android:style/Theme.DeviceDefault.Settings"> <item name="android:windowActionBar">false</item> <item name="preferenceTheme">@style/TunerPreferenceTheme</item> @@ -881,13 +877,18 @@ <item name="android:textAlignment">center</item> </style> - <style name="Widget.Dialog.Button" parent = "Theme.SystemUI.Dialog"> + + <style name="Widget" /> + <style name="Widget.Dialog" /> + <style name="Widget.Dialog.Button"> + <item name="android:buttonCornerRadius">28dp</item> <item name="android:background">@drawable/qs_dialog_btn_filled</item> <item name="android:textColor">?androidprv:attr/textColorOnAccent</item> <item name="android:textSize">14sp</item> <item name="android:lineHeight">20sp</item> <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> <item name="android:stateListAnimator">@null</item> + <item name="android:minWidth">0dp</item> </style> <style name="Widget.Dialog.Button.BorderButton"> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java index 7d0fb5d38849..567e7aa3d78f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java @@ -53,8 +53,8 @@ public class PipSurfaceTransactionHelper { tx.setMatrix(leash, mTmpTransform, mTmpFloat9) .setPosition(leash, positionX, positionY) .setCornerRadius(leash, cornerRadius); - return new PictureInPictureSurfaceTransaction( - positionX, positionY, mTmpFloat9, 0 /* rotation */, cornerRadius, sourceBounds); + return newPipSurfaceTransaction(positionX, positionY, + mTmpFloat9, 0 /* rotation */, cornerRadius, sourceBounds); } public PictureInPictureSurfaceTransaction scale( @@ -70,8 +70,8 @@ public class PipSurfaceTransactionHelper { tx.setMatrix(leash, mTmpTransform, mTmpFloat9) .setPosition(leash, positionX, positionY) .setCornerRadius(leash, cornerRadius); - return new PictureInPictureSurfaceTransaction( - positionX, positionY, mTmpFloat9, degree, cornerRadius, sourceBounds); + return newPipSurfaceTransaction(positionX, positionY, + mTmpFloat9, degree, cornerRadius, sourceBounds); } public PictureInPictureSurfaceTransaction scaleAndCrop( @@ -93,8 +93,8 @@ public class PipSurfaceTransactionHelper { .setWindowCrop(leash, mTmpDestinationRect) .setPosition(leash, left, top) .setCornerRadius(leash, cornerRadius); - return new PictureInPictureSurfaceTransaction( - left, top, mTmpFloat9, 0 /* rotation */, cornerRadius, mTmpDestinationRect); + return newPipSurfaceTransaction(left, top, + mTmpFloat9, 0 /* rotation */, cornerRadius, mTmpDestinationRect); } public PictureInPictureSurfaceTransaction scaleAndRotate( @@ -125,8 +125,7 @@ public class PipSurfaceTransactionHelper { .setWindowCrop(leash, mTmpDestinationRect) .setPosition(leash, adjustedPositionX, adjustedPositionY) .setCornerRadius(leash, cornerRadius); - return new PictureInPictureSurfaceTransaction( - adjustedPositionX, adjustedPositionY, + return newPipSurfaceTransaction(adjustedPositionX, adjustedPositionY, mTmpFloat9, degree, cornerRadius, mTmpDestinationRect); } @@ -137,6 +136,17 @@ public class PipSurfaceTransactionHelper { return mCornerRadius * scale; } + private static PictureInPictureSurfaceTransaction newPipSurfaceTransaction( + float posX, float posY, float[] float9, float rotation, float cornerRadius, + Rect windowCrop) { + return new PictureInPictureSurfaceTransaction.Builder() + .setPosition(posX, posY) + .setTransform(float9, rotation) + .setCornerRadius(cornerRadius) + .setWindowCrop(windowCrop) + .build(); + } + /** @return {@link SurfaceControl.Transaction} instance with vsync-id */ public static SurfaceControl.Transaction newSurfaceControlTransaction() { final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt index e46b6f12e4a3..ea93a3b9375e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt @@ -60,14 +60,12 @@ fun createUnfoldTransitionProgressProvider( hingeAngleProvider, screenStatusProvider, deviceStateManager, - mainExecutor + mainExecutor, + mainHandler ) val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) { - PhysicsBasedUnfoldTransitionProgressProvider( - mainHandler, - foldStateProvider - ) + PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider) } else { FixedTimingTransitionProgressProvider(foldStateProvider) } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt index 90f5998053b8..51eae573f040 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt @@ -15,7 +15,6 @@ */ package com.android.systemui.unfold.progress -import android.os.Handler import android.util.Log import android.util.MathUtils.saturate import androidx.dynamicanimation.animation.DynamicAnimation @@ -24,9 +23,10 @@ import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.updates.FOLD_UPDATE_ABORTED import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED -import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING +import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate @@ -39,7 +39,6 @@ import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener * - doesn't handle postures */ internal class PhysicsBasedUnfoldTransitionProgressProvider( - private val handler: Handler, private val foldStateProvider: FoldStateProvider ) : UnfoldTransitionProgressProvider, @@ -51,8 +50,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider) } - private val timeoutRunnable = TimeoutRunnable() - private var isTransitionRunning = false private var isAnimatedCancelRunning = false @@ -92,7 +89,7 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( cancelTransition(endValue = 1f, animate = true) } } - FOLD_UPDATE_FINISH_FULL_OPEN -> { + FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_ABORTED -> { // Do not cancel if we haven't started the transition yet. // This could happen when we fully unfolded the device before the screen // became available. In this case we start and immediately cancel the animation @@ -106,7 +103,11 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( cancelTransition(endValue = 0f, animate = false) } FOLD_UPDATE_START_CLOSING -> { - startTransition(startValue = 1f) + // The transition might be already running as the device might start closing several + // times before reaching an end state. + if (!isTransitionRunning) { + startTransition(startValue = 1f) + } } } @@ -116,8 +117,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( } private fun cancelTransition(endValue: Float, animate: Boolean) { - handler.removeCallbacks(timeoutRunnable) - if (isTransitionRunning && animate) { isAnimatedCancelRunning = true springAnimation.animateToFinalPosition(endValue) @@ -175,8 +174,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( } springAnimation.start() - - handler.postDelayed(timeoutRunnable, TRANSITION_TIMEOUT_MILLIS) } override fun addCallback(listener: TransitionProgressListener) { @@ -187,13 +184,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( listeners.remove(listener) } - private inner class TimeoutRunnable : Runnable { - - override fun run() { - cancelTransition(endValue = 1f, animate = true) - } - } - private object AnimationProgressProperty : FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") { @@ -212,7 +202,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider" private const val DEBUG = true -private const val TRANSITION_TIMEOUT_MILLIS = 2000L private const val SPRING_STIFFNESS = 200.0f private const val MINIMAL_VISIBLE_CHANGE = 0.001f private const val FINAL_HINGE_ANGLE_POSITION = 165f diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index 35e2b30d0a39..6d9631c12430 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -15,14 +15,19 @@ */ package com.android.systemui.unfold.updates +import android.annotation.FloatRange import android.content.Context import android.hardware.devicestate.DeviceStateManager +import android.os.Handler +import android.util.Log +import androidx.annotation.VisibleForTesting import androidx.core.util.Consumer -import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener +import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES import com.android.systemui.unfold.updates.hinge.HingeAngleProvider +import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import java.util.concurrent.Executor class DeviceFoldStateProvider( @@ -30,7 +35,8 @@ class DeviceFoldStateProvider( private val hingeAngleProvider: HingeAngleProvider, private val screenStatusProvider: ScreenStatusProvider, private val deviceStateManager: DeviceStateManager, - private val mainExecutor: Executor + private val mainExecutor: Executor, + private val handler: Handler ) : FoldStateProvider { private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf() @@ -38,9 +44,13 @@ class DeviceFoldStateProvider( @FoldUpdate private var lastFoldUpdate: Int? = null + @FloatRange(from = 0.0, to = 180.0) + private var lastHingeAngle: Float = 0f + private val hingeAngleListener = HingeAngleListener() private val screenListener = ScreenStatusListener() private val foldStateListener = FoldStateListener(context) + private val timeoutRunnable = TimeoutRunnable() private var isFolded = false private var isUnfoldHandled = true @@ -72,47 +82,69 @@ class DeviceFoldStateProvider( override val isFullyOpened: Boolean get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN + private val isTransitionInProgess: Boolean + get() = lastFoldUpdate == FOLD_UPDATE_START_OPENING || + lastFoldUpdate == FOLD_UPDATE_START_CLOSING + private fun onHingeAngle(angle: Float) { - when (lastFoldUpdate) { - FOLD_UPDATE_FINISH_FULL_OPEN -> { - if (FULLY_OPEN_DEGREES - angle > START_CLOSING_THRESHOLD_DEGREES) { - lastFoldUpdate = FOLD_UPDATE_START_CLOSING - outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_CLOSING) } - } - } - FOLD_UPDATE_START_OPENING -> { - if (FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES) { - lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN - outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) } - } - } - FOLD_UPDATE_START_CLOSING -> { - if (FULLY_OPEN_DEGREES - angle < START_CLOSING_THRESHOLD_DEGREES) { - lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN - outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) } - } + if (DEBUG) { Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") } + + val isClosing = angle < lastHingeAngle + val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES + val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING + + if (isClosing && !closingEventDispatched && !isFullyOpened) { + notifyFoldUpdate(FOLD_UPDATE_START_CLOSING) + } + + if (isTransitionInProgess) { + if (isFullyOpened) { + notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) + cancelTimeout() + } else { + // The timeout will trigger some constant time after the last angle update. + rescheduleAbortAnimationTimeout() } } + lastHingeAngle = angle outputListeners.forEach { it.onHingeAngleUpdate(angle) } } private inner class FoldStateListener(context: Context) : DeviceStateManager.FoldStateListener(context, { folded: Boolean -> isFolded = folded + lastHingeAngle = FULLY_CLOSED_DEGREES if (folded) { - lastFoldUpdate = FOLD_UPDATE_FINISH_CLOSED - outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) } hingeAngleProvider.stop() + notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) + cancelTimeout() isUnfoldHandled = false } else { - lastFoldUpdate = FOLD_UPDATE_START_OPENING - outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_OPENING) } + notifyFoldUpdate(FOLD_UPDATE_START_OPENING) + rescheduleAbortAnimationTimeout() hingeAngleProvider.start() } }) + private fun notifyFoldUpdate(@FoldUpdate update: Int) { + if (DEBUG) { Log.d(TAG, stateToString(update)) } + outputListeners.forEach { it.onFoldUpdate(update) } + lastFoldUpdate = update + } + + private fun rescheduleAbortAnimationTimeout() { + if (isTransitionInProgess) { + cancelTimeout() + } + handler.postDelayed(timeoutRunnable, ABORT_CLOSING_MILLIS) + } + + private fun cancelTimeout() { + handler.removeCallbacks(timeoutRunnable) + } + private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener { @@ -136,7 +168,39 @@ class DeviceFoldStateProvider( onHingeAngle(angle) } } + + private inner class TimeoutRunnable : Runnable { + + override fun run() { + notifyFoldUpdate(FOLD_UPDATE_ABORTED) + } + } +} + +private fun stateToString(@FoldUpdate update: Int): String { + return when (update) { + FOLD_UPDATE_START_OPENING -> "START_OPENING" + FOLD_UPDATE_HALF_OPEN -> "HALF_OPEN" + FOLD_UPDATE_START_CLOSING -> "START_CLOSING" + FOLD_UPDATE_ABORTED -> "ABORTED" + FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE" + FOLD_UPDATE_FINISH_HALF_OPEN -> "FINISH_HALF_OPEN" + FOLD_UPDATE_FINISH_FULL_OPEN -> "FINISH_FULL_OPEN" + FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED" + else -> "UNKNOWN" + } } -private const val START_CLOSING_THRESHOLD_DEGREES = 95f -private const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file +private const val TAG = "DeviceFoldProvider" +private const val DEBUG = false + +/** + * Time after which [FOLD_UPDATE_ABORTED] is emitted following a [FOLD_UPDATE_START_CLOSING] or + * [FOLD_UPDATE_START_OPENING] event, if an end state is not reached. + */ +@VisibleForTesting +const val ABORT_CLOSING_MILLIS = 1000L + +/** Threshold after which we consider the device fully unfolded. */ +@VisibleForTesting +const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt index 643ece353522..bffebcd4512b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt @@ -39,6 +39,7 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { FOLD_UPDATE_START_OPENING, FOLD_UPDATE_HALF_OPEN, FOLD_UPDATE_START_CLOSING, + FOLD_UPDATE_ABORTED, FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, FOLD_UPDATE_FINISH_HALF_OPEN, FOLD_UPDATE_FINISH_FULL_OPEN, @@ -51,7 +52,8 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { const val FOLD_UPDATE_START_OPENING = 0 const val FOLD_UPDATE_HALF_OPEN = 1 const val FOLD_UPDATE_START_CLOSING = 2 -const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 3 -const val FOLD_UPDATE_FINISH_HALF_OPEN = 4 -const val FOLD_UPDATE_FINISH_FULL_OPEN = 5 -const val FOLD_UPDATE_FINISH_CLOSED = 6 +const val FOLD_UPDATE_ABORTED = 3 +const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 4 +const val FOLD_UPDATE_FINISH_HALF_OPEN = 5 +const val FOLD_UPDATE_FINISH_FULL_OPEN = 6 +const val FOLD_UPDATE_FINISH_CLOSED = 7 diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index c628d4401bb1..032da789518c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -89,7 +89,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final KeyguardBypassController mBypassController; - private int mLargeClockTopMargin = 0; private int mKeyguardClockTopMargin = 0; /** @@ -276,16 +275,15 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } private void updateClockLayout() { + int largeClockTopMargin = 0; if (mSmartspaceController.isEnabled()) { - RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, - MATCH_PARENT); - mLargeClockTopMargin = getContext().getResources().getDimensionPixelSize( + largeClockTopMargin = getContext().getResources().getDimensionPixelSize( R.dimen.keyguard_large_clock_top_margin); - lp.topMargin = mLargeClockTopMargin; - mLargeClockFrame.setLayoutParams(lp); - } else { - mLargeClockTopMargin = 0; } + RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, + MATCH_PARENT); + lp.topMargin = largeClockTopMargin; + mLargeClockFrame.setLayoutParams(lp); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index ba6771644db1..237ca71027b5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -51,6 +51,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; @@ -335,6 +336,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mLockIconPressed; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private final Executor mBackgroundExecutor; + private SensorPrivacyManager mSensorPrivacyManager; + private int mFaceAuthUserId; /** * Short delay before restarting fingerprint authentication after a successful try. This should @@ -1016,6 +1019,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // Error is always the end of authentication lifecycle mFaceCancelSignal = null; + boolean cameraPrivacyEnabled = false; + if (mSensorPrivacyManager != null) { + cameraPrivacyEnabled = mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, + mFaceAuthUserId); + } if (msgId == FaceManager.FACE_ERROR_CANCELED && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) { @@ -1025,7 +1034,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab setFaceRunningState(BIOMETRIC_STATE_STOPPED); } - if (msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE + final boolean isHwUnavailable = msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE; + + if (isHwUnavailable || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) { if (mHardwareFaceUnavailableRetryCount < HAL_ERROR_RETRY_MAX) { mHardwareFaceUnavailableRetryCount++; @@ -1041,6 +1052,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab requireStrongAuthIfAllLockedOut(); } + if (isHwUnavailable && cameraPrivacyEnabled) { + errString = mContext.getString(R.string.kg_face_sensor_privacy_enabled); + } + for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -1816,6 +1831,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLockPatternUtils = lockPatternUtils; mAuthController = authController; dumpManager.registerDumpable(getClass().getName(), this); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); mHandler = new Handler(mainLooper) { @Override @@ -2517,6 +2533,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // This would need to be updated for multi-sensor devices final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty() && mFaceSensorProperties.get(0).supportsFaceDetection; + mFaceAuthUserId = userId; if (isEncryptedOrLockdown(userId) && supportsFaceDetection) { mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId); } else { diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index b2ecc6140460..0c1934cb977b 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -218,11 +218,14 @@ public class LockIconView extends FrameLayout implements Dumpable { @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")"); - pw.println("Radius in pixels: " + mRadius); - pw.println("topLeft= (" + getX() + ", " + getY() + ")"); - pw.println("topLeft= (" + getX() + ", " + getY() + ")"); - pw.println("mIconType=" + typeToString(mIconType)); - pw.println("mAod=" + mAod); + pw.println("Lock Icon View Parameters:"); + pw.println(" Center in px (x, y)= (" + + mLockIconCenter.x + ", " + mLockIconCenter.y + ")"); + pw.println(" Radius in pixels: " + mRadius); + pw.println(" mIconType=" + typeToString(mIconType)); + pw.println(" mAod=" + mAod); + pw.println("Lock Icon View actual measurements:"); + pw.println(" topLeft= (" + getX() + ", " + getY() + ")"); + pw.println(" width=" + getWidth() + " height=" + getHeight()); } } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 163392314cfc..7ac3ca629e3f 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -37,11 +37,10 @@ import android.media.AudioAttributes; import android.os.Process; import android.os.Vibrator; import android.util.DisplayMetrics; +import android.util.Log; import android.util.MathUtils; -import android.view.GestureDetector; -import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.VelocityTracker; import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; @@ -67,8 +66,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; -import com.airbnb.lottie.LottieAnimationView; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; @@ -83,6 +80,7 @@ import javax.inject.Inject; */ @StatusBarComponent.StatusBarScope public class LockIconViewController extends ViewController<LockIconView> implements Dumpable { + private static final String TAG = "LockIconViewController"; private static final float sDefaultDensity = (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT; private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36); @@ -91,6 +89,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .build(); + private static final long LONG_PRESS_TIMEOUT = 200L; // milliseconds @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardViewController mKeyguardViewController; @@ -101,10 +100,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private final AccessibilityManager mAccessibilityManager; @NonNull private final ConfigurationController mConfigurationController; @NonNull private final DelayableExecutor mExecutor; - @NonNull private final LayoutInflater mLayoutInflater; private boolean mUdfpsEnrolled; - @Nullable private LottieAnimationView mAodFp; @NonNull private final AnimatedStateListDrawable mIcon; @NonNull private CharSequence mUnlockedLabel; @@ -112,6 +109,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Nullable private final Vibrator mVibrator; @Nullable private final AuthRippleController mAuthRippleController; + // Tracks the velocity of a touch to help filter out the touches that move too fast. + private VelocityTracker mVelocityTracker; + // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. + private int mActivePointerId = -1; + private boolean mIsDozing; private boolean mIsBouncerShowing; private boolean mRunningFPS; @@ -122,6 +124,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mUserUnlockedWithBiometric; private Runnable mCancelDelayedUpdateVisibilityRunnable; private Runnable mOnGestureDetectedRunnable; + private Runnable mLongPressCancelRunnable; private boolean mUdfpsSupported; private float mHeightPixels; @@ -133,7 +136,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme // for udfps when strong auth is required or unlocked on AOD private boolean mShowAodLockIcon; - private boolean mShowAODFpIcon; + private boolean mShowAodUnlockedIcon; private final int mMaxBurnInOffsetX; private final int mMaxBurnInOffsetY; private float mInterpolatedDarkAmount; @@ -156,8 +159,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull @Main DelayableExecutor executor, @Nullable Vibrator vibrator, @Nullable AuthRippleController authRippleController, - @NonNull @Main Resources resources, - @NonNull LayoutInflater inflater + @NonNull @Main Resources resources ) { super(view); mStatusBarStateController = statusBarStateController; @@ -171,7 +173,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mExecutor = executor; mVibrator = vibrator; mAuthRippleController = authRippleController; - mLayoutInflater = inflater; mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); @@ -181,7 +182,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mView.setImageDrawable(mIcon); mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button); mLockedLabel = resources.getString(R.string.accessibility_lock_icon); - dumpManager.registerDumpable("LockIconViewController", this); + dumpManager.registerDumpable(TAG, this); } @Override @@ -253,11 +254,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } boolean wasShowingUnlock = mShowUnlockIcon; - boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon; + boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon + && !mShowAodUnlockedIcon && !mShowAodLockIcon; mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen() && (!mUdfpsEnrolled || !mRunningFPS); mShowUnlockIcon = (mCanDismissLockScreen || mUserUnlockedWithBiometric) && isLockScreen(); - mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen; + mShowAodUnlockedIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen; mShowAodLockIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && !mCanDismissLockScreen; final CharSequence prevContentDescription = mView.getContentDescription(); @@ -274,14 +276,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mView.updateIcon(ICON_UNLOCK, false); mView.setContentDescription(mUnlockedLabel); mView.setVisibility(View.VISIBLE); - } else if (mShowAODFpIcon) { - // AOD fp icon is special cased as a lottie view (it updates for each burn-in offset), - // this state shows a transparent view - mView.setContentDescription(null); - mAodFp.setVisibility(View.VISIBLE); - mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel); - - mView.updateIcon(ICON_FINGERPRINT, true); // this shows no icon + } else if (mShowAodUnlockedIcon) { + mView.updateIcon(ICON_UNLOCK, true); + mView.setContentDescription(mUnlockedLabel); mView.setVisibility(View.VISIBLE); } else if (mShowAodLockIcon) { if (wasShowingUnlock) { @@ -297,11 +294,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mView.setContentDescription(null); } - if (!mShowAODFpIcon && mAodFp != null) { - mAodFp.setVisibility(View.INVISIBLE); - mAodFp.setContentDescription(null); - } - if (!Objects.equals(prevContentDescription, mView.getContentDescription()) && mView.getContentDescription() != null) { mView.announceForAccessibility(mView.getContentDescription()); @@ -320,7 +312,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme getResources().getString(R.string.accessibility_enter_hint)); public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(v, info); - if (isClickable()) { + if (isActionable()) { if (mShowLockIcon) { info.addAction(mAccessibilityAuthenticateHint); } else if (mShowUnlockIcon) { @@ -389,7 +381,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme pw.println(); pw.println(" mShowUnlockIcon: " + mShowUnlockIcon); pw.println(" mShowLockIcon: " + mShowLockIcon); - pw.println(" mShowAODFpIcon: " + mShowAODFpIcon); + pw.println(" mShowAodUnlockedIcon: " + mShowAodUnlockedIcon); pw.println(" mIsDozing: " + mIsDozing); pw.println(" mIsBouncerShowing: " + mIsBouncerShowing); pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric); @@ -418,17 +410,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme - mMaxBurnInOffsetY, mInterpolatedDarkAmount); float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount); - if (mAodFp != null) { - mAodFp.setTranslationX(offsetX); - mAodFp.setTranslationY(offsetY); - mAodFp.setProgress(progress); - mAodFp.setAlpha(255 * mInterpolatedDarkAmount); - } - - if (mShowAodLockIcon) { - mView.setTranslationX(offsetX); - mView.setTranslationY(offsetY); - } + mView.setTranslationX(offsetX); + mView.setTranslationY(offsetY); } private void updateIsUdfpsEnrolled() { @@ -439,10 +422,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mView.setUseBackground(mUdfpsSupported); mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); - if (!wasUdfpsEnrolled && mUdfpsEnrolled && mAodFp == null) { - mLayoutInflater.inflate(R.layout.udfps_aod_lock_icon, mView); - mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp); - } if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) { updateVisibility(); } @@ -477,7 +456,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override public void onKeyguardVisibilityChanged(boolean showing) { // reset mIsBouncerShowing state in case it was preemptively set - // onAffordanceClick + // onLongPress mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); updateVisibility(); } @@ -571,104 +550,75 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } }; - private final GestureDetector mGestureDetector = - new GestureDetector(new SimpleOnGestureListener() { - public boolean onDown(MotionEvent e) { - if (!isClickable()) { - mDownDetected = false; - return false; - } - - // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or - // MotionEvent.ACTION_UP (see #onTouchEvent) - if (mVibrator != null && !mDownDetected) { - mVibrator.vibrate( - Process.myUid(), - getContext().getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "lockIcon-onDown", - VIBRATION_SONIFICATION_ATTRIBUTES); - } - - mDownDetected = true; - return true; - } - - public void onLongPress(MotionEvent e) { - if (!wasClickableOnDownEvent()) { - return; - } - - if (onAffordanceClick() && mVibrator != null) { - // only vibrate if the click went through and wasn't intercepted by falsing - mVibrator.vibrate( - Process.myUid(), - getContext().getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "lockIcon-onLongPress", - VIBRATION_SONIFICATION_ATTRIBUTES); - } - } + /** + * Handles the touch if it is within the lock icon view and {@link #isActionable()} is true. + * Subsequently, will trigger {@link #onLongPress()} if a touch is continuously in the lock icon + * area for {@link #LONG_PRESS_TIMEOUT} ms. + * + * Touch speed debouncing mimics logic from the velocity tracker in {@link UdfpsController}. + */ + public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) { + if (!onInterceptTouchEvent(event)) { + cancelTouches(); + return false; + } - public boolean onSingleTapUp(MotionEvent e) { - if (!wasClickableOnDownEvent()) { - return false; - } - onAffordanceClick(); - return true; + mOnGestureDetectedRunnable = onGestureDetectedRunnable; + switch(event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_HOVER_ENTER: + if (mVibrator != null && !mDownDetected) { + mVibrator.vibrate( + Process.myUid(), + getContext().getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "lock-icon-down", + VIBRATION_SONIFICATION_ATTRIBUTES); } - public boolean onFling(MotionEvent e1, MotionEvent e2, - float velocityX, float velocityY) { - if (!wasClickableOnDownEvent()) { - return false; - } - onAffordanceClick(); - return true; + // The pointer that causes ACTION_DOWN is always at index 0. + // We need to persist its ID to track it during ACTION_MOVE that could include + // data for many other pointers because of multi-touch support. + mActivePointerId = event.getPointerId(0); + if (mVelocityTracker == null) { + // To simplify the lifecycle of the velocity tracker, make sure it's never null + // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP. + mVelocityTracker = VelocityTracker.obtain(); + } else { + // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new + // ACTION_DOWN, in that case we should just reuse the old instance. + mVelocityTracker.clear(); } - - private boolean wasClickableOnDownEvent() { - return mDownDetected; + mVelocityTracker.addMovement(event); + + mDownDetected = true; + mLongPressCancelRunnable = mExecutor.executeDelayed( + this::onLongPress, LONG_PRESS_TIMEOUT); + break; + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_HOVER_MOVE: + mVelocityTracker.addMovement(event); + // Compute pointer velocity in pixels per second. + mVelocityTracker.computeCurrentVelocity(1000); + float velocity = UdfpsController.computePointerSpeed(mVelocityTracker, + mActivePointerId); + if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS + && UdfpsController.exceedsVelocityThreshold(velocity)) { + Log.v(TAG, "lock icon long-press rescheduled due to " + + "high pointer velocity=" + velocity); + mLongPressCancelRunnable.run(); + mLongPressCancelRunnable = mExecutor.executeDelayed( + this::onLongPress, LONG_PRESS_TIMEOUT); } - - /** - * Whether we tried to launch the affordance. - * - * If falsing intercepts the click, returns false. - */ - private boolean onAffordanceClick() { - if (mFalsingManager.isFalseTouch(LOCK_ICON)) { - return false; - } - - // pre-emptively set to true to hide view - mIsBouncerShowing = true; - if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { - mAuthRippleController.showRipple(FINGERPRINT); - } - updateVisibility(); - if (mOnGestureDetectedRunnable != null) { - mOnGestureDetectedRunnable.run(); - } - mKeyguardViewController.showBouncer(/* scrim */ true); - return true; - } - }); - - /** - * Send touch events to this view and handles it if the touch is within this view and we are - * in a 'clickable' state - * @return whether to intercept the touch event - */ - public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) { - if (onInterceptTouchEvent(event)) { - mOnGestureDetectedRunnable = onGestureDetectedRunnable; - mGestureDetector.onTouchEvent(event); - return true; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_HOVER_EXIT: + cancelTouches(); + break; } - mDownDetected = false; - return false; + return true; } /** @@ -676,7 +626,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme * bounds. */ public boolean onInterceptTouchEvent(MotionEvent event) { - if (!inLockIconArea(event) || !isClickable()) { + if (!inLockIconArea(event) || !isActionable()) { return false; } @@ -687,13 +637,58 @@ public class LockIconViewController extends ViewController<LockIconView> impleme return mDownDetected; } + private void onLongPress() { + cancelTouches(); + if (mFalsingManager.isFalseTouch(LOCK_ICON)) { + Log.v(TAG, "lock icon long-press rejected by the falsing manager."); + return; + } + + // pre-emptively set to true to hide view + mIsBouncerShowing = true; + if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { + mAuthRippleController.showRipple(FINGERPRINT); + } + updateVisibility(); + if (mOnGestureDetectedRunnable != null) { + mOnGestureDetectedRunnable.run(); + } + + if (mVibrator != null) { + // play device entry haptic (same as biometric success haptic) + mVibrator.vibrate( + Process.myUid(), + getContext().getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "lock-icon-device-entry", + VIBRATION_SONIFICATION_ATTRIBUTES); + } + + mKeyguardViewController.showBouncer(/* scrim */ true); + } + + + private void cancelTouches() { + mDownDetected = false; + if (mLongPressCancelRunnable != null) { + mLongPressCancelRunnable.run(); + } + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + if (mVibrator != null) { + mVibrator.cancel(); + } + } + + private boolean inLockIconArea(MotionEvent event) { return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) - && (mView.getVisibility() == View.VISIBLE - || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE)); + && mView.getVisibility() == View.VISIBLE; } - private boolean isClickable() { + private boolean isActionable() { return mUdfpsSupported || mShowUnlockIcon; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index f11dc9313852..e35b55841f2f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -636,6 +636,7 @@ public abstract class AuthBiometricView extends LinearLayout { mIndicatorView.setText(message); mIndicatorView.setTextColor(mTextColorError); mIndicatorView.setVisibility(View.VISIBLE); + mIndicatorView.setSelected(true); mHandler.postDelayed(resetMessageRunnable, mInjector.getDelayAfterError()); Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 1226dca1f306..d8f6a01398d1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -31,6 +31,7 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PointF; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager.Authenticators; @@ -89,6 +90,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, private static final String TAG = "AuthController"; private static final boolean DEBUG = true; + private static final int SENSOR_PRIVACY_DELAY = 500; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final CommandQueue mCommandQueue; @@ -122,6 +124,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps; @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; + private SensorPrivacyManager mSensorPrivacyManager; private class BiometricTaskStackListener extends TaskStackListener { @Override @@ -492,6 +495,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.registerReceiver(mBroadcastReceiver, filter); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } private void updateFingerprintLocation() { @@ -642,10 +646,16 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT) || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT); + boolean isCameraPrivacyEnabled = false; + if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE + && mSensorPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, + mCurrentDialogArgs.argi1 /* userId */)) { + isCameraPrivacyEnabled = true; + } // TODO(b/141025588): Create separate methods for handling hard and soft errors. final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED - || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT); - + || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT + || isCameraPrivacyEnabled); if (mCurrentDialog != null) { if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) { if (DEBUG) Log.d(TAG, "onBiometricError, lockout"); @@ -655,12 +665,23 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, ? mContext.getString(R.string.biometric_not_recognized) : getErrorString(modality, error, vendorCode); if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage); - mCurrentDialog.onAuthenticationFailed(modality, errorMessage); + // The camera privacy error can return before the prompt initializes its state, + // causing the prompt to appear to endlessly authenticate. Add a small delay + // to stop this. + if (isCameraPrivacyEnabled) { + mHandler.postDelayed(() -> { + mCurrentDialog.onAuthenticationFailed(modality, + mContext.getString(R.string.face_sensor_privacy_enabled)); + }, SENSOR_PRIVACY_DELAY); + } else { + mCurrentDialog.onAuthenticationFailed(modality, errorMessage); + } } else { final String errorMessage = getErrorString(modality, error, vendorCode); if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage); mCurrentDialog.onError(modality, errorMessage); } + } else { Log.w(TAG, "onBiometricError callback but dialog is gone"); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java index fb4616a832dc..07aec6994bd0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java @@ -23,6 +23,7 @@ import android.graphics.RectF; import com.android.systemui.Dumpable; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.util.ViewController; @@ -44,6 +45,7 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView> extends ViewController<T> implements Dumpable { @NonNull final StatusBarStateController mStatusBarStateController; @NonNull final PanelExpansionStateManager mPanelExpansionStateManager; + @NonNull final SystemUIDialogManager mDialogManager; @NonNull final DumpManager mDumpManger; boolean mNotificationShadeVisible; @@ -52,10 +54,12 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView> T view, @NonNull StatusBarStateController statusBarStateController, @NonNull PanelExpansionStateManager panelExpansionStateManager, + @NonNull SystemUIDialogManager dialogManager, @NonNull DumpManager dumpManager) { super(view); mStatusBarStateController = statusBarStateController; mPanelExpansionStateManager = panelExpansionStateManager; + mDialogManager = dialogManager; mDumpManger = dumpManager; } @@ -64,12 +68,14 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView> @Override protected void onViewAttached() { mPanelExpansionStateManager.addExpansionListener(mPanelExpansionListener); + mDialogManager.registerListener(mDialogListener); mDumpManger.registerDumpable(getDumpTag(), this); } @Override protected void onViewDetached() { mPanelExpansionStateManager.removeExpansionListener(mPanelExpansionListener); + mDialogManager.registerListener(mDialogListener); mDumpManger.unregisterDumpable(getDumpTag()); } @@ -95,7 +101,8 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView> * authentication. */ boolean shouldPauseAuth() { - return mNotificationShadeVisible; + return mNotificationShadeVisible + || mDialogManager.shouldHideAffordance(); } /** @@ -189,4 +196,7 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView> updatePauseAuth(); } }; + + private final SystemUIDialogManager.Listener mDialogListener = + (shouldHide) -> updatePauseAuth(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java index 894b29583cc9..3732100a2a06 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; /** @@ -30,8 +31,10 @@ class UdfpsBpViewController extends UdfpsAnimationViewController<UdfpsBpView> { @NonNull UdfpsBpView view, @NonNull StatusBarStateController statusBarStateController, @NonNull PanelExpansionStateManager panelExpansionStateManager, + @NonNull SystemUIDialogManager systemUIDialogManager, @NonNull DumpManager dumpManager) { - super(view, statusBarStateController, panelExpansionStateManager, dumpManager); + super(view, statusBarStateController, panelExpansionStateManager, + systemUIDialogManager, dumpManager); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 09f2af4a86da..3e9d6b0fa362 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -47,7 +47,6 @@ import android.os.RemoteException; import android.os.Trace; import android.os.VibrationEffect; import android.os.Vibrator; -import android.provider.Settings; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -71,6 +70,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -103,6 +103,7 @@ import kotlin.Unit; public class UdfpsController implements DozeReceiver { private static final String TAG = "UdfpsController"; private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000; + private static final long DEFAULT_VIBRATION_DURATION = 1000; // milliseconds // Minimum required delay between consecutive touch logs in milliseconds. private static final long MIN_TOUCH_LOG_INTERVAL = 50; @@ -118,6 +119,7 @@ public class UdfpsController implements DozeReceiver { @NonNull private final KeyguardStateController mKeyguardStateController; @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager; @NonNull private final DumpManager mDumpManager; + @NonNull private final SystemUIDialogManager mDialogManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Nullable private final Vibrator mVibrator; @NonNull private final FalsingManager mFalsingManager; @@ -164,10 +166,6 @@ public class UdfpsController implements DozeReceiver { private boolean mAttemptedToDismissKeyguard; private Set<Callback> mCallbacks = new HashSet<>(); - // by default, use low tick - private int mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_LOW_TICK; - private final VibrationEffect mTick; - @VisibleForTesting public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES = new AudioAttributes.Builder() @@ -282,9 +280,6 @@ public class UdfpsController implements DozeReceiver { return; } mGoodCaptureReceived = true; - if (mVibrator != null) { - mVibrator.cancel(); - } mView.stopIllumination(); if (mServerRequest != null) { mServerRequest.onAcquiredGood(); @@ -327,12 +322,23 @@ public class UdfpsController implements DozeReceiver { } } - private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { + /** + * Calculate the pointer speed given a velocity tracker and the pointer id. + * This assumes that the velocity tracker has already been passed all relevant motion events. + */ + public static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { final float vx = tracker.getXVelocity(pointerId); final float vy = tracker.getYVelocity(pointerId); return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); } + /** + * Whether the velocity exceeds the acceptable UDFPS debouncing threshold. + */ + public static boolean exceedsVelocityThreshold(float velocity) { + return velocity > 750f; + } + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -467,7 +473,7 @@ public class UdfpsController implements DozeReceiver { final float v = computePointerSpeed(mVelocityTracker, mActivePointerId); final float minor = event.getTouchMinor(idx); final float major = event.getTouchMajor(idx); - final boolean exceedsVelocityThreshold = v > 750f; + final boolean exceedsVelocityThreshold = exceedsVelocityThreshold(v); final String touchInfo = String.format( "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b", minor, major, v, exceedsVelocityThreshold); @@ -548,7 +554,8 @@ public class UdfpsController implements DozeReceiver { @Main Handler mainHandler, @NonNull ConfigurationController configurationController, @NonNull SystemClock systemClock, - @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { + @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, + @NonNull SystemUIDialogManager dialogManager) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -563,6 +570,7 @@ public class UdfpsController implements DozeReceiver { mKeyguardStateController = keyguardStateController; mKeyguardViewManager = statusBarKeyguardViewManager; mDumpManager = dumpManager; + mDialogManager = dialogManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mFalsingManager = falsingManager; mPowerManager = powerManager; @@ -575,7 +583,6 @@ public class UdfpsController implements DozeReceiver { mConfigurationController = configurationController; mSystemClock = systemClock; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - mTick = lowTick(); mSensorProps = findFirstUdfps(); // At least one UDFPS sensor exists @@ -610,36 +617,6 @@ public class UdfpsController implements DozeReceiver { udfpsHapticsSimulator.setUdfpsController(this); } - private VibrationEffect lowTick() { - boolean useLowTickDefault = mContext.getResources() - .getBoolean(R.bool.config_udfpsUseLowTick); - if (Settings.Global.getFloat( - mContext.getContentResolver(), - "tick-low", useLowTickDefault ? 1 : 0) == 0) { - mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK; - } - float tickIntensity = Settings.Global.getFloat( - mContext.getContentResolver(), - "tick-intensity", - mContext.getResources().getFloat(R.dimen.config_udfpsTickIntensity)); - int tickDelay = Settings.Global.getInt( - mContext.getContentResolver(), - "tick-delay", - mContext.getResources().getInteger(R.integer.config_udfpsTickDelay)); - - VibrationEffect.Composition composition = VibrationEffect.startComposition(); - composition.addPrimitive(mPrimitiveTick, tickIntensity, 0); - int primitives = 1000 / tickDelay; - float[] rampUp = new float[]{.48f, .58f, .69f, .83f}; - for (int i = 0; i < rampUp.length; i++) { - composition.addPrimitive(mPrimitiveTick, tickIntensity * rampUp[i], tickDelay); - } - for (int i = rampUp.length; i < primitives; i++) { - composition.addPrimitive(mPrimitiveTick, tickIntensity, tickDelay); - } - return composition.compose(); - } - /** * Play haptic to signal udfps scanning started. */ @@ -649,8 +626,8 @@ public class UdfpsController implements DozeReceiver { mVibrator.vibrate( Process.myUid(), mContext.getOpPackageName(), - mTick, - "udfps-onStart-tick", + EFFECT_CLICK, + "udfps-onStart-click", VIBRATION_SONIFICATION_ATTRIBUTES); } } @@ -842,6 +819,7 @@ public class UdfpsController implements DozeReceiver { mServerRequest.mEnrollHelper, mStatusBarStateController, mPanelExpansionStateManager, + mDialogManager, mDumpManager ); case BiometricOverlayConstants.REASON_AUTH_KEYGUARD: @@ -860,6 +838,7 @@ public class UdfpsController implements DozeReceiver { mSystemClock, mKeyguardStateController, mUnlockedScreenOffAnimationController, + mDialogManager, this ); case BiometricOverlayConstants.REASON_AUTH_BP: @@ -870,6 +849,7 @@ public class UdfpsController implements DozeReceiver { bpView, mStatusBarStateController, mPanelExpansionStateManager, + mDialogManager, mDumpManager ); case BiometricOverlayConstants.REASON_AUTH_OTHER: @@ -881,6 +861,7 @@ public class UdfpsController implements DozeReceiver { authOtherView, mStatusBarStateController, mPanelExpansionStateManager, + mDialogManager, mDumpManager ); default: @@ -1037,7 +1018,6 @@ public class UdfpsController implements DozeReceiver { } } mOnFingerDown = false; - mVibrator.cancel(); if (mView.isIlluminationRequested()) { mView.stopIllumination(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index 1f01fc5a4b3d..ac38b13cc4dd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java @@ -20,9 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Context; -import android.content.res.TypedArray; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.Rect; @@ -30,7 +28,6 @@ import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; -import android.util.TypedValue; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.LinearInterpolator; @@ -38,7 +35,6 @@ import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.graphics.ColorUtils; import com.android.systemui.R; /** @@ -106,9 +102,8 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mSensorOutlinePaint = new Paint(0 /* flags */); mSensorOutlinePaint.setAntiAlias(true); - mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon)); - mSensorOutlinePaint.setStyle(Paint.Style.STROKE); - mSensorOutlinePaint.setStrokeWidth(2.f); + mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_moving_target_fill)); + mSensorOutlinePaint.setStyle(Paint.Style.FILL); mBlueFill = new Paint(0 /* flags */); mBlueFill.setAntiAlias(true); @@ -117,12 +112,12 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mMovingTargetFpIcon = context.getResources() .getDrawable(R.drawable.ic_kg_fingerprint, null); - mMovingTargetFpIcon.setTint(Color.WHITE); + mMovingTargetFpIcon.setTint(mContext.getColor(R.color.udfps_enroll_icon)); mMovingTargetFpIcon.mutate(); mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon)); - mHintColorFaded = getHintColorFaded(context); + mHintColorFaded = context.getColor(R.color.udfps_moving_target_fill); mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress); mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP); mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP); @@ -218,22 +213,6 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { }; } - @ColorInt - private static int getHintColorFaded(@NonNull Context context) { - final TypedValue tv = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true); - final int alpha = (int) (tv.getFloat() * 255f); - - final int[] attrs = new int[] {android.R.attr.colorControlNormal}; - final TypedArray ta = context.obtainStyledAttributes(attrs); - try { - @ColorInt final int color = ta.getColor(0, context.getColor(R.color.white_disabled)); - return ColorUtils.setAlphaComponent(color, alpha); - } finally { - ta.recycle(); - } - } - void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { mEnrollHelper = helper; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java index 79c7e66d40f7..631a461b0627 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java @@ -18,12 +18,10 @@ package com.android.systemui.biometrics; import android.animation.ValueAnimator; import android.content.Context; -import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.drawable.Drawable; -import android.util.TypedValue; import android.view.animation.Interpolator; import android.view.animation.OvershootInterpolator; @@ -80,24 +78,11 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { mBackgroundPaint = new Paint(); mBackgroundPaint.setStrokeWidth(mStrokeWidthPx); - mBackgroundPaint.setColor(context.getColor(R.color.white_disabled)); + mBackgroundPaint.setColor(context.getColor(R.color.udfps_moving_target_fill)); mBackgroundPaint.setAntiAlias(true); mBackgroundPaint.setStyle(Paint.Style.STROKE); mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND); - // Set background paint color and alpha. - final int[] attrs = new int[] {android.R.attr.colorControlNormal}; - final TypedArray typedArray = context.obtainStyledAttributes(attrs); - try { - @ColorInt final int tintColor = typedArray.getColor(0, mBackgroundPaint.getColor()); - mBackgroundPaint.setColor(tintColor); - } finally { - typedArray.recycle(); - } - TypedValue alpha = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true); - mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f)); - // Progress fill should *not* use the extracted system color. mFillPaint = new Paint(); mFillPaint.setStrokeWidth(mStrokeWidthPx); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java index 292a904af96e..ac9e92e22569 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java @@ -22,6 +22,7 @@ import android.graphics.PointF; import com.android.systemui.R; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; /** @@ -54,8 +55,10 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp @NonNull UdfpsEnrollHelper enrollHelper, @NonNull StatusBarStateController statusBarStateController, @NonNull PanelExpansionStateManager panelExpansionStateManager, + @NonNull SystemUIDialogManager systemUIDialogManager, @NonNull DumpManager dumpManager) { - super(view, statusBarStateController, panelExpansionStateManager, dumpManager); + super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager, + dumpManager); mEnrollProgressBarRadius = getContext().getResources() .getInteger(R.integer.config_udfpsEnrollProgressBar); mEnrollHelper = enrollHelper; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java index 619873367ee8..97dca0fbdedf 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; /** @@ -33,8 +34,10 @@ class UdfpsFpmOtherViewController extends UdfpsAnimationViewController<UdfpsFpmO @NonNull UdfpsFpmOtherView view, @NonNull StatusBarStateController statusBarStateController, @NonNull PanelExpansionStateManager panelExpansionStateManager, + @NonNull SystemUIDialogManager systemUIDialogManager, @NonNull DumpManager dumpManager) { - super(view, statusBarStateController, panelExpansionStateManager, dumpManager); + super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager, + dumpManager); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 8f4d6f6aa973..3e8a568cb803 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -31,6 +31,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; @@ -86,8 +87,10 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @NonNull SystemClock systemClock, @NonNull KeyguardStateController keyguardStateController, @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, + @NonNull SystemUIDialogManager systemUIDialogManager, @NonNull UdfpsController udfpsController) { - super(view, statusBarStateController, panelExpansionStateManager, dumpManager); + super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager, + dumpManager); mKeyguardViewManager = statusBarKeyguardViewManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockScreenShadeTransitionController = transitionController; @@ -217,6 +220,10 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud return false; } + if (mDialogManager.shouldHideAffordance()) { + return true; + } + if (mLaunchTransitionFadingAway) { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java index 71edbc06840e..d17eadd163fc 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java @@ -19,6 +19,7 @@ package com.android.systemui.classifier; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_HORIZONTAL_ANGLE_RANGE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_VERTICAL_ANGLE_RANGE; import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE; +import static com.android.systemui.classifier.Classifier.LOCK_ICON; import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE; import android.provider.DeviceConfig; @@ -71,7 +72,9 @@ class DiagonalClassifier extends FalsingClassifier { return Result.passed(0); } - if (interactionType == LEFT_AFFORDANCE || interactionType == RIGHT_AFFORDANCE) { + if (interactionType == LEFT_AFFORDANCE + || interactionType == RIGHT_AFFORDANCE + || interactionType == LOCK_ICON) { return Result.passed(0); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index d3d6e03c9bc7..6f30ac3901e1 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -18,6 +18,7 @@ package com.android.systemui.controls.ui import android.annotation.MainThread import android.app.Dialog +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -88,7 +89,7 @@ class ControlActionCoordinatorImpl @Inject constructor( bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) if (cvh.usePanel()) { - showDetail(cvh, control.getAppIntent().getIntent()) + showDetail(cvh, control.getAppIntent()) } else { cvh.action(CommandAction(templateId)) } @@ -116,7 +117,7 @@ class ControlActionCoordinatorImpl @Inject constructor( // Long press snould only be called when there is valid control state, otherwise ignore cvh.cws.control?.let { cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - showDetail(cvh, it.getAppIntent().getIntent()) + showDetail(cvh, it.getAppIntent()) } }, false /* blockable */)) } @@ -167,10 +168,10 @@ class ControlActionCoordinatorImpl @Inject constructor( bgExecutor.execute { vibrator.vibrate(effect) } } - private fun showDetail(cvh: ControlViewHolder, intent: Intent) { + private fun showDetail(cvh: ControlViewHolder, pendingIntent: PendingIntent) { bgExecutor.execute { val activities: List<ResolveInfo> = context.packageManager.queryIntentActivities( - intent, + pendingIntent.getIntent(), PackageManager.MATCH_DEFAULT_ONLY ) @@ -178,7 +179,7 @@ class ControlActionCoordinatorImpl @Inject constructor( // make sure the intent is valid before attempting to open the dialog if (activities.isNotEmpty() && taskViewFactory.isPresent) { taskViewFactory.get().create(context, uiExecutor, { - dialog = DetailDialog(activityContext, it, intent, cvh).also { + dialog = DetailDialog(activityContext, it, pendingIntent, cvh).also { it.setOnDismissListener { _ -> dialog = null } it.show() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index 8a47a36de8c4..4758ab04e2e5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -43,7 +43,7 @@ import com.android.wm.shell.TaskView class DetailDialog( val activityContext: Context?, val taskView: TaskView, - val intent: Intent, + val pendingIntent: PendingIntent, val cvh: ControlViewHolder ) : Dialog( activityContext ?: cvh.context, @@ -59,6 +59,14 @@ class DetailDialog( var detailTaskId = INVALID_TASK_ID + private val fillInIntent = Intent().apply { + putExtra(EXTRA_USE_PANEL, true) + + // Apply flags to make behaviour match documentLaunchMode=always. + addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) + addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) + } + fun removeDetailTask() { if (detailTaskId == INVALID_TASK_ID) return ActivityTaskManager.getInstance().removeTask(detailTaskId) @@ -67,13 +75,6 @@ class DetailDialog( val stateCallback = object : TaskView.Listener { override fun onInitialized() { - val launchIntent = Intent(intent) - launchIntent.putExtra(EXTRA_USE_PANEL, true) - - // Apply flags to make behaviour match documentLaunchMode=always. - launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) - launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - val options = activityContext?.let { ActivityOptions.makeCustomAnimation( it, @@ -82,9 +83,8 @@ class DetailDialog( ) } ?: ActivityOptions.makeBasic() taskView.startActivity( - PendingIntent.getActivity(context, 0, launchIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE), - null /* fillInIntent */, + pendingIntent, + fillInIntent, options, getTaskViewBounds() ) @@ -97,6 +97,9 @@ class DetailDialog( override fun onTaskCreated(taskId: Int, name: ComponentName?) { detailTaskId = taskId + requireViewById<ViewGroup>(R.id.controls_activity_view).apply { + setAlpha(1f) + } } override fun onReleased() { @@ -121,6 +124,7 @@ class DetailDialog( requireViewById<ViewGroup>(R.id.controls_activity_view).apply { addView(taskView) + setAlpha(0f) } requireViewById<ImageView>(R.id.control_detail_close).apply { @@ -134,7 +138,7 @@ class DetailDialog( removeDetailTask() dismiss() context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) - v.context.startActivity(intent) + pendingIntent.send() } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index ff14064834d7..2ebcd8531128 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -121,6 +121,7 @@ import com.android.systemui.scrim.ScrimDrawable; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -236,6 +237,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected Handler mMainHandler; private int mSmallestScreenWidthDp; private final Optional<StatusBar> mStatusBarOptional; + private final SystemUIDialogManager mDialogManager; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final DialogLaunchAnimator mDialogLaunchAnimator; @@ -346,7 +348,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene PackageManager packageManager, Optional<StatusBar> statusBarOptional, KeyguardUpdateMonitor keyguardUpdateMonitor, - DialogLaunchAnimator dialogLaunchAnimator) { + DialogLaunchAnimator dialogLaunchAnimator, + SystemUIDialogManager dialogManager) { mContext = context; mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -378,6 +381,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mStatusBarOptional = statusBarOptional; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mDialogLaunchAnimator = dialogLaunchAnimator; + mDialogManager = dialogManager; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -677,7 +681,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, mSysUiState, this::onRefresh, mKeyguardShowing, mPowerAdapter, mUiEventLogger, - mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils); + mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils, + mDialogManager); dialog.setOnDismissListener(this); dialog.setOnShowListener(this); @@ -2219,10 +2224,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene SysUiState sysuiState, Runnable onRefreshCallback, boolean keyguardShowing, MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, Optional<StatusBar> statusBarOptional, - KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) { + KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils, + SystemUIDialogManager systemUiDialogManager) { // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to // dismiss this dialog when the device is locked. - super(context, themeRes, false /* dismissOnDeviceLock */); + super(context, themeRes, false /* dismissOnDeviceLock */, + systemUiDialogManager); mContext = context; mAdapter = adapter; mOverflowAdapter = overflowAdapter; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 8d0733645117..89a5d72a3ca4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2090,6 +2090,15 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private final Runnable mKeyguardGoingAwayRunnable = new Runnable() { @Override public void run() { + // If the keyguard is already going away, or it's about to because we are going to + // trigger the going-away remote animation to show the surface behind, don't do it + // again. That will cause the current animation to be cancelled unnecessarily. + if (mKeyguardStateController.isKeyguardGoingAway() + || mSurfaceBehindRemoteAnimationRequested + || mSurfaceBehindRemoteAnimationRunning) { + return; + } + Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable"); if (DEBUG) Log.d(TAG, "keyguardGoingAway"); mKeyguardViewControllerLazy.get().keyguardGoingAway(); @@ -2451,9 +2460,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, if (mSurfaceBehindRemoteAnimationFinishedCallback != null) { try { - if (!cancelled) { - mSurfaceBehindRemoteAnimationFinishedCallback.onAnimationFinished(); - } + mSurfaceBehindRemoteAnimationFinishedCallback.onAnimationFinished(); mSurfaceBehindRemoteAnimationFinishedCallback = null; } catch (RemoteException e) { e.printStackTrace(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 03a097746ba9..f2cb254c3b97 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -44,6 +44,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.systemui.R; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; /** * Base dialog for media output UI @@ -52,6 +53,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements MediaOutputController.Callback, Window.Callback { private static final String TAG = "MediaOutputDialog"; + private static final String EMPTY_TITLE = " "; private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); private final RecyclerView.LayoutManager mLayoutManager; @@ -63,13 +65,14 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements View mDialogView; private TextView mHeaderTitle; private TextView mHeaderSubtitle; - private ImageView mHeaderIcon; private RecyclerView mDevicesRecyclerView; private LinearLayout mDeviceListLayout; private Button mDoneButton; private Button mStopButton; private int mListMaxHeight; + protected ImageView mHeaderIcon; + MediaOutputBaseAdapter mAdapter; private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> { @@ -81,9 +84,12 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } }; - public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) { - super(context); - mContext = context; + public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController, + SystemUIDialogManager dialogManager) { + super(context, dialogManager); + + // Save the context that is wrapped with our theme. + mContext = getContext(); mMediaOutputController = mediaOutputController; mLayoutManager = new LinearLayoutManager(mContext); mListMaxHeight = context.getResources().getDimensionPixelSize( @@ -104,6 +110,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements lp.setFitInsetsIgnoringVisibility(true); window.setAttributes(lp); window.setContentView(mDialogView); + // Sets window to a blank string to avoid talkback announce app label first when pop up, + // which doesn't make sense. + window.setTitle(EMPTY_TITLE); mHeaderTitle = mDialogView.requireViewById(R.id.header_title); mHeaderSubtitle = mDialogView.requireViewById(R.id.header_subtitle); @@ -140,7 +149,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mMediaOutputController.stop(); } - @VisibleForTesting void refresh() { // Update header icon final int iconRes = getHeaderIconRes(); @@ -154,12 +162,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } else { mHeaderIcon.setVisibility(View.GONE); } - if (mHeaderIcon.getVisibility() == View.VISIBLE) { - final int size = getHeaderIconSize(); - final int padding = mContext.getResources().getDimensionPixelSize( - R.dimen.media_output_dialog_header_icon_padding); - mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size)); - } // Update title and subtitle mHeaderTitle.setText(getHeaderText()); final CharSequence subTitle = getHeaderSubtitle(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 6da4d4811ac9..a1e2c57c5c37 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -16,6 +16,8 @@ package com.android.systemui.media.dialog; +import static android.provider.Settings.ACTION_BLUETOOTH_PAIRING_SETTINGS; + import android.app.Notification; import android.content.Context; import android.content.Intent; @@ -30,6 +32,7 @@ import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.view.View; @@ -46,7 +49,6 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.InfoMediaManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; -import com.android.settingslib.media.MediaOutputConstants; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.R; import com.android.systemui.animation.DialogLaunchAnimator; @@ -54,6 +56,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import java.util.ArrayList; import java.util.Collection; @@ -69,7 +72,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { private static final String TAG = "MediaOutputController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - + private static final String PAGE_CONNECTED_DEVICES_KEY = + "top_level_connected_devices"; private final String mPackageName; private final Context mContext; private final MediaSessionManager mMediaSessionManager; @@ -77,6 +81,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { private final ShadeController mShadeController; private final ActivityStarter mActivityStarter; private final DialogLaunchAnimator mDialogLaunchAnimator; + private final SystemUIDialogManager mDialogManager; private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>(); private final boolean mAboveStatusbar; private final boolean mVolumeAdjustmentForRemoteGroupSessions; @@ -98,7 +103,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager lbm, ShadeController shadeController, ActivityStarter starter, NotificationEntryManager notificationEntryManager, UiEventLogger uiEventLogger, - DialogLaunchAnimator dialogLaunchAnimator) { + DialogLaunchAnimator dialogLaunchAnimator, SystemUIDialogManager dialogManager) { mContext = context; mPackageName = packageName; mMediaSessionManager = mediaSessionManager; @@ -114,6 +119,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { mDialogLaunchAnimator = dialogLaunchAnimator; mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean( com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); + mDialogManager = dialogManager; } void start(@NonNull Callback cb) { @@ -451,23 +457,34 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mCallback.dismissDialog(); - final ActivityStarter.OnDismissAction postKeyguardAction = () -> { - mContext.sendBroadcast(new Intent() - .setAction(MediaOutputConstants.ACTION_LAUNCH_BLUETOOTH_PAIRING) - .setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME)); - mShadeController.animateCollapsePanels(); - return true; - }; - mActivityStarter.dismissKeyguardThenExecute(postKeyguardAction, null, true); + Intent launchIntent = + new Intent(ACTION_BLUETOOTH_PAIRING_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + final Intent deepLinkIntent = + new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY); + if (deepLinkIntent.resolveActivity(mContext.getPackageManager()) != null) { + Log.d(TAG, "Device support split mode, launch page with deep link"); + deepLinkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + deepLinkIntent.putExtra( + Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI, + launchIntent.toUri(Intent.URI_INTENT_SCHEME)); + deepLinkIntent.putExtra( + Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, + PAGE_CONNECTED_DEVICES_KEY); + mActivityStarter.startActivity(deepLinkIntent, true); + return; + } + mActivityStarter.startActivity(launchIntent, true); } void launchMediaOutputGroupDialog(View mediaOutputDialog) { // We show the output group dialog from the output dialog. MediaOutputController controller = new MediaOutputController(mContext, mPackageName, mAboveStatusbar, mMediaSessionManager, mLocalBluetoothManager, mShadeController, - mActivityStarter, mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); + mActivityStarter, mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, + mDialogManager); MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar, - controller); + controller, mDialogManager); mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java index eca8ac90427b..7e2558ceb1e8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java @@ -28,6 +28,7 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; /** * Dialog for media output transferring. @@ -37,8 +38,9 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { final UiEventLogger mUiEventLogger; MediaOutputDialog(Context context, boolean aboveStatusbar, MediaOutputController - mediaOutputController, UiEventLogger uiEventLogger) { - super(context, mediaOutputController); + mediaOutputController, UiEventLogger uiEventLogger, + SystemUIDialogManager dialogManager) { + super(context, mediaOutputController, dialogManager); mUiEventLogger = uiEventLogger; mAdapter = new MediaOutputAdapter(mMediaOutputController, this); if (!aboveStatusbar) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt index b91901de5af3..a7bc85256fcd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt @@ -25,6 +25,7 @@ import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.phone.ShadeController +import com.android.systemui.statusbar.phone.SystemUIDialogManager import javax.inject.Inject /** @@ -38,7 +39,8 @@ class MediaOutputDialogFactory @Inject constructor( private val starter: ActivityStarter, private val notificationEntryManager: NotificationEntryManager, private val uiEventLogger: UiEventLogger, - private val dialogLaunchAnimator: DialogLaunchAnimator + private val dialogLaunchAnimator: DialogLaunchAnimator, + private val dialogManager: SystemUIDialogManager ) { companion object { var mediaOutputDialog: MediaOutputDialog? = null @@ -51,8 +53,9 @@ class MediaOutputDialogFactory @Inject constructor( val controller = MediaOutputController(context, packageName, aboveStatusBar, mediaSessionManager, lbm, shadeController, starter, notificationEntryManager, - uiEventLogger, dialogLaunchAnimator) - val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger) + uiEventLogger, dialogLaunchAnimator, dialogManager) + val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger, + dialogManager) mediaOutputDialog = dialog // Show the dialog. diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java index 1300400f3b66..478b890bf92d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java @@ -20,10 +20,12 @@ import android.content.Context; import android.os.Bundle; import android.view.View; import android.view.WindowManager; +import android.widget.LinearLayout; import androidx.core.graphics.drawable.IconCompat; import com.android.systemui.R; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; /** * Dialog for media output group. @@ -31,8 +33,8 @@ import com.android.systemui.R; public class MediaOutputGroupDialog extends MediaOutputBaseDialog { MediaOutputGroupDialog(Context context, boolean aboveStatusbar, MediaOutputController - mediaOutputController) { - super(context, mediaOutputController); + mediaOutputController, SystemUIDialogManager dialogManager) { + super(context, mediaOutputController, dialogManager); mMediaOutputController.resetGroupMediaDevices(); mAdapter = new MediaOutputGroupAdapter(mMediaOutputController); if (!aboveStatusbar) { @@ -76,6 +78,15 @@ public class MediaOutputGroupDialog extends MediaOutputBaseDialog { } @Override + void refresh() { + super.refresh(); + final int size = getHeaderIconSize(); + final int padding = mContext.getResources().getDimensionPixelSize( + R.dimen.media_output_dialog_header_icon_padding); + mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size)); + } + + @Override int getStopButtonVisibility() { return View.VISIBLE; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index cdf770f80387..c3de3c57724f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -110,6 +110,16 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } @Override + public int getTilesHeight() { + // Use the first page as that is the maximum height we need to show. + TileLayout tileLayout = mPages.get(0); + if (tileLayout == null) { + return 0; + } + return tileLayout.getTilesHeight(); + } + + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Pass configuration change to non-attached pages as well. Some config changes will cause diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 8588ddfcfa63..e230e1bf8051 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -27,7 +27,6 @@ import android.graphics.PointF; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; -import android.widget.ImageView; import com.android.systemui.Dumpable; import com.android.systemui.R; @@ -53,7 +52,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { private float mQsExpansion; private QSCustomizer mQSCustomizer; private NonInterceptingScrollView mQSPanelContainer; - private ImageView mDragHandle; private int mSideMargins; private boolean mQsDisabled; @@ -71,7 +69,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { mQSDetail = findViewById(R.id.qs_detail); mHeader = findViewById(R.id.header); mQSCustomizer = findViewById(R.id.qs_customize); - mDragHandle = findViewById(R.id.qs_drag_handle); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } @@ -190,23 +187,14 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { mQSDetail.setBottom(getTop() + scrollBottom); int qsDetailBottomMargin = ((MarginLayoutParams) mQSDetail.getLayoutParams()).bottomMargin; mQSDetail.setBottom(getTop() + scrollBottom - qsDetailBottomMargin); - // Pin the drag handle to the bottom of the panel. - mDragHandle.setTranslationY(scrollBottom - mDragHandle.getHeight()); } protected int calculateContainerHeight() { int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight(); // Need to add the dragHandle height so touches will be intercepted by it. - int dragHandleHeight; - if (mDragHandle.getVisibility() == VISIBLE) { - dragHandleHeight = Math.round((1 - mQsExpansion) * mDragHandle.getHeight()); - } else { - dragHandleHeight = 0; - } return mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight() : Math.round(mQsExpansion * (heightOverride - mHeader.getHeight())) - + mHeader.getHeight() - + dragHandleHeight; + + mHeader.getHeight(); } int calculateContainerBottom() { @@ -221,8 +209,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { public void setExpansion(float expansion) { mQsExpansion = expansion; mQSPanelContainer.setScrollingEnabled(expansion > 0f); - mDragHandle.setAlpha(1.0f - expansion); - mDragHandle.setClickable(expansion == 0f); // Only clickable when fully collapsed updateExpansion(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index ee59ae6ab6d7..e82e9d284bdd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -33,7 +33,6 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout.LayoutParams; -import android.widget.ImageView; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -94,7 +93,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private float mLastPanelFraction; private float mSquishinessFraction = 1; private boolean mQsDisabled; - private ImageView mQsDragHandler; private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; private final CommandQueue mCommandQueue; @@ -205,7 +203,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mHeader = view.findViewById(R.id.header); mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container)); mFooter = qsFragmentComponent.getQSFooter(); - mQsDragHandler = view.findViewById(R.id.qs_drag_handle); mQsDetailDisplayer.setQsPanelController(mQSPanelController); @@ -249,11 +246,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSPanelController.getMediaHost().getHostView().setAlpha(1.0f); mQSAnimator.requestAnimatorUpdate(); }); - - mQsDragHandler.setOnClickListener(v -> { - Log.d(TAG, "drag handler clicked"); - mCommandQueue.animateExpandSettingsPanel(null); - }); } @Override @@ -385,30 +377,26 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } private void updateQsState() { - final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling + final boolean expanded = mQsExpanded || mInSplitShade; + final boolean expandVisually = expanded || mStackScrollerOverscrolling || mHeaderAnimating; - mQSPanelController.setExpanded(mQsExpanded); - mQSDetail.setExpanded(mQsExpanded); + mQSPanelController.setExpanded(expanded); + mQSDetail.setExpanded(expanded); boolean keyguardShowing = isKeyguardState(); - mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating + mHeader.setVisibility((expanded || !keyguardShowing || mHeaderAnimating || mShowCollapsedOnKeyguard) ? View.VISIBLE : View.INVISIBLE); mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) - || (mQsExpanded && !mStackScrollerOverscrolling), mQuickQSPanelController); - mFooter.setVisibility(!mQsDisabled && (mQsExpanded || !keyguardShowing || mHeaderAnimating + || (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController); + mFooter.setVisibility(!mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating || mShowCollapsedOnKeyguard) ? View.VISIBLE : View.INVISIBLE); mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) - || (mQsExpanded && !mStackScrollerOverscrolling)); + || (expanded && !mStackScrollerOverscrolling)); mQSPanelController.setVisibility( !mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE); - mQsDragHandler.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating - || mShowCollapsedOnKeyguard) - && Utils.shouldUseSplitNotificationShade(getResources()) - ? View.VISIBLE - : View.GONE); } private boolean isKeyguardState() { @@ -418,7 +406,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } private void updateShowCollapsedOnKeyguard() { - boolean showCollapsed = mBypassController.getBypassEnabled() || mTransitioningToFullShade; + boolean showCollapsed = mBypassController.getBypassEnabled() + || (mTransitioningToFullShade && !mInSplitShade); if (showCollapsed != mShowCollapsedOnKeyguard) { mShowCollapsedOnKeyguard = showCollapsed; updateQsState(); @@ -498,6 +487,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void setInSplitShade(boolean inSplitShade) { mInSplitShade = inSplitShade; mQSAnimator.setTranslateWhileExpanding(inSplitShade); + updateShowCollapsedOnKeyguard(); + updateQsState(); } @Override @@ -516,7 +507,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void setQsExpansion(float expansion, float panelExpansionFraction, float proposedTranslation, float squishinessFraction) { float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation; - float progress = mTransitioningToFullShade ? mFullShadeProgress : panelExpansionFraction; + float progress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD + ? mFullShadeProgress : panelExpansionFraction; setAlphaAnimationProgress(mInSplitShade ? progress : 1); mContainer.setExpansion(expansion); final float translationScaleY = (mInSplitShade diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index d69deefc3477..20c0fdd7de89 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -258,13 +258,8 @@ public class QSPanel extends LinearLayout implements Tunable { } private void updateViewPositions() { - if (!(mTileLayout instanceof TileLayout)) { - return; - } - TileLayout layout = (TileLayout) mTileLayout; - // Adjust view positions based on tile squishing - int tileHeightOffset = layout.getTilesHeight() - layout.getHeight(); + int tileHeightOffset = mTileLayout.getTilesHeight() - mTileLayout.getHeight(); boolean move = false; for (int i = 0; i < getChildCount(); i++) { @@ -787,6 +782,12 @@ public class QSPanel extends LinearLayout implements Tunable { /** */ void setListening(boolean listening, UiEventLogger uiEventLogger); + /** */ + int getHeight(); + + /** */ + int getTilesHeight(); + /** * Sets a size modifier for the tile. Where 0 means collapsed, and 1 expanded. */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 6794d5b0cee4..001c740e310a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -45,6 +45,7 @@ import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.tuner.TunerService; +import com.android.systemui.util.Utils; import javax.inject.Inject; import javax.inject.Named; @@ -72,6 +73,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { new QSPanel.OnConfigurationChangedListener() { @Override public void onConfigurationChange(Configuration newConfig) { + updateMediaExpansion(); mView.updateResources(); mQsSecurityFooter.onConfigurationChanged(); if (mView.isListening()) { @@ -121,13 +123,17 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { @Override public void onInit() { super.onInit(); - mMediaHost.setExpansion(1); + updateMediaExpansion(); mMediaHost.setShowsOnlyActiveMedia(false); mMediaHost.init(MediaHierarchyManager.LOCATION_QS); mQsCustomizerController.init(); mBrightnessSliderController.init(); } + private void updateMediaExpansion() { + mMediaHost.setExpansion(Utils.shouldUseSplitNotificationShade(getResources()) ? 0 : 1); + } + @Override protected void onViewAttached() { super.onViewAttached(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 7f08e5bdb575..bff318a6f44e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -275,6 +275,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { return Math.max(mColumns * mRows, 1); } + @Override public int getTilesHeight() { return mLastTileBottom + getPaddingBottom(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index 7ba9cc22bec9..a2577d6e7f60 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -98,7 +98,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements toggleDataSaver(); Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true); }); - dialog.setNegativeButton(com.android.internal.R.string.cancel, null); + dialog.setNeutralButton(com.android.internal.R.string.cancel, null); dialog.setShowForAllUsers(true); if (view != null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 20805a141312..a339dcf73f74 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -190,7 +190,6 @@ public class DndTile extends QSTileImpl<BooleanState> { case Settings.Secure.ZEN_DURATION_PROMPT: mUiHandler.post(() -> { Dialog dialog = makeZenModeDialog(); - SystemUIDialog.registerDismissListener(dialog); if (view != null) { mDialogLaunchAnimator.showFromView(dialog, view, false); } else { @@ -211,10 +210,12 @@ public class DndTile extends QSTileImpl<BooleanState> { } private Dialog makeZenModeDialog() { - AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog) - .createDialog(); + AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog, + true /* cancelIsNeutral */).createDialog(); SystemUIDialog.applyFlags(dialog); SystemUIDialog.setShowForAllUsers(dialog, true); + SystemUIDialog.registerDismissListener(dialog); + SystemUIDialog.setDialogSize(dialog); return dialog; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 26c89ff83076..2a398496af77 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -78,7 +78,7 @@ public class InternetDialog extends SystemUIDialog implements private static final String TAG = "InternetDialog"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - static final long PROGRESS_DELAY_MS = 2000L; + static final long PROGRESS_DELAY_MS = 1500L; private final Handler mHandler; private final Executor mBackgroundExecutor; @@ -137,6 +137,8 @@ public class InternetDialog extends SystemUIDialog implements protected WifiEntry mConnectedWifiEntry; @VisibleForTesting protected int mWifiEntriesCount; + @VisibleForTesting + protected boolean mHasMoreEntry; // Wi-Fi scanning progress bar protected boolean mIsProgressBarVisible; @@ -157,7 +159,9 @@ public class InternetDialog extends SystemUIDialog implements if (DEBUG) { Log.d(TAG, "Init InternetDialog"); } - mContext = context; + + // Save the context that is wrapped with our theme. + mContext = getContext(); mHandler = handler; mBackgroundExecutor = executor; mInternetDialogFactory = internetDialogFactory; @@ -299,15 +303,11 @@ public class InternetDialog extends SystemUIDialog implements if (DEBUG) { Log.d(TAG, "updateDialog"); } - if (mInternetDialogController.isAirplaneModeEnabled()) { - mInternetDialogSubTitle.setVisibility(View.GONE); - mAirplaneModeLayout.setVisibility(View.VISIBLE); - } else { - mInternetDialogTitle.setText(getDialogTitleText()); - mInternetDialogSubTitle.setVisibility(View.VISIBLE); - mInternetDialogSubTitle.setText(getSubtitleText()); - mAirplaneModeLayout.setVisibility(View.GONE); - } + mInternetDialogTitle.setText(getDialogTitleText()); + mInternetDialogSubTitle.setText(getSubtitleText()); + mAirplaneModeLayout.setVisibility( + mInternetDialogController.isAirplaneModeEnabled() ? View.VISIBLE : View.GONE); + updateEthernet(); if (shouldUpdateMobileNetwork) { setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular(), @@ -464,8 +464,7 @@ public class InternetDialog extends SystemUIDialog implements } mWifiRecyclerView.setMinimumHeight(mWifiNetworkHeight * getWifiListMaxCount()); mWifiRecyclerView.setVisibility(View.VISIBLE); - final boolean showSeeAll = mConnectedWifiEntry != null || mWifiEntriesCount > 0; - mSeeAllLayout.setVisibility(showSeeAll ? View.VISIBLE : View.INVISIBLE); + mSeeAllLayout.setVisibility(mHasMoreEntry ? View.VISIBLE : View.INVISIBLE); } @VisibleForTesting @@ -549,9 +548,13 @@ public class InternetDialog extends SystemUIDialog implements } private void setProgressBarVisible(boolean visible) { + if (mIsProgressBarVisible == visible) { + return; + } mIsProgressBarVisible = visible; - mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE); - mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE); + mProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE); + mProgressBar.setIndeterminate(visible); + mDivider.setVisibility(visible ? View.GONE : View.VISIBLE); mInternetDialogSubTitle.setText(getSubtitleText()); } @@ -651,13 +654,14 @@ public class InternetDialog extends SystemUIDialog implements @Override @WorkerThread public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries, - @Nullable WifiEntry connectedEntry) { + @Nullable WifiEntry connectedEntry, boolean hasMoreEntry) { // Should update the carrier network layout when it is connected under airplane mode ON. boolean shouldUpdateCarrierNetwork = mMobileNetworkLayout.getVisibility() == View.VISIBLE && mInternetDialogController.isAirplaneModeEnabled(); mHandler.post(() -> { mConnectedWifiEntry = connectedEntry; mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size(); + mHasMoreEntry = hasMoreEntry; updateDialog(shouldUpdateCarrierNetwork /* shouldUpdateMobileNetwork */); mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount); mAdapter.notifyDataSetChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 6f63a08c6c0f..4908d4fb214e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -316,15 +316,11 @@ public class InternetDialogController implements AccessPointController.AccessPoi } CharSequence getSubtitleText(boolean isProgressBarVisible) { - if (isAirplaneModeEnabled()) { - return null; - } - if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) { - // When the airplane mode is off and Wi-Fi is disabled. + // When Wi-Fi is disabled. // Sub-Title: Wi-Fi is off if (DEBUG) { - Log.d(TAG, "Airplane mode off + Wi-Fi off."); + Log.d(TAG, "Wi-Fi off."); } return mContext.getText(SUBTITLE_TEXT_WIFI_IS_OFF); } @@ -348,6 +344,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS); } + if (isCarrierNetworkActive()) { + return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE); + } + // Sub-Title: // show non_carrier_network_unavailable // - while Wi-Fi on + no Wi-Fi item @@ -878,21 +878,24 @@ public class InternetDialogController implements AccessPointController.AccessPoi if (accessPoints == null || accessPoints.size() == 0) { mConnectedEntry = null; mWifiEntriesCount = 0; - if (mCallback != null) { - mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */); - } + mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */, + false /* hasMoreEntry */); return; } + boolean hasMoreEntry = false; int count = MAX_WIFI_ENTRY_COUNT; if (mHasEthernet) { count -= 1; } - if (hasActiveSubId()) { + if (hasActiveSubId() || isCarrierNetworkActive()) { count -= 1; } - if (count > accessPoints.size()) { - count = accessPoints.size(); + final int wifiTotalCount = accessPoints.size(); + if (count > wifiTotalCount) { + count = wifiTotalCount; + } else if (count < wifiTotalCount) { + hasMoreEntry = true; } WifiEntry connectedEntry = null; @@ -908,9 +911,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi mConnectedEntry = connectedEntry; mWifiEntriesCount = wifiEntries.size(); - if (mCallback != null) { - mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry); - } + mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry, hasMoreEntry); } @Override @@ -1060,7 +1061,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi void dismissDialog(); void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries, - @Nullable WifiEntry connectedEntry); + @Nullable WifiEntry connectedEntry, boolean hasMoreEntry); } void makeOverlayToast(int stringId) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 85bf98c09f59..7f130cb203c0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -16,6 +16,7 @@ package com.android.systemui.recents; +import static com.android.systemui.shared.recents.utilities.Utilities.isTablet; import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; @@ -248,8 +249,8 @@ public class ScreenPinningRequest implements View.OnClickListener, .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); View buttons = mLayout.findViewById(R.id.screen_pinning_buttons); WindowManagerWrapper wm = WindowManagerWrapper.getInstance(); - if (!QuickStepContract.isGesturalMode(mNavBarMode) - && wm.hasSoftNavigationBar(mContext.getDisplayId())) { + if (!QuickStepContract.isGesturalMode(mNavBarMode) + && wm.hasSoftNavigationBar(mContext.getDisplayId()) && !isTablet(mContext)) { buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); swapChildrenIfRtlAndVertical(buttons); } else { diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt index c50365f1bf38..71c5fad5322e 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt @@ -15,7 +15,8 @@ import com.android.systemui.statusbar.phone.SystemUIDialog class SensorUseDialog( context: Context, val sensor: Int, - val clickListener: DialogInterface.OnClickListener + val clickListener: DialogInterface.OnClickListener, + val dismissListener: DialogInterface.OnDismissListener ) : SystemUIDialog(context) { // TODO move to onCreate (b/200815309) @@ -69,6 +70,8 @@ class SensorUseDialog( context.getString(com.android.internal.R.string .cancel), clickListener) + setOnDismissListener(dismissListener) + setCancelable(false) } } diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index b0071d92481d..dae375ad7cc7 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -50,7 +50,7 @@ class SensorUseStartedActivity @Inject constructor( private val keyguardStateController: KeyguardStateController, private val keyguardDismissUtil: KeyguardDismissUtil, @Background private val bgHandler: Handler -) : Activity(), DialogInterface.OnClickListener { +) : Activity(), DialogInterface.OnClickListener, DialogInterface.OnDismissListener { companion object { private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName @@ -120,7 +120,7 @@ class SensorUseStartedActivity @Inject constructor( } } - mDialog = SensorUseDialog(this, sensor, this) + mDialog = SensorUseDialog(this, sensor, this, this) mDialog!!.show() } @@ -212,4 +212,8 @@ class SensorUseStartedActivity @Inject constructor( .suppressSensorPrivacyReminders(sensor, suppressed) } } + + override fun onDismiss(dialog: DialogInterface?) { + finish() + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index f43d9c350d62..d7b4738340e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -887,7 +887,13 @@ public class KeyguardIndicationController { mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState); } } else { - showBiometricMessage(mContext.getString(R.string.keyguard_unlock)); + if (mKeyguardUpdateMonitor.isUdfpsSupported() + && mKeyguardUpdateMonitor.getUserCanSkipBouncer( + KeyguardUpdateMonitor.getCurrentUser())) { + showBiometricMessage(mContext.getString(R.string.keyguard_unlock_press)); + } else { + showBiometricMessage(mContext.getString(R.string.keyguard_unlock)); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index dca7f70d3470..0fb08e403483 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -174,7 +174,7 @@ class LockscreenShadeTransitionController @Inject constructor( internal fun canDragDown(): Boolean { return (statusBarStateController.state == StatusBarState.KEYGUARD || nsslController.isInLockedDownShade()) && - qS.isFullyCollapsed + (qS.isFullyCollapsed || useSplitShade) } /** @@ -285,7 +285,7 @@ class LockscreenShadeTransitionController @Inject constructor( internal val isDragDownAnywhereEnabled: Boolean get() = (statusBarStateController.getState() == StatusBarState.KEYGUARD && !keyguardBypassController.bypassEnabled && - qS.isFullyCollapsed) + (qS.isFullyCollapsed || useSplitShade)) /** * The amount in pixels that the user has dragged down. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java index c4fadff16c09..4551807499ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java @@ -40,7 +40,7 @@ public class UserUtil { super(context); setTitle(R.string.user_remove_user_title); setMessage(context.getString(R.string.user_remove_user_message)); - setButton(DialogInterface.BUTTON_NEGATIVE, + setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.user_remove_user_remove), this); @@ -51,7 +51,7 @@ public class UserUtil { @Override public void onClick(DialogInterface dialog, int which) { - if (which == BUTTON_NEGATIVE) { + if (which == BUTTON_NEUTRAL) { cancel(); } else { dismiss(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt new file mode 100644 index 000000000000..a1d086b5d768 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.charging + +import android.graphics.Color +import android.graphics.PointF +import android.graphics.RuntimeShader +import android.util.MathUtils + +/** + * Shader class that renders a distorted ripple for the UDFPS dwell effect. + * Adjustable shader parameters: + * - progress + * - origin + * - color + * - time + * - maxRadius + * - distortionStrength. + * See per field documentation for more details. + * + * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java. + */ +class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) { + companion object { + private const val SHADER_UNIFORMS = """uniform vec2 in_origin; + uniform float in_time; + uniform float in_radius; + uniform float in_blur; + uniform vec4 in_color; + uniform float in_phase1; + uniform float in_phase2; + uniform float in_distortion_strength;""" + private const val SHADER_LIB = """ + float softCircle(vec2 uv, vec2 xy, float radius, float blur) { + float blurHalf = blur * 0.5; + float d = distance(uv, xy); + return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius); + } + + float softRing(vec2 uv, vec2 xy, float radius, float blur) { + float thickness_half = radius * 0.25; + float circle_outer = softCircle(uv, xy, radius + thickness_half, blur); + float circle_inner = softCircle(uv, xy, radius - thickness_half, blur); + return circle_outer - circle_inner; + } + + vec2 distort(vec2 p, float time, float distort_amount_xy, float frequency) { + return p + vec2(sin(p.x * frequency + in_phase1), + cos(p.y * frequency * 1.23 + in_phase2)) * distort_amount_xy; + } + + vec4 ripple(vec2 p, float distort_xy, float frequency) { + vec2 p_distorted = distort(p, in_time, distort_xy, frequency); + float circle = softCircle(p_distorted, in_origin, in_radius * 1.2, in_blur); + float rippleAlpha = max(circle, + softRing(p_distorted, in_origin, in_radius, in_blur)) * 0.25; + return in_color * rippleAlpha; + } + """ + private const val SHADER_MAIN = """vec4 main(vec2 p) { + vec4 color1 = ripple(p, + 12 * in_distortion_strength, // distort_xy + 0.012 // frequency + ); + vec4 color2 = ripple(p, + 17.5 * in_distortion_strength, // distort_xy + 0.018 // frequency + ); + // Alpha blend between two layers. + return vec4(color1.xyz + color2.xyz + * (1 - color1.w), color1.w + color2.w * (1-color1.w)); + }""" + private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN + } + + /** + * Maximum radius of the ripple. + */ + var maxRadius: Float = 0.0f + + /** + * Origin coordinate of the ripple. + */ + var origin: PointF = PointF() + set(value) { + field = value + setUniform("in_origin", floatArrayOf(value.x, value.y)) + } + + /** + * Progress of the ripple. Float value between [0, 1]. + */ + var progress: Float = 0.0f + set(value) { + field = value + setUniform("in_radius", + (1 - (1 - value) * (1 - value) * (1 - value))* maxRadius) + setUniform("in_blur", MathUtils.lerp(1f, 0.7f, value)) + } + + /** + * Distortion strength between [0, 1], with 0 being no distortion and 1 being full distortion. + */ + var distortionStrength: Float = 0.0f + set(value) { + field = value + setUniform("in_distortion_strength", value) + } + + /** + * Play time since the start of the effect in seconds. + */ + var time: Float = 0.0f + set(value) { + field = value * 0.001f + setUniform("in_time", field) + setUniform("in_phase1", field * 2f + 0.367f) + setUniform("in_phase2", field * 5.2f * 1.531f) + } + + /** + * A hex value representing the ripple color, in the format of ARGB + */ + var color: Int = 0xffffff.toInt() + set(value) { + field = value + val color = Color.valueOf(value) + setUniform("in_color", floatArrayOf(color.red(), + color.green(), color.blue(), color.alpha())) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java index 103ca0ebc6ca..ff9d919dda13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java @@ -83,7 +83,7 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> @Override public void notifyListeners(SignalCallback callback) { if (mCurrentState.isCarrierMerged) { - if (mCurrentState.isDefault) { + if (mCurrentState.isDefault || !mNetworkController.isRadioOn()) { notifyListenersForCarrierWifi(callback); } } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index 1d921702e632..f2d926d97108 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -25,7 +25,6 @@ import android.service.dreams.IDreamManager; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; -import com.android.systemui.animation.LaunchAnimator; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; @@ -301,24 +300,15 @@ public interface StatusBarDependenciesModule { */ @Provides @SysUISingleton - static LaunchAnimator provideLaunchAnimator(Context context) { - return new LaunchAnimator(context); + static ActivityLaunchAnimator provideActivityLaunchAnimator() { + return new ActivityLaunchAnimator(); } /** */ @Provides @SysUISingleton - static ActivityLaunchAnimator provideActivityLaunchAnimator(LaunchAnimator launchAnimator) { - return new ActivityLaunchAnimator(launchAnimator); - } - - /** - */ - @Provides - @SysUISingleton - static DialogLaunchAnimator provideDialogLaunchAnimator(Context context, - LaunchAnimator launchAnimator, IDreamManager dreamManager) { - return new DialogLaunchAnimator(context, launchAnimator, dreamManager); + static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager) { + return new DialogLaunchAnimator(dreamManager); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt index 64a73054c434..349b1918a504 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt @@ -2,6 +2,7 @@ package com.android.systemui.statusbar.notification import android.util.MathUtils import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.Interpolators import com.android.systemui.animation.LaunchAnimator import kotlin.math.min @@ -55,6 +56,7 @@ class ExpandAnimationParameters( } fun getProgress(delay: Long, duration: Long): Float { - return LaunchAnimator.getProgress(linearProgress, delay, duration) + return LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay, + duration) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 4441270f895b..fa9217d831f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -20,6 +20,7 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.Trace; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; @@ -158,6 +159,9 @@ public class NotificationLogger implements StateListener { mExpansionStateLogger.onVisibilityChanged( mTmpCurrentlyVisibleNotifications, mTmpCurrentlyVisibleNotifications); + Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Active]", N); + Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Visible]", + mCurrentlyVisibleNotifications.size()); recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications); mTmpCurrentlyVisibleNotifications.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 2eb20654716d..63cb4ae39a58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -579,14 +579,24 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (contentView.hasOverlappingRendering()) { int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE : LAYER_TYPE_HARDWARE; - int currentLayerType = contentView.getLayerType(); - if (currentLayerType != layerType) { - contentView.setLayerType(layerType, null); - } + contentView.setLayerType(layerType, null); } contentView.setAlpha(contentAlpha); + // After updating the current view, reset all views. + if (contentAlpha == 1f) { + resetAllContentAlphas(); + } } + /** + * If a subclass's {@link #getContentView()} returns different views depending on state, + * this method is an opportunity to reset the alpha of ALL content views, not just the + * current one, which may prevent a content view that is temporarily hidden from being reset. + * + * This should setAlpha(1.0f) and setLayerType(LAYER_TYPE_NONE) for all content views. + */ + protected void resetAllContentAlphas() {} + @Override protected void applyRoundness() { super.applyRoundness(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 23a0a750561c..819f5a3f6dc2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2130,15 +2130,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public void setExpandAnimationRunning(boolean expandAnimationRunning) { - View contentView; - if (mIsSummaryWithChildren) { - contentView = mChildrenContainer; - } else { - contentView = getShowingLayout(); - } - if (mGuts != null && mGuts.isExposed()) { - contentView = mGuts; - } if (expandAnimationRunning) { setAboveShelf(true); mExpandAnimationRunning = true; @@ -2151,9 +2142,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mGuts != null) { mGuts.setAlpha(1.0f); } - if (contentView != null) { - contentView.setAlpha(1.0f); - } + resetAllContentAlphas(); setExtraWidthForClipping(0.0f); if (mNotificationParent != null) { mNotificationParent.setExtraWidthForClipping(0.0f); @@ -2601,10 +2590,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout.animate().cancel(); if (mChildrenContainer != null) { mChildrenContainer.animate().cancel(); - mChildrenContainer.setAlpha(1f); } - mPublicLayout.setAlpha(1f); - mPrivateLayout.setAlpha(1f); + resetAllContentAlphas(); mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); updateChildrenVisibility(); } else { @@ -2631,7 +2618,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView .alpha(0f) .setStartDelay(delay) .setDuration(duration) - .withEndAction(() -> hiddenView.setVisibility(View.INVISIBLE)); + .withEndAction(() -> { + hiddenView.setVisibility(View.INVISIBLE); + resetAllContentAlphas(); + }); } for (View showView : shownChildren) { showView.setVisibility(View.VISIBLE); @@ -2754,12 +2744,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (wasAppearing) { // During the animation the visible view might have changed, so let's make sure all // alphas are reset - if (mChildrenContainer != null) { - mChildrenContainer.setAlpha(1.0f); - } - for (NotificationContentView l : mLayouts) { - l.setAlpha(1.0f); - } + resetAllContentAlphas(); if (FADE_LAYER_OPTIMIZATION_ENABLED) { setNotificationFaded(false); } else { @@ -2770,6 +2755,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + @Override + protected void resetAllContentAlphas() { + mPrivateLayout.setAlpha(1f); + mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null); + mPublicLayout.setAlpha(1f); + mPublicLayout.setLayerType(LAYER_TYPE_NONE, null); + if (mChildrenContainer != null) { + mChildrenContainer.setAlpha(1f); + mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null); + } + } + /** Gets the last value set with {@link #setNotificationFaded(boolean)} */ @Override public boolean isNotificationFaded() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index a9cc3237d719..e65846865ef8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -59,6 +59,7 @@ public class AmbientState { private float mMaxHeadsUpTranslation; private boolean mDismissAllInProgress; private int mLayoutMinHeight; + private int mLayoutMaxHeight; private NotificationShelf mShelf; private int mZDistanceBetweenElements; private int mBaseZHeight; @@ -326,6 +327,14 @@ public class AmbientState { mLayoutHeight = layoutHeight; } + public void setLayoutMaxHeight(int maxLayoutHeight) { + mLayoutMaxHeight = maxLayoutHeight; + } + + public int getLayoutMaxHeight() { + return mLayoutMaxHeight; + } + public float getTopPadding() { return mTopPadding; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index ab08865c8bef..16c9fa79f24c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -119,6 +119,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -681,6 +682,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0) && mIsCurrentUserSetup // see: b/193149550 && mStatusBarState != StatusBarState.KEYGUARD + && mQsExpansionFraction != 1 && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() && !mIsRemoteInputActive; boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), @@ -726,36 +728,57 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } if (DEBUG) { - int y = mTopPadding; - mDebugPaint.setColor(Color.RED); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + onDrawDebug(canvas); + } + } - y = getLayoutHeight(); - mDebugPaint.setColor(Color.YELLOW); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + /** Used to track the Y positions that were already used to draw debug text labels. */ + private static final Set<Integer> DEBUG_TEXT_USED_Y_POSITIONS = + DEBUG ? new HashSet<>() : Collections.emptySet(); - y = (int) mMaxLayoutHeight; - mDebugPaint.setColor(Color.MAGENTA); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + private void onDrawDebug(Canvas canvas) { + DEBUG_TEXT_USED_Y_POSITIONS.clear(); - if (mKeyguardBottomPadding >= 0) { - y = getHeight() - (int) mKeyguardBottomPadding; - mDebugPaint.setColor(Color.GRAY); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); - } + int y = mTopPadding; + drawDebugInfo(canvas, y, Color.RED, /* label= */ "mTopPadding"); + + y = getLayoutHeight(); + drawDebugInfo(canvas, y, Color.YELLOW, /* label= */ "getLayoutHeight()"); + + y = (int) mMaxLayoutHeight; + drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "mMaxLayoutHeight"); + + if (mKeyguardBottomPadding >= 0) { + y = getHeight() - (int) mKeyguardBottomPadding; + drawDebugInfo(canvas, y, Color.GRAY, + /* label= */ "getHeight() - mKeyguardBottomPadding"); + } - y = getHeight() - getEmptyBottomMargin(); - mDebugPaint.setColor(Color.GREEN); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + y = getHeight() - getEmptyBottomMargin(); + drawDebugInfo(canvas, y, Color.GREEN, /* label= */ "getHeight() - getEmptyBottomMargin()"); - y = (int) (mAmbientState.getStackY()); - mDebugPaint.setColor(Color.CYAN); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + y = (int) (mAmbientState.getStackY()); + drawDebugInfo(canvas, y, Color.CYAN, /* label= */ "mAmbientState.getStackY()"); - y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight()); - mDebugPaint.setColor(Color.BLUE); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight()); + drawDebugInfo(canvas, y, Color.BLUE, + /* label= */ "mAmbientState.getStackY() + mAmbientState.getStackHeight()"); + } + + private void drawDebugInfo(Canvas canvas, int y, int color, String label) { + mDebugPaint.setColor(color); + canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ getWidth(), /* stopY= */ y, + mDebugPaint); + canvas.drawText(label, /* x= */ 0, /* y= */ computeDebugYTextPosition(y), mDebugPaint); + } + + private int computeDebugYTextPosition(int lineY) { + int textY = lineY; + while (DEBUG_TEXT_USED_Y_POSITIONS.contains(textY)) { + textY = (int) (textY + mDebugPaint.getTextSize()); } + DEBUG_TEXT_USED_Y_POSITIONS.add(textY); + return textY; } @ShadeViewRefactor(RefactorComponent.DECORATOR) @@ -1087,6 +1110,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) private void updateAlgorithmHeightAndPadding() { mAmbientState.setLayoutHeight(getLayoutHeight()); + mAmbientState.setLayoutMaxHeight(mMaxLayoutHeight); updateAlgorithmLayoutMinHeight(); mAmbientState.setTopPadding(mTopPadding); } @@ -3962,6 +3986,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateChronometers(); requestChildrenUpdate(); updateUseRoundedRectClipping(); + updateDismissBehavior(); } } @@ -4744,6 +4769,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setQsExpansionFraction(float qsExpansionFraction) { + boolean footerAffected = mQsExpansionFraction != qsExpansionFraction + && (mQsExpansionFraction == 1 || qsExpansionFraction == 1); mQsExpansionFraction = qsExpansionFraction; updateUseRoundedRectClipping(); @@ -4752,6 +4779,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mOwnScrollY > 0) { setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction)); } + if (footerAffected) { + updateFooter(); + } } @ShadeViewRefactor(RefactorComponent.COORDINATOR) @@ -4903,6 +4933,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable StringBuilder sb = new StringBuilder("[") .append(this.getClass().getSimpleName()).append(":") .append(" pulsing=").append(mPulsing ? "T" : "f") + .append(" expanded=").append(mIsExpanded ? "T" : "f") + .append(" headsUpPinned=").append(mInHeadsUpPinnedMode ? "T" : "f") + .append(" qsClipping=").append(mShouldUseRoundedRectClipping ? "T" : "f") + .append(" qsClipDismiss=").append(mDismissUsingRowTranslationX ? "T" : "f") .append(" visibility=").append(DumpUtilsKt.visibilityString(getVisibility())) .append(" alpha=").append(getAlpha()) .append(" scrollY=").append(mAmbientState.getScrollY()) @@ -5430,7 +5464,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // On the split keyguard, dismissing with clipping without a visual boundary looks odd, // so let's use the content dismiss behavior instead. boolean dismissUsingRowTranslationX = !mShouldUseSplitNotificationShade - || mStatusBarState != StatusBarState.KEYGUARD; + || (mStatusBarState != StatusBarState.KEYGUARD && mIsExpanded); if (mDismissUsingRowTranslationX != dismissUsingRowTranslationX) { mDismissUsingRowTranslationX = dismissUsingRowTranslationX; for (int i = 0; i < getChildCount(); i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 015edb8e5541..2c70a5fd9ce7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -29,6 +29,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.R; import com.android.systemui.animation.ShadeInterpolation; +import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -60,6 +61,7 @@ public class StackScrollAlgorithm { @VisibleForTesting float mHeadsUpInset; private int mPinnedZTranslationExtra; private float mNotificationScrimPadding; + private int mCloseHandleUnderlapHeight; public StackScrollAlgorithm( Context context, @@ -85,6 +87,7 @@ public class StackScrollAlgorithm { R.dimen.heads_up_pinned_elevation); mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings); + mCloseHandleUnderlapHeight = res.getDimensionPixelSize(R.dimen.close_handle_underlap); } /** @@ -459,13 +462,17 @@ public class StackScrollAlgorithm { && !hasOngoingNotifs(algorithmState)); } } else { - if (view != ambientState.getTrackedHeadsUpRow()) { + if (view instanceof EmptyShadeView) { + float fullHeight = ambientState.getLayoutMaxHeight() + mCloseHandleUnderlapHeight + - ambientState.getStackY(); + viewState.yTranslation = (fullHeight - getMaxAllowedChildHeight(view)) / 2f; + } else if (view != ambientState.getTrackedHeadsUpRow()) { if (ambientState.isExpansionChanging()) { // We later update shelf state, then hide views below the shelf. viewState.hidden = false; viewState.inShelf = algorithmState.firstViewInShelf != null && i >= algorithmState.visibleChildren.indexOf( - algorithmState.firstViewInShelf); + algorithmState.firstViewInShelf); } else if (ambientState.getShelf() != null) { // When pulsing (incoming notification on AOD), innerHeight is 0; clamp all // to shelf start, thereby hiding all notifications (except the first one, which diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index aa3b3e12b8f8..ad1c23283912 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -74,7 +74,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000; private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock"; private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); - private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3; + private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 2; @IntDef(prefix = { "MODE_" }, value = { MODE_NONE, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 0312c30c73ec..5eb35ac74732 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -110,6 +110,7 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.DejankUtils; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.LaunchAnimator; import com.android.systemui.biometrics.AuthController; @@ -225,7 +226,8 @@ public class NotificationPanelViewController extends PanelViewController { */ private static final int FLING_HIDE = 2; private static final long ANIMATION_DELAY_ICON_FADE_IN = - LaunchAnimator.ANIMATION_DURATION - CollapsedStatusBarFragment.FADE_IN_DURATION + ActivityLaunchAnimator.TIMINGS.getTotalDuration() + - CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY - 48; private final DozeParameters mDozeParameters; @@ -2246,11 +2248,14 @@ public class NotificationPanelViewController extends PanelViewController { private void updateQsExpansion() { if (mQs == null) return; - float qsExpansionFraction = computeQsExpansionFraction(); - float squishiness = mNotificationStackScrollLayoutController - .getNotificationSquishinessFraction(); - mQs.setQsExpansion(qsExpansionFraction, getExpandedFraction(), getHeaderTranslation(), - mQsExpandImmediate || mQsExpanded ? 1f : squishiness); + final float squishiness = + mQsExpandImmediate || mQsExpanded ? 1f : mNotificationStackScrollLayoutController + .getNotificationSquishinessFraction(); + final float qsExpansionFraction = computeQsExpansionFraction(); + final float adjustedExpansionFraction = mShouldUseSplitNotificationShade + ? 1f : computeQsExpansionFraction(); + mQs.setQsExpansion(adjustedExpansionFraction, getExpandedFraction(), getHeaderTranslation(), + squishiness); mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); mMediaHierarchyManager.setQsExpansion(qsExpansionFraction); int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction); @@ -3257,12 +3262,6 @@ public class NotificationPanelViewController extends PanelViewController { mStatusBarStateController.setState(KEYGUARD); } return true; - case StatusBarState.SHADE: - - // This gets called in the middle of the touch handling, where the state is still - // that we are tracking the panel. Collapse the panel after this is done. - mView.post(mPostCollapseRunnable); - return false; default: return true; } @@ -3626,8 +3625,8 @@ public class NotificationPanelViewController extends PanelViewController { } public void applyLaunchAnimationProgress(float linearProgress) { - boolean hideIcons = LaunchAnimator.getProgress(linearProgress, - ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f; + boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, + linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f; if (hideIcons != mHideIconsDuringLaunchAnimation) { mHideIconsDuringLaunchAnimation = hideIcons; if (!hideIcons) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 2823d985102f..2bf16fc9e52c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -928,7 +928,6 @@ public abstract class PanelViewController { private void abortAnimations() { cancelHeightAnimator(); - mView.removeCallbacks(mPostCollapseRunnable); mView.removeCallbacks(mFlingCollapseRunnable); } @@ -1105,13 +1104,6 @@ public abstract class PanelViewController { return onMiddleClicked(); } - protected final Runnable mPostCollapseRunnable = new Runnable() { - @Override - public void run() { - collapse(false /* delayed */, 1.0f /* speedUpFactor */); - } - }; - protected abstract boolean onMiddleClicked(); protected abstract boolean isDozing(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt index 32aae6c05df6..2ba37c2ec29f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt @@ -23,7 +23,8 @@ class StatusBarLaunchAnimatorController( delegate.onLaunchAnimationStart(isExpandingFullyAbove) statusBar.notificationPanelViewController.setIsLaunchAnimationRunning(true) if (!isExpandingFullyAbove) { - statusBar.collapsePanelWithDuration(LaunchAnimator.ANIMATION_DURATION.toInt()) + statusBar.collapsePanelWithDuration( + ActivityLaunchAnimator.TIMINGS.totalDuration.toInt()) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 43264b600a0e..93f9892cf5b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -45,6 +45,10 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; /** * Base class for dialogs that should appear over panels and keyguard. + * + * Optionally provide a {@link SystemUIDialogManager} to its constructor to send signals to + * listeners on whether this dialog is showing. + * * The SystemUIDialog registers a listener for the screen off / close system dialogs broadcast, * and dismisses itself when it receives the broadcast. */ @@ -54,8 +58,9 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh "persist.systemui.flag_tablet_dialog_width"; private final Context mContext; - private final DismissReceiver mDismissReceiver; + @Nullable private final DismissReceiver mDismissReceiver; private final Handler mHandler = new Handler(); + @Nullable private final SystemUIDialogManager mDialogManager; private int mLastWidth = Integer.MIN_VALUE; private int mLastHeight = Integer.MIN_VALUE; @@ -66,11 +71,22 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh this(context, R.style.Theme_SystemUI_Dialog); } + public SystemUIDialog(Context context, SystemUIDialogManager dialogManager) { + this(context, R.style.Theme_SystemUI_Dialog, true, dialogManager); + } + public SystemUIDialog(Context context, int theme) { this(context, theme, true /* dismissOnDeviceLock */); } - public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) { + this(context, theme, dismissOnDeviceLock, null); + } + + /** + * @param udfpsDialogManager If set, UDFPS will hide if this dialog is showing. + */ + public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock, + SystemUIDialogManager dialogManager) { super(context, theme); mContext = context; @@ -80,6 +96,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh getWindow().setAttributes(attrs); mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this) : null; + mDialogManager = dialogManager; } @Override @@ -126,30 +143,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh * the device configuration changes, and the result will be used to resize this dialog window. */ protected int getWidth() { - boolean isOnTablet = - mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600; - if (!isOnTablet) { - return ViewGroup.LayoutParams.MATCH_PARENT; - } - - int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0); - if (flagValue == -1) { - // The width of bottom sheets (624dp). - return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624, - mContext.getResources().getDisplayMetrics())); - } else if (flagValue == -2) { - // The suggested small width for all dialogs (348dp) - return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348, - mContext.getResources().getDisplayMetrics())); - } else if (flagValue > 0) { - // Any given width. - return Math.round( - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue, - mContext.getResources().getDisplayMetrics())); - } else { - // By default we use the same width as the notification shade in portrait mode (504dp). - return mContext.getResources().getDimensionPixelSize(R.dimen.large_dialog_width); - } + return getDefaultDialogWidth(mContext); } /** @@ -157,7 +151,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh * the device configuration changes, and the result will be used to resize this dialog window. */ protected int getHeight() { - return ViewGroup.LayoutParams.WRAP_CONTENT; + return getDefaultDialogHeight(); } @Override @@ -168,6 +162,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mDismissReceiver.register(); } + if (mDialogManager != null) { + mDialogManager.setShowing(this, true); + } + // Listen for configuration changes to resize this dialog window. This is mostly necessary // for foldables that often go from large <=> small screen when folding/unfolding. ViewRootImpl.addConfigCallback(this); @@ -181,6 +179,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mDismissReceiver.unregister(); } + if (mDialogManager != null) { + mDialogManager.setShowing(this, false); + } + ViewRootImpl.removeConfigCallback(this); } @@ -267,6 +269,45 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh dismissReceiver.register(); } + /** Set an appropriate size to {@code dialog} depending on the current configuration. */ + public static void setDialogSize(Dialog dialog) { + // We need to create the dialog first, otherwise the size will be overridden when it is + // created. + dialog.create(); + dialog.getWindow().setLayout(getDefaultDialogWidth(dialog.getContext()), + getDefaultDialogHeight()); + } + + private static int getDefaultDialogWidth(Context context) { + boolean isOnTablet = context.getResources().getConfiguration().smallestScreenWidthDp >= 600; + if (!isOnTablet) { + return ViewGroup.LayoutParams.MATCH_PARENT; + } + + int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0); + if (flagValue == -1) { + // The width of bottom sheets (624dp). + return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624, + context.getResources().getDisplayMetrics())); + } else if (flagValue == -2) { + // The suggested small width for all dialogs (348dp) + return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348, + context.getResources().getDisplayMetrics())); + } else if (flagValue > 0) { + // Any given width. + return Math.round( + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue, + context.getResources().getDisplayMetrics())); + } else { + // By default we use the same width as the notification shade in portrait mode (504dp). + return context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width); + } + } + + private static int getDefaultDialogHeight() { + return ViewGroup.LayoutParams.WRAP_CONTENT; + } + private static class DismissReceiver extends BroadcastReceiver { private static final IntentFilter INTENT_FILTER = new IntentFilter(); static { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java new file mode 100644 index 000000000000..204f710b633a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import androidx.annotation.NonNull; + +import com.android.systemui.Dumpable; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Set; + +import javax.inject.Inject; + +/** + * Register dialogs to this manager if extraneous affordances (like the UDFPS sensor area) + * should be hidden from the screen when the dialog shows. + * + * Currently, only used if UDFPS is supported on the device; however, can be extended in the future + * for other use cases. + */ +@SysUISingleton +public class SystemUIDialogManager implements Dumpable { + private final StatusBarKeyguardViewManager mKeyguardViewManager; + + private final Set<SystemUIDialog> mDialogsShowing = new HashSet<>(); + private final Set<Listener> mListeners = new HashSet<>(); + + @Inject + public SystemUIDialogManager( + DumpManager dumpManager, + StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + dumpManager.registerDumpable(this); + mKeyguardViewManager = statusBarKeyguardViewManager; + } + + /** + * Whether listeners should hide affordances like the UDFPS sensor icon. + */ + public boolean shouldHideAffordance() { + return !mDialogsShowing.isEmpty(); + } + + /** + * Register a listener to receive callbacks. + */ + public void registerListener(@NonNull Listener listener) { + mListeners.add(listener); + } + + /** + * Unregister a listener from receiving callbacks. + */ + public void unregisterListener(@NonNull Listener listener) { + mListeners.remove(listener); + } + + void setShowing(SystemUIDialog dialog, boolean showing) { + final boolean wasHidingAffordances = shouldHideAffordance(); + if (showing) { + mDialogsShowing.add(dialog); + } else { + mDialogsShowing.remove(dialog); + } + + if (wasHidingAffordances != shouldHideAffordance()) { + updateDialogListeners(); + } + } + + private void updateDialogListeners() { + if (shouldHideAffordance()) { + mKeyguardViewManager.resetAlternateAuth(true); + } + + for (Listener listener : mListeners) { + listener.shouldHideAffordances(shouldHideAffordance()); + } + } + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("listeners:"); + for (Listener listener : mListeners) { + pw.println("\t" + listener); + } + pw.println("dialogs tracked:"); + for (SystemUIDialog dialog : mDialogsShowing) { + pw.println("\t" + dialog); + } + } + + /** SystemUIDialogManagerListener */ + public interface Listener { + /** + * Callback where shouldHide=true if listeners should hide their views that may overlap + * a showing dialog. + */ + void shouldHideAffordances(boolean shouldHide); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 36e56f967424..ebf5a6da5d1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -1161,7 +1161,7 @@ public class UserSwitcherController implements Dumpable { ? com.android.settingslib.R.string.guest_reset_guest_dialog_title : R.string.guest_exit_guest_dialog_title); setMessage(context.getString(R.string.guest_exit_guest_dialog_message)); - setButton(DialogInterface.BUTTON_NEGATIVE, + setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(mGuestUserAutoCreated @@ -1180,7 +1180,7 @@ public class UserSwitcherController implements Dumpable { if (mFalsingManager.isFalseTap(penalty)) { return; } - if (which == BUTTON_NEGATIVE) { + if (which == BUTTON_NEUTRAL) { cancel(); } else { mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE); @@ -1198,7 +1198,7 @@ public class UserSwitcherController implements Dumpable { super(context); setTitle(R.string.user_add_user_title); setMessage(context.getString(R.string.user_add_user_message_short)); - setButton(DialogInterface.BUTTON_NEGATIVE, + setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this); @@ -1212,7 +1212,7 @@ public class UserSwitcherController implements Dumpable { if (mFalsingManager.isFalseTap(penalty)) { return; } - if (which == BUTTON_NEGATIVE) { + if (which == BUTTON_NEUTRAL) { cancel(); } else { mDialogLaunchAnimator.dismissStack(this); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index d819fa2adc38..1fe3d4417730 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -46,7 +46,7 @@ import org.mockito.junit.MockitoJUnit @RunWithLooper class ActivityLaunchAnimatorTest : SysuiTestCase() { private val launchContainer = LinearLayout(mContext) - private val launchAnimator = LaunchAnimator(mContext, isForTesting = true) + private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) @Mock lateinit var callback: ActivityLaunchAnimator.Callback @Spy private val controller = TestLaunchAnimatorController(launchContainer) @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt index f9ad740f86df..b951345a145b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -33,7 +33,7 @@ import org.mockito.junit.MockitoJUnit @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class DialogLaunchAnimatorTest : SysuiTestCase() { - private val launchAnimator = LaunchAnimator(context, isForTesting = true) + private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) private lateinit var dialogLaunchAnimator: DialogLaunchAnimator private val attachedViews = mutableSetOf<View>() @@ -42,7 +42,8 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { @Before fun setUp() { - dialogLaunchAnimator = DialogLaunchAnimator(context, launchAnimator, dreamManager) + dialogLaunchAnimator = DialogLaunchAnimator( + dreamManager, launchAnimator, isForTesting = true) } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt new file mode 100644 index 000000000000..dadf94e2a9dd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt @@ -0,0 +1,23 @@ +package com.android.systemui.animation + +/** + * A [LaunchAnimator.Timings] to be used in tests. + * + * Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions + * when computing the progress of a sub-animation (the contents fade in/out). + */ +val TEST_TIMINGS = LaunchAnimator.Timings( + totalDuration = 0L, + contentBeforeFadeOutDelay = 1L, + contentBeforeFadeOutDuration = 1L, + contentAfterFadeInDelay = 1L, + contentAfterFadeInDuration = 1L +) + +/** A [LaunchAnimator.Interpolators] to be used in tests. */ +val TEST_INTERPOLATORS = LaunchAnimator.Interpolators( + positionInterpolator = Interpolators.STANDARD, + positionXInterpolator = Interpolators.STANDARD, + contentBeforeFadeOutInterpolator = Interpolators.STANDARD, + contentAfterFadeInInterpolator = Interpolators.STANDARD +)
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 9e42ff37ea69..ac221a9f7891 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -65,6 +65,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -170,6 +171,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private TypedArray mBrightnessValues; @Mock private TypedArray mBrightnessBacklight; + @Mock + private SystemUIDialogManager mSystemUIDialogManager; // Capture listeners so that they can be used to send events @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor; @@ -179,8 +182,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor; private ScreenLifecycle.Observer mScreenObserver; - @Captor private ArgumentCaptor<UdfpsAnimationViewController> mAnimViewControllerCaptor; - @Before public void setUp() { setUpResources(); @@ -238,7 +239,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mHandler, mConfigurationController, mSystemClock, - mUnlockedScreenOffAnimationController); + mUnlockedScreenOffAnimationController, + mSystemUIDialogManager); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); @@ -642,12 +644,12 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); moveEvent.recycle(); - // THEN low-tick haptic is played + // THEN click haptic is played verify(mVibrator).vibrate( anyInt(), anyString(), any(), - eq("udfps-onStart-tick"), + eq("udfps-onStart-click"), eq(UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES)); // THEN make sure vibration attributes has so that it always will play the haptic, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index 1cf21ac40e31..0ae3c39e659b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -42,6 +42,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; @@ -92,6 +93,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @Mock + private SystemUIDialogManager mDialogManager; + @Mock private UdfpsController mUdfpsController; private FakeSystemClock mSystemClock = new FakeSystemClock(); @@ -130,6 +133,7 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mSystemClock, mKeyguardStateController, mUnlockedScreenOffAnimationController, + mDialogManager, mUdfpsController); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt new file mode 100644 index 000000000000..87b9172dcefc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 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.controls.ui + +import android.app.PendingIntent +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.wm.shell.TaskView +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class DetailDialogTest : SysuiTestCase() { + + @Mock + private lateinit var taskView: TaskView + @Mock + private lateinit var controlViewHolder: ControlViewHolder + @Mock + private lateinit var pendingIntent: PendingIntent + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun testPendingIntentIsUnModified() { + // GIVEN the dialog is created with a PendingIntent + val dialog = createDialog(pendingIntent) + + // WHEN the TaskView is initialized + dialog.stateCallback.onInitialized() + + // THEN the PendingIntent used to call startActivity is unmodified by systemui + verify(taskView).startActivity(eq(pendingIntent), any(), any(), any()) + } + + private fun createDialog(pendingIntent: PendingIntent): DetailDialog { + return DetailDialog( + mContext, + taskView, + pendingIntent, + controlViewHolder + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index bf5522c50a78..e3a7e3b43b77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -62,6 +62,7 @@ import com.android.systemui.plugins.GlobalActions; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -117,6 +118,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private StatusBar mStatusBar; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; + @Mock private SystemUIDialogManager mDialogManager; private TestableLooper mTestableLooper; @@ -162,7 +164,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mPackageManager, Optional.of(mStatusBar), mKeyguardUpdateMonitor, - mDialogLaunchAnimator); + mDialogLaunchAnimator, + mDialogManager); mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting(); ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java index e01583e1cb1e..d7c00fbe1e85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -16,15 +16,16 @@ package com.android.systemui.keyguard; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.keyguard.LockIconView.ICON_LOCK; import static com.android.keyguard.LockIconView.ICON_UNLOCK; import static junit.framework.Assert.assertEquals; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -41,7 +42,6 @@ import android.os.Vibrator; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Pair; -import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; @@ -57,6 +57,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthRippleController; +import com.android.systemui.doze.util.BurnInHelperKt; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -66,8 +67,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; -import com.airbnb.lottie.LottieAnimationView; - +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -76,6 +76,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import java.util.ArrayList; import java.util.List; @@ -86,6 +88,8 @@ import java.util.List; public class LockIconViewControllerTest extends SysuiTestCase { private static final String UNLOCKED_LABEL = "unlocked"; + private MockitoSession mStaticMockSession; + private @Mock LockIconView mLockIconView; private @Mock AnimatedStateListDrawable mIconDrawable; private @Mock Context mContext; @@ -102,8 +106,6 @@ public class LockIconViewControllerTest extends SysuiTestCase { private @Mock ConfigurationController mConfigurationController; private @Mock Vibrator mVibrator; private @Mock AuthRippleController mAuthRippleController; - private @Mock LottieAnimationView mAodFp; - private @Mock LayoutInflater mLayoutInflater; private FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock()); private LockIconViewController mLockIconViewController; @@ -133,11 +135,14 @@ public class LockIconViewControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { + mStaticMockSession = mockitoSession() + .mockStatic(BurnInHelperKt.class) + .strictness(Strictness.LENIENT) + .startMocking(); MockitoAnnotations.initMocks(this); when(mLockIconView.getResources()).thenReturn(mResources); when(mLockIconView.getContext()).thenReturn(mContext); - when(mLockIconView.findViewById(R.layout.udfps_aod_lock_icon)).thenReturn(mAodFp); when(mContext.getResources()).thenReturn(mResources); when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager); Rect windowBounds = new Rect(0, 0, 800, 1200); @@ -164,38 +169,13 @@ public class LockIconViewControllerTest extends SysuiTestCase { mDelayableExecutor, mVibrator, mAuthRippleController, - mResources, - mLayoutInflater + mResources ); } - @Test - public void testIgnoreUdfpsWhenNotSupported() { - // GIVEN Udpfs sensor is NOT available - mLockIconViewController.init(); - captureAttachListener(); - - // WHEN the view is attached - mAttachListener.onViewAttachedToWindow(mLockIconView); - - // THEN lottie animation should NOT be inflated - verify(mLayoutInflater, never()).inflate(eq(R.layout.udfps_aod_lock_icon), any()); - } - - @Test - public void testInflateUdfpsWhenSupported() { - // GIVEN Udpfs sensor is available - setupUdfps(); - when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true); - - mLockIconViewController.init(); - captureAttachListener(); - - // WHEN the view is attached - mAttachListener.onViewAttachedToWindow(mLockIconView); - - // THEN lottie animation should be inflated - verify(mLayoutInflater).inflate(eq(R.layout.udfps_aod_lock_icon), any()); + @After + public void tearDown() { + mStaticMockSession.finishMocking(); } @Test @@ -369,6 +349,42 @@ public class LockIconViewControllerTest extends SysuiTestCase { verify(mLockIconView).updateIcon(ICON_LOCK, true); } + @Test + public void testBurnInOffsetsUpdated_onDozeAmountChanged() { + // GIVEN udfps enrolled + setupUdfps(); + when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true); + + // GIVEN burn-in offset = 5 + int burnInOffset = 5; + when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset); + + // GIVEN starting state for the lock icon (keyguard) + setupShowLockIcon(); + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); + captureStatusBarStateListener(); + reset(mLockIconView); + + // WHEN dozing updates + mStatusBarStateListener.onDozingChanged(true /* isDozing */); + mStatusBarStateListener.onDozeAmountChanged(1f, 1f); + + // THEN the view's translation is updated to use the AoD burn-in offsets + verify(mLockIconView).setTranslationY(burnInOffset); + verify(mLockIconView).setTranslationX(burnInOffset); + reset(mLockIconView); + + // WHEN the device is no longer dozing + mStatusBarStateListener.onDozingChanged(false /* isDozing */); + mStatusBarStateListener.onDozeAmountChanged(0f, 0f); + + // THEN the view is updated to NO translation (no burn-in offsets anymore) + verify(mLockIconView).setTranslationY(0); + verify(mLockIconView).setTranslationX(0); + + } private Pair<Integer, PointF> setupUdfps() { when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true); final PointF udfpsLocation = new PointF(50, 75); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 053851ec385d..451291d264b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -43,6 +43,7 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.Before; import org.junit.Test; @@ -65,6 +66,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mock(NotificationEntryManager.class); private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); + private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class); private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl; private MediaOutputController mMediaOutputController; @@ -77,7 +79,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { public void setUp() { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); + mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mMediaOutputController); mMediaOutputBaseDialogImpl.onCreate(new Bundle()); @@ -167,7 +169,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog { MediaOutputBaseDialogImpl(Context context, MediaOutputController mediaOutputController) { - super(context, mediaOutputController); + super(context, mediaOutputController, mDialogManager); mAdapter = mMediaOutputBaseAdapter; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 09ec4ca0e1df..cd26e0d960dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -54,6 +54,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.Before; import org.junit.Test; @@ -93,6 +94,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mock(NotificationEntryManager.class); private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); + private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class); private Context mSpyContext; private MediaOutputController mMediaOutputController; @@ -115,7 +117,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); + mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; MediaDescription.Builder builder = new MediaDescription.Builder(); @@ -159,7 +161,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void start_withoutPackageName_verifyMediaControllerInit() { mMediaOutputController = new MediaOutputController(mSpyContext, null, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); + mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); mMediaOutputController.start(mCb); @@ -180,7 +182,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void stop_withoutPackageName_verifyMediaControllerDeinit() { mMediaOutputController = new MediaOutputController(mSpyContext, null, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); + mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); mMediaOutputController.start(mCb); @@ -451,7 +453,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void getNotificationLargeIcon_withoutPackageName_returnsNull() { mMediaOutputController = new MediaOutputController(mSpyContext, null, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); + mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); assertThat(mMediaOutputController.getNotificationIcon()).isNull(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index 8a3ea562269d..ada8d3592012 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -40,6 +40,7 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.After; import org.junit.Before; @@ -67,6 +68,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { mock(NotificationEntryManager.class); private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); + private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class); private MediaOutputDialog mMediaOutputDialog; private MediaOutputController mMediaOutputController; @@ -76,10 +78,10 @@ public class MediaOutputDialogTest extends SysuiTestCase { public void setUp() { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); + mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputDialog = new MediaOutputDialog(mContext, false, - mMediaOutputController, mUiEventLogger); + mMediaOutputController, mUiEventLogger, mDialogManager); mMediaOutputDialog.show(); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice); @@ -125,7 +127,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { // and verify if the calling times increases. public void onCreate_ShouldLogVisibility() { MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, - mMediaOutputController, mUiEventLogger); + mMediaOutputController, mUiEventLogger, mDialogManager); testDialog.show(); testDialog.dismissDialog(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java index e8cd6c88956d..b114452facc3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java @@ -38,6 +38,7 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.After; import org.junit.Before; @@ -66,6 +67,7 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase { mock(NotificationEntryManager.class); private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); + private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class); private MediaOutputGroupDialog mMediaOutputGroupDialog; private MediaOutputController mMediaOutputController; @@ -75,10 +77,10 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase { public void setUp() { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); + mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false, - mMediaOutputController); + mMediaOutputController, mDialogManager); mMediaOutputGroupDialog.show(); when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mMediaDevices); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index 95e7a98c0a60..47f6e5c420f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -159,7 +159,6 @@ public class InternetDialogControllerTest extends SysuiTestCase { mAccessPoints.add(mWifiEntry1); when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry); when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID}); - when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry); when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt())) .thenReturn(mSystemUIToast); when(mSystemUIToast.getView()).thenReturn(mToastView); @@ -236,10 +235,18 @@ public class InternetDialogControllerTest extends SysuiTestCase { } @Test - public void getSubtitleText_withAirplaneModeOn_returnNull() { + public void getSubtitleText_withApmOnAndWifiOff_returnWifiIsOff() { fakeAirplaneModeEnabled(true); + when(mWifiManager.isWifiEnabled()).thenReturn(false); + + assertThat(mInternetDialogController.getSubtitleText(false)) + .isEqualTo(getResourcesString("wifi_is_off")); - assertThat(mInternetDialogController.getSubtitleText(false)).isNull(); + // if the Wi-Fi disallow config, then don't return Wi-Fi related string. + mInternetDialogController.mCanConfigWifi = false; + + assertThat(mInternetDialogController.getSubtitleText(false)) + .isNotEqualTo(getResourcesString("wifi_is_off")); } @Test @@ -335,6 +342,17 @@ public class InternetDialogControllerTest extends SysuiTestCase { } @Test + public void getSubtitleText_withCarrierNetworkActiveOnly_returnNoOtherAvailable() { + fakeAirplaneModeEnabled(false); + when(mWifiManager.isWifiEnabled()).thenReturn(true); + mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); + when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true); + + assertThat(mInternetDialogController.getSubtitleText(false)) + .isEqualTo(getResourcesString("non_carrier_network_unavailable")); + } + + @Test public void getWifiDetailsSettingsIntent_withNoKey_returnNull() { assertThat(mInternetDialogController.getWifiDetailsSettingsIntent(null)).isNull(); } @@ -400,7 +418,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); - verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any()); + verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any(), anyBoolean()); } @Test @@ -409,8 +427,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); - verify(mInternetDialogCallback) - .onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(null /* wifiEntries */, + null /* connectedEntry */, false /* hasMoreEntry */); } @Test @@ -423,7 +441,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.clear(); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); } @Test @@ -437,8 +456,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, false /* hasMoreEntry */); } @Test @@ -453,7 +472,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); } @Test @@ -470,7 +490,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); mWifiEntries.add(mWifiEntry2); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); } @Test @@ -489,7 +510,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.add(mWifiEntry1); mWifiEntries.add(mWifiEntry2); mWifiEntries.add(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); @@ -498,7 +520,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + true /* hasMoreEntry */); } @Test @@ -518,7 +541,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.add(mWifiEntry1); mWifiEntries.add(mWifiEntry2); mWifiEntries.add(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + true /* hasMoreEntry */); // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); @@ -527,7 +551,38 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + true /* hasMoreEntry */); + } + + @Test + public void onAccessPointsChanged_oneCarrierWifiAndFourOthers_callbackCutMore() { + reset(mInternetDialogCallback); + fakeAirplaneModeEnabled(true); + when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true); + mAccessPoints.clear(); + mAccessPoints.add(mWifiEntry1); + mAccessPoints.add(mWifiEntry2); + mAccessPoints.add(mWifiEntry3); + mAccessPoints.add(mWifiEntry4); + + mInternetDialogController.onAccessPointsChanged(mAccessPoints); + + mWifiEntries.clear(); + mWifiEntries.add(mWifiEntry1); + mWifiEntries.add(mWifiEntry2); + mWifiEntries.add(mWifiEntry3); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); + + // Turn off airplane mode to has carrier WiFi, then Wi-Fi entries will keep the same. + reset(mInternetDialogCallback); + fakeAirplaneModeEnabled(false); + + mInternetDialogController.onAccessPointsChanged(mAccessPoints); + + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); } @Test @@ -547,8 +602,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.add(mWifiEntry2); mWifiEntries.add(mWifiEntry3); mWifiEntries.add(mWifiEntry4); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, false /* hasMoreEntry */); // If the Ethernet exists, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); @@ -557,8 +612,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry4); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); @@ -567,8 +622,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry3); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); } @Test @@ -584,8 +639,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, false /* hasMoreEntry */); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index 0cf063f5db39..89537884903a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -140,7 +140,7 @@ public class InternetDialogTest extends SysuiTestCase { mInternetDialog.updateDialog(true); - assertThat(mSubTitle.getVisibility()).isEqualTo(View.GONE); + assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE); } @Test @@ -316,6 +316,20 @@ public class InternetDialogTest extends SysuiTestCase { } @Test + public void updateDialog_wifiOnAndOneWifiEntry_showWifiListAndSeeAllArea() { + // The precondition WiFi ON is already in setUp() + mInternetDialog.mConnectedWifiEntry = null; + mInternetDialog.mWifiEntriesCount = 1; + + mInternetDialog.updateDialog(false); + + assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE); + // Show a blank block to fix the dialog height even if there is no WiFi list + assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE); + } + + @Test public void updateDialog_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() { // The preconditions WiFi ON and WiFi entries are already in setUp() mInternetDialog.mWifiEntriesCount = 0; @@ -325,13 +339,15 @@ public class InternetDialogTest extends SysuiTestCase { assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE); // Show a blank block to fix the dialog height even if there is no WiFi list assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE); } @Test - public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() { + public void updateDialog_wifiOnAndHasMaxWifiList_showWifiListAndSeeAll() { // The preconditions WiFi ON and WiFi entries are already in setUp() mInternetDialog.mConnectedWifiEntry = null; + mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT; + mInternetDialog.mHasMoreEntry = true; mInternetDialog.updateDialog(false); @@ -343,6 +359,8 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndHasBothWifiEntry_showBothWifiEntryAndSeeAll() { // The preconditions WiFi ON and WiFi entries are already in setUp() + mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT - 1; + mInternetDialog.mHasMoreEntry = true; mInternetDialog.updateDialog(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 793851160dc2..89435ae164b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -6,6 +6,7 @@ import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.util.DisplayMetrics import com.android.systemui.ExpandHelper +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.media.MediaHierarchyManager @@ -81,6 +82,8 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { mDependency, TestableLooper.get(this)) row = helper.createRow() + context.getOrCreateTestableResources() + .addOverride(R.bool.config_use_split_notification_shade, false) transitionController = LockscreenShadeTransitionController( statusBarStateController = statusbarStateController, lockscreenGestureLogger = lockscreenGestureLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java index a39971d27303..9f152e1e2fe5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java @@ -269,6 +269,21 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } @Test + public void testDisableWiFiWithVcnWithUnderlyingWifi() { + String testSsid = "Test VCN SSID"; + setWifiEnabled(true); + verifyLastWifiIcon(false, WifiIcons.WIFI_NO_NETWORK); + + mNetworkController.setNoNetworksAvailable(false); + setWifiStateForVcn(true, testSsid); + setWifiLevelForVcn(1); + verifyLastMobileDataIndicatorsForVcn(true, 1, TelephonyIcons.ICON_CWF, false); + + setWifiEnabled(false); + verifyLastMobileDataIndicatorsForVcn(false, 1, 0, false); + } + + @Test public void testCallStrengh() { if (true) return; String testSsid = "Test SSID"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 5b60c9e07342..ea681435132e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -4,6 +4,7 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.EmptyShadeView import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider @@ -55,4 +56,24 @@ class StackScrollAlgorithmTest : SysuiTestCase() { // top margin presence should decrease heads up translation up to minHeadsUpTranslation assertThat(expandableViewState.yTranslation).isEqualTo(minHeadsUpTranslation) } -}
\ No newline at end of file + + @Test + fun resetViewStates_childIsEmptyShadeView_viewIsCenteredVertically() { + stackScrollAlgorithm.initView(context) + val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply { + layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100) + } + hostView.removeAllViews() + hostView.addView(emptyShadeView) + ambientState.layoutMaxHeight = 1280 + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + val closeHandleUnderlapHeight = + context.resources.getDimensionPixelSize(R.dimen.close_handle_underlap) + val fullHeight = + ambientState.layoutMaxHeight + closeHandleUnderlapHeight - ambientState.stackY + val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f + assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 07debe68e224..c3349f1d70f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -381,16 +381,15 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test - public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() { + public void onUdfpsConsecutivelyFailedTwoTimes_showBouncer() { // GIVEN UDFPS is supported when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true); - // WHEN udfps fails twice - then don't show the bouncer - mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + // WHEN udfps fails once - then don't show the bouncer mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean()); - // WHEN udfps fails the third time + // WHEN udfps fails the second time mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); // THEN show the bouncer diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index a1d9a7b50d81..be1720d07d11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -18,7 +18,9 @@ package com.android.systemui.unfold.updates import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.DeviceStateManager.FoldStateListener +import android.os.Handler import android.testing.AndroidTestingRunner +import androidx.core.util.Consumer import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.updates.hinge.HingeAngleProvider @@ -31,9 +33,12 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import java.lang.Exception @RunWith(AndroidTestingRunner::class) @SmallTest @@ -48,16 +53,28 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Mock private lateinit var deviceStateManager: DeviceStateManager - private lateinit var foldStateProvider: FoldStateProvider + @Mock + private lateinit var handler: Handler + + @Captor + private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener> + + @Captor + private lateinit var screenOnListenerCaptor: ArgumentCaptor<ScreenListener> + + @Captor + private lateinit var hingeAngleCaptor: ArgumentCaptor<Consumer<Float>> + + private lateinit var foldStateProvider: DeviceFoldStateProvider private val foldUpdates: MutableList<Int> = arrayListOf() private val hingeAngleUpdates: MutableList<Float> = arrayListOf() - private val foldStateListenerCaptor = ArgumentCaptor.forClass(FoldStateListener::class.java) private var foldedDeviceState: Int = 0 private var unfoldedDeviceState: Int = 0 - private val screenOnListenerCaptor = ArgumentCaptor.forClass(ScreenListener::class.java) + private var scheduledRunnable: Runnable? = null + private var scheduledRunnableDelay: Long? = null @Before fun setUp() { @@ -75,7 +92,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { hingeAngleProvider, screenStatusProvider, deviceStateManager, - context.mainExecutor + context.mainExecutor, + handler ) foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener { @@ -91,6 +109,22 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture()) verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture()) + verify(hingeAngleProvider).addCallback(hingeAngleCaptor.capture()) + + whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock -> + scheduledRunnable = invocationOnMock.getArgument<Runnable>(0) + scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1) + null + } + + whenever(handler.removeCallbacks(any<Runnable>())).then { invocationOnMock -> + val removedRunnable = invocationOnMock.getArgument<Runnable>(0) + if (removedRunnable == scheduledRunnable) { + scheduledRunnableDelay = null + scheduledRunnable = null + } + null + } } @Test @@ -167,6 +201,86 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { assertThat(foldUpdates).isEmpty() } + @Test + fun startClosingEvent_afterTimeout_abortEmitted() { + sendHingeAngleEvent(90) + sendHingeAngleEvent(80) + + simulateTimeout(ABORT_CLOSING_MILLIS) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED) + } + + @Test + fun startClosingEvent_beforeTimeout_abortNotEmitted() { + sendHingeAngleEvent(90) + sendHingeAngleEvent(80) + + simulateTimeout(ABORT_CLOSING_MILLIS - 1) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) + } + + @Test + fun startClosingEvent_eventBeforeTimeout_oneEventEmitted() { + sendHingeAngleEvent(180) + sendHingeAngleEvent(90) + + simulateTimeout(ABORT_CLOSING_MILLIS - 1) + sendHingeAngleEvent(80) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) + } + + @Test + fun startClosingEvent_timeoutAfterTimeoutRescheduled_abortEmitted() { + sendHingeAngleEvent(180) + sendHingeAngleEvent(90) + + simulateTimeout(ABORT_CLOSING_MILLIS - 1) // The timeout should not trigger here. + sendHingeAngleEvent(80) + simulateTimeout(ABORT_CLOSING_MILLIS) // The timeout should trigger here. + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED) + } + + @Test + fun startClosingEvent_shortTimeBetween_emitsOnlyOneEvents() { + sendHingeAngleEvent(180) + + sendHingeAngleEvent(90) + sendHingeAngleEvent(80) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) + } + + @Test + fun startClosingEvent_whileClosing_emittedDespiteInitialAngle() { + val maxAngle = 180 - FULLY_OPEN_THRESHOLD_DEGREES.toInt() + for (i in 1..maxAngle) { + foldUpdates.clear() + + simulateFolding(startAngle = i) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) + simulateTimeout() // Timeout to set the state to aborted. + } + } + + private fun simulateTimeout(waitTime: Long = ABORT_CLOSING_MILLIS) { + val runnableDelay = scheduledRunnableDelay ?: throw Exception("No runnable scheduled.") + if (waitTime >= runnableDelay) { + scheduledRunnable?.run() + scheduledRunnable = null + scheduledRunnableDelay = null + } + } + + private fun simulateFolding(startAngle: Int) { + sendHingeAngleEvent(startAngle) + sendHingeAngleEvent(startAngle - 1) + } + private fun setFoldState(folded: Boolean) { val state = if (folded) foldedDeviceState else unfoldedDeviceState foldStateListenerCaptor.value.onStateChanged(state) @@ -175,4 +289,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { private fun fireScreenOnEvent() { screenOnListenerCaptor.value.onScreenTurnedOn() } + + private fun sendHingeAngleEvent(angle: Int) { + hingeAngleCaptor.value.accept(angle.toFloat()) + } } diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java index eaf269415fdc..6744ea8e26a5 100644 --- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java +++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java @@ -288,8 +288,6 @@ public class SystemActionPerformer { showGlobalActions(); return true; } - case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: - return toggleSplitScreen(); case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: return lockScreen(); case AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT: @@ -369,21 +367,6 @@ public class SystemActionPerformer { mWindowManagerService.showGlobalActions(); } - private boolean toggleSplitScreen() { - final long token = Binder.clearCallingIdentity(); - try { - StatusBarManagerInternal statusBarService = LocalServices.getService( - StatusBarManagerInternal.class); - if (statusBarService == null) { - return false; - } - statusBarService.toggleSplitScreen(); - } finally { - Binder.restoreCallingIdentity(token); - } - return true; - } - private boolean lockScreen() { mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0); diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 9ee0159e903a..0b95fefec103 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -4068,14 +4068,20 @@ public class UserBackupManagerService { } int operationType; + TransportClient transportClient = null; try { - operationType = getOperationTypeFromTransport( - mTransportManager.getTransportClientOrThrow(transport, /* caller */ - "BMS.beginRestoreSession")); + transportClient = mTransportManager.getTransportClientOrThrow( + transport, /* caller */"BMS.beginRestoreSession"); + operationType = getOperationTypeFromTransport(transportClient); } catch (TransportNotAvailableException | TransportNotRegisteredException | RemoteException e) { Slog.w(TAG, "Failed to get operation type from transport: " + e); return null; + } finally { + if (transportClient != null) { + mTransportManager.disposeOfTransportClient(transportClient, + /* caller */"BMS.beginRestoreSession"); + } } synchronized (this) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 53b1608841b4..ec15af335c78 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4184,7 +4184,7 @@ public class ActivityManagerService extends IActivityManager.Stub didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId, ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit, - evenPersistent, true /* setRemoved */, + evenPersistent, true /* setRemoved */, uninstalling, packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED : ApplicationExitInfo.REASON_USER_REQUESTED, ApplicationExitInfo.SUBREASON_UNKNOWN, @@ -7220,6 +7220,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */, true /* callerWillRestart */, true /* doit */, true /* evenPersistent */, false /* setRemoved */, + false /* uninstalling */, ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_KILL_UID, reason != null ? reason : "kill uid"); @@ -7241,6 +7242,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */, true /* callerWillRestart */, true /* doit */, true /* evenPersistent */, false /* setRemoved */, + false /* uninstalling */, ApplicationExitInfo.REASON_PERMISSION_CHANGE, ApplicationExitInfo.SUBREASON_UNKNOWN, reason != null ? reason : "kill uid"); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index ff480d1f614c..7673123c6476 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -1633,7 +1633,7 @@ public class OomAdjuster { int schedGroup; int procState; int cachedAdjSeq; - int capability = 0; + int capability = cycleReEval ? app.mState.getCurCapability() : 0; boolean foregroundActivities = false; boolean hasVisibleActivities = false; @@ -2018,10 +2018,6 @@ public class OomAdjuster { } if ((cr.flags & Context.BIND_WAIVE_PRIORITY) == 0) { - if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { - continue; - } - if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { capability |= cstate.getCurCapability(); } @@ -2042,6 +2038,10 @@ public class OomAdjuster { } } + if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { + continue; + } + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { // If the other app is cached for any reason, for purposes here // we are going to consider it empty. The specific cached state diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index b77270f5963b..1e66ed42ff96 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -374,6 +374,16 @@ public final class ProcessList { private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id. /** + * Native heap allocations in AppZygote process and its descendants will now have a + * non-zero tag in the most significant byte. + * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged + * Pointers</a> + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S) + private static final long NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE = 207557677; + + /** * Enable asynchronous (ASYNC) memory tag checking in this process. This * flag will only have an effect on hardware supporting the ARM Memory * Tagging Extension (MTE). @@ -1738,6 +1748,16 @@ public final class ProcessList { return level; } + private int decideTaggingLevelForAppZygote(ProcessRecord app) { + int level = decideTaggingLevel(app); + // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature. + if (!mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE, app.info) + && level == Zygote.MEMORY_TAG_LEVEL_TBI) { + level = Zygote.MEMORY_TAG_LEVEL_NONE; + } + return level; + } + private int decideGwpAsanLevel(ProcessRecord app) { // Look at the process attribute first. if (app.processInfo != null @@ -2238,7 +2258,8 @@ public final class ProcessList { // not the calling one. appInfo.packageName = app.getHostingRecord().getDefiningPackageName(); appInfo.uid = uid; - appZygote = new AppZygote(appInfo, uid, firstUid, lastUid); + int runtimeFlags = decideTaggingLevelForAppZygote(app); + appZygote = new AppZygote(appInfo, uid, firstUid, lastUid, runtimeFlags); mAppZygotes.put(app.info.processName, uid, appZygote); zygoteProcessList = new ArrayList<ProcessRecord>(); mAppZygoteProcesses.put(appZygote, zygoteProcessList); @@ -2750,8 +2771,8 @@ public final class ProcessList { int reasonCode, int subReason, String reason) { return killPackageProcessesLSP(packageName, appId, userId, minOomAdj, false /* callerWillRestart */, true /* allowRestart */, true /* doit */, - false /* evenPersistent */, false /* setRemoved */, reasonCode, - subReason, reason); + false /* evenPersistent */, false /* setRemoved */, false /* uninstalling */, + reasonCode, subReason, reason); } @GuardedBy("mService") @@ -2784,9 +2805,10 @@ public final class ProcessList { @GuardedBy({"mService", "mProcLock"}) boolean killPackageProcessesLSP(String packageName, int appId, int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart, - boolean doit, boolean evenPersistent, boolean setRemoved, int reasonCode, - int subReason, String reason) { - ArrayList<ProcessRecord> procs = new ArrayList<>(); + boolean doit, boolean evenPersistent, boolean setRemoved, boolean uninstalling, + int reasonCode, int subReason, String reason) { + final PackageManagerInternal pm = mService.getPackageManagerInternal(); + final ArrayList<Pair<ProcessRecord, Boolean>> procs = new ArrayList<>(); // Remove all processes this package may have touched: all with the // same UID (except for the system or root user), and all whose name @@ -2803,7 +2825,18 @@ public final class ProcessList { } if (app.isRemoved()) { if (doit) { - procs.add(app); + boolean shouldAllowRestart = false; + if (!uninstalling && packageName != null) { + // This package has a dependency on the given package being stopped, + // while it's not being frozen nor uninstalled, allow to restart it. + shouldAllowRestart = !app.getPkgList().containsKey(packageName) + && app.getPkgDeps() != null + && app.getPkgDeps().contains(packageName) + && app.info != null + && !pm.isPackageFrozen(app.info.packageName, app.uid, + app.userId); + } + procs.add(new Pair<>(app, shouldAllowRestart)); } continue; } @@ -2818,6 +2851,8 @@ public final class ProcessList { continue; } + boolean shouldAllowRestart = false; + // If no package is specified, we call all processes under the // give user id. if (packageName == null) { @@ -2839,9 +2874,16 @@ public final class ProcessList { if (userId != UserHandle.USER_ALL && app.userId != userId) { continue; } - if (!app.getPkgList().containsKey(packageName) && !isDep) { + final boolean isInPkgList = app.getPkgList().containsKey(packageName); + if (!isInPkgList && !isDep) { continue; } + if (!isInPkgList && isDep && !uninstalling && app.info != null + && !pm.isPackageFrozen(app.info.packageName, app.uid, app.userId)) { + // This package has a dependency on the given package being stopped, + // while it's not being frozen nor uninstalled, allow to restart it. + shouldAllowRestart = true; + } } // Process has passed all conditions, kill it! @@ -2851,14 +2893,15 @@ public final class ProcessList { if (setRemoved) { app.setRemoved(true); } - procs.add(app); + procs.add(new Pair<>(app, shouldAllowRestart)); } } int N = procs.size(); for (int i=0; i<N; i++) { - removeProcessLocked(procs.get(i), callerWillRestart, allowRestart, - reasonCode, subReason, reason); + final Pair<ProcessRecord, Boolean> proc = procs.get(i); + removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second, + reasonCode, subReason, reason); } killAppZygotesLocked(packageName, appId, userId, false /* force */); mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END); diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 5ecdfe49cdf7..f053e942fe6b 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -328,7 +328,7 @@ import java.util.concurrent.atomic.AtomicBoolean; } boolean isBtScoRequested = isBluetoothScoRequested(); - if (isBtScoRequested && !wasBtScoRequested) { + if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) { if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) { Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: " + pid); diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index f42870b4b734..758cf7a7d430 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1036,7 +1036,8 @@ public class BiometricService extends SystemService { promptInfo.setAuthenticators(authenticators); return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, - userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */); + userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */, + getContext()); } /** @@ -1375,7 +1376,8 @@ public class BiometricService extends SystemService { try { final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo, - opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists()); + opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(), + getContext()); final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus(); @@ -1383,8 +1385,11 @@ public class BiometricService extends SystemService { + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo + " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: " + promptInfo.isIgnoreEnrollmentState()); - - if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) { + // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can + // be shown for this case. + if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS + || preAuthStatus.second + == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) { // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but // CREDENTIAL is requested and available, set the bundle to only request // CREDENTIAL. diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index a5a3542f49c7..05c3f68f355b 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -26,6 +26,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; +import android.content.Context; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.PromptInfo; @@ -59,6 +61,7 @@ class PreAuthInfo { static final int CREDENTIAL_NOT_ENROLLED = 9; static final int BIOMETRIC_LOCKOUT_TIMED = 10; static final int BIOMETRIC_LOCKOUT_PERMANENT = 11; + static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12; @IntDef({AUTHENTICATOR_OK, BIOMETRIC_NO_HARDWARE, BIOMETRIC_DISABLED_BY_DEVICE_POLICY, @@ -69,7 +72,8 @@ class PreAuthInfo { BIOMETRIC_NOT_ENABLED_FOR_APPS, CREDENTIAL_NOT_ENROLLED, BIOMETRIC_LOCKOUT_TIMED, - BIOMETRIC_LOCKOUT_PERMANENT}) + BIOMETRIC_LOCKOUT_PERMANENT, + BIOMETRIC_SENSOR_PRIVACY_ENABLED}) @Retention(RetentionPolicy.SOURCE) @interface AuthenticatorStatus {} @@ -84,13 +88,15 @@ class PreAuthInfo { final boolean credentialAvailable; final boolean confirmationRequested; final boolean ignoreEnrollmentState; + final int userId; + final Context context; static PreAuthInfo create(ITrustManager trustManager, DevicePolicyManager devicePolicyManager, BiometricService.SettingObserver settingObserver, List<BiometricSensor> sensors, int userId, PromptInfo promptInfo, String opPackageName, - boolean checkDevicePolicyManager) + boolean checkDevicePolicyManager, Context context) throws RemoteException { final boolean confirmationRequested = promptInfo.isConfirmationRequested(); @@ -116,14 +122,22 @@ class PreAuthInfo { devicePolicyManager, settingObserver, sensor, userId, opPackageName, checkDevicePolicyManager, requestedStrength, promptInfo.getAllowedSensorIds(), - promptInfo.isIgnoreEnrollmentState()); + promptInfo.isIgnoreEnrollmentState(), + context); Slog.d(TAG, "Package: " + opPackageName + " Sensor ID: " + sensor.id + " Modality: " + sensor.modality + " Status: " + status); - if (status == AUTHENTICATOR_OK) { + // A sensor with privacy enabled will still be eligible to + // authenticate with biometric prompt. This is so the framework can display + // a sensor privacy error message to users after briefly showing the + // Biometric Prompt. + // + // Note: if only a certain sensor is required and the privacy is enabled, + // canAuthenticate() will return false. + if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) { eligibleSensors.add(sensor); } else { ineligibleSensors.add(new Pair<>(sensor, status)); @@ -133,7 +147,7 @@ class PreAuthInfo { return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, - promptInfo.isIgnoreEnrollmentState()); + promptInfo.isIgnoreEnrollmentState(), userId, context); } /** @@ -149,7 +163,7 @@ class PreAuthInfo { BiometricSensor sensor, int userId, String opPackageName, boolean checkDevicePolicyManager, int requestedStrength, @NonNull List<Integer> requestedSensorIds, - boolean ignoreEnrollmentState) { + boolean ignoreEnrollmentState, Context context) { if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) { return BIOMETRIC_NO_HARDWARE; @@ -175,6 +189,16 @@ class PreAuthInfo { && !ignoreEnrollmentState) { return BIOMETRIC_NOT_ENROLLED; } + final SensorPrivacyManager sensorPrivacyManager = context + .getSystemService(SensorPrivacyManager.class); + + if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) { + if (sensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) { + return BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } + } + final @LockoutTracker.LockoutMode int lockoutMode = sensor.impl.getLockoutModeForUser(userId); @@ -243,7 +267,8 @@ class PreAuthInfo { private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List<BiometricSensor> eligibleSensors, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, - boolean confirmationRequested, boolean ignoreEnrollmentState) { + boolean confirmationRequested, boolean ignoreEnrollmentState, int userId, + Context context) { mBiometricRequested = biometricRequested; mBiometricStrengthRequested = biometricStrengthRequested; this.credentialRequested = credentialRequested; @@ -253,6 +278,8 @@ class PreAuthInfo { this.credentialAvailable = credentialAvailable; this.confirmationRequested = confirmationRequested; this.ignoreEnrollmentState = ignoreEnrollmentState; + this.userId = userId; + this.context = context; } private Pair<BiometricSensor, Integer> calculateErrorByPriority() { @@ -280,15 +307,35 @@ class PreAuthInfo { private Pair<Integer, Integer> getInternalStatus() { @AuthenticatorStatus final int status; @BiometricAuthenticator.Modality int modality = TYPE_NONE; + + final SensorPrivacyManager sensorPrivacyManager = context + .getSystemService(SensorPrivacyManager.class); + + boolean cameraPrivacyEnabled = false; + if (sensorPrivacyManager != null) { + cameraPrivacyEnabled = sensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId); + } + if (mBiometricRequested && credentialRequested) { if (credentialAvailable || !eligibleSensors.isEmpty()) { - status = AUTHENTICATOR_OK; - if (credentialAvailable) { - modality |= TYPE_CREDENTIAL; - } for (BiometricSensor sensor : eligibleSensors) { modality |= sensor.modality; } + + if (credentialAvailable) { + modality |= TYPE_CREDENTIAL; + status = AUTHENTICATOR_OK; + } else if (modality == TYPE_FACE && cameraPrivacyEnabled) { + // If the only modality requested is face, credential is unavailable, + // and the face sensor privacy is enabled then return + // BIOMETRIC_SENSOR_PRIVACY_ENABLED. + // + // Note: This sensor will still be eligible for calls to authenticate. + status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } else { + status = AUTHENTICATOR_OK; + } } else { // Pick the first sensor error if it exists if (!ineligibleSensors.isEmpty()) { @@ -302,10 +349,18 @@ class PreAuthInfo { } } else if (mBiometricRequested) { if (!eligibleSensors.isEmpty()) { - status = AUTHENTICATOR_OK; - for (BiometricSensor sensor : eligibleSensors) { - modality |= sensor.modality; - } + for (BiometricSensor sensor : eligibleSensors) { + modality |= sensor.modality; + } + if (modality == TYPE_FACE && cameraPrivacyEnabled) { + // If the only modality requested is face and the privacy is enabled + // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED. + // + // Note: This sensor will still be eligible for calls to authenticate. + status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } else { + status = AUTHENTICATOR_OK; + } } else { // Pick the first sensor error if it exists if (!ineligibleSensors.isEmpty()) { @@ -326,9 +381,9 @@ class PreAuthInfo { Slog.e(TAG, "No authenticators requested"); status = BIOMETRIC_NO_HARDWARE; } - Slog.d(TAG, "getCanAuthenticateInternal Modality: " + modality + " AuthenticatorStatus: " + status); + return new Pair<>(modality, status); } @@ -362,6 +417,7 @@ class PreAuthInfo { case CREDENTIAL_NOT_ENROLLED: case BIOMETRIC_LOCKOUT_TIMED: case BIOMETRIC_LOCKOUT_PERMANENT: + case BIOMETRIC_SENSOR_PRIVACY_ENABLED: break; case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 4f7c6b012c23..0e2582c23b86 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -33,6 +33,7 @@ import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_LOCKOUT_TIMED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE; +import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED; import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED; import android.annotation.NonNull; @@ -278,6 +279,9 @@ public class Utils { case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT: biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS; break; + case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED: + biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; + break; default: Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode); biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; @@ -337,7 +341,8 @@ public class Utils { case BIOMETRIC_LOCKOUT_PERMANENT: return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; - + case BIOMETRIC_SENSOR_PRIVACY_ENABLED: + return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED; case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: case BIOMETRIC_HARDWARE_NOT_DETECTED: case BIOMETRIC_NOT_ENABLED_FOR_APPS: 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 7341e744dea3..358263df916b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -503,10 +503,14 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> protected int getShowOverlayReason() { if (isKeyguard()) { return BiometricOverlayConstants.REASON_AUTH_KEYGUARD; - } else if (isSettings()) { - return BiometricOverlayConstants.REASON_AUTH_SETTINGS; } else if (isBiometricPrompt()) { + // BP reason always takes precedent over settings, since callers from within + // settings can always invoke BP. return BiometricOverlayConstants.REASON_AUTH_BP; + } else if (isSettings()) { + // This is pretty much only for FingerprintManager#authenticate usage from + // FingerprintSettings. + return BiometricOverlayConstants.REASON_AUTH_SETTINGS; } else { return BiometricOverlayConstants.REASON_AUTH_OTHER; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 97d791b7e1c9..4131ae127ab2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.NotificationManager; import android.content.Context; import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; @@ -56,6 +57,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @NonNull private final LockoutCache mLockoutCache; @Nullable private final NotificationManager mNotificationManager; @Nullable private ICancellationSignal mCancellationSignal; + @Nullable private SensorPrivacyManager mSensorPrivacyManager; private final int[] mBiometricPromptIgnoreList; private final int[] mBiometricPromptIgnoreListVendor; @@ -81,6 +83,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements mUsageStats = usageStats; mLockoutCache = lockoutCache; mNotificationManager = context.getSystemService(NotificationManager.class); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -108,7 +111,16 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @Override protected void startHalOperation() { try { - mCancellationSignal = getFreshDaemon().authenticate(mOperationId); + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, + getTargetUserId())) { + onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + } else { + mCancellationSignal = getFreshDaemon().authenticate(mOperationId); + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting auth", e); onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java index 2ef0911658b1..2158dfe7bde5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java @@ -19,6 +19,8 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.SensorPrivacyManager; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.face.ISession; @@ -41,6 +43,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det private final boolean mIsStrongBiometric; @Nullable private ICancellationSignal mCancellationSignal; + @Nullable private SensorPrivacyManager mSensorPrivacyManager; public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId, @@ -51,6 +54,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } @Override @@ -73,6 +77,14 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det @Override protected void startHalOperation() { + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) { + onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + return; + } + try { mCancellationSignal = getFreshDaemon().detectInteraction(); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 40f2801541d3..7548d2871a15 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; @@ -55,6 +56,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { private final int[] mKeyguardIgnoreListVendor; private int mLastAcquire; + private SensorPrivacyManager mSensorPrivacyManager; FaceAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @@ -71,6 +73,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { isKeyguardBypassEnabled); setRequestId(requestId); mUsageStats = usageStats; + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -97,6 +100,15 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { @Override protected void startHalOperation() { + + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) { + onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + return; + } + try { getFreshDaemon().authenticate(mOperationId); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 5ce72c2fd080..3120dc58eebd 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -25,7 +25,6 @@ import android.annotation.TestApi; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.compat.CompatChanges; -import android.app.TaskStackListener; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; import android.compat.annotation.Overridable; @@ -35,6 +34,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.hardware.CameraSessionStats; import android.hardware.CameraStreamStats; @@ -84,7 +84,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -309,8 +308,6 @@ public class CameraServiceProxy extends SystemService private final DisplayWindowListener mDisplayWindowListener = new DisplayWindowListener(); - private final TaskStateHandler mTaskStackListener = new TaskStateHandler(); - public static final class TaskInfo { public int frontTaskId; public boolean isResizeable; @@ -320,54 +317,6 @@ public class CameraServiceProxy extends SystemService public int userId; } - private final class TaskStateHandler extends TaskStackListener { - private final Object mMapLock = new Object(); - - // maps the package name to its corresponding current top level task id - @GuardedBy("mMapLock") - private final ArrayMap<String, TaskInfo> mTaskInfoMap = new ArrayMap<>(); - - @Override - public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { - synchronized (mMapLock) { - TaskInfo info = new TaskInfo(); - info.frontTaskId = taskInfo.taskId; - info.isResizeable = - (taskInfo.topActivityInfo.resizeMode != RESIZE_MODE_UNRESIZEABLE); - info.displayId = taskInfo.displayId; - info.userId = taskInfo.userId; - info.isFixedOrientationLandscape = ActivityInfo.isFixedOrientationLandscape( - taskInfo.topActivityInfo.screenOrientation); - info.isFixedOrientationPortrait = ActivityInfo.isFixedOrientationPortrait( - taskInfo.topActivityInfo.screenOrientation); - mTaskInfoMap.put(taskInfo.topActivityInfo.packageName, info); - } - } - - @Override - public void onTaskRemoved(int taskId) { - synchronized (mMapLock) { - for (Map.Entry<String, TaskInfo> entry : mTaskInfoMap.entrySet()){ - if (entry.getValue().frontTaskId == taskId) { - mTaskInfoMap.remove(entry.getKey()); - break; - } - } - } - } - - public @Nullable TaskInfo getFrontTaskInfo(String packageName) { - synchronized (mMapLock) { - if (mTaskInfoMap.containsKey(packageName)) { - return mTaskInfoMap.get(packageName); - } - } - - Log.e(TAG, "Top task with package name: " + packageName + " not found!"); - return null; - } - } - private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -490,18 +439,53 @@ public class CameraServiceProxy extends SystemService private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() { @Override - public int getRotateAndCropOverride(String packageName, int lensFacing) { + public int getRotateAndCropOverride(String packageName, int lensFacing, int userId) { if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " + " camera service UID!"); return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; } + TaskInfo taskInfo = null; + ParceledListSlice<ActivityManager.RecentTaskInfo> recentTasks = null; + + try { + recentTasks = ActivityTaskManager.getService().getRecentTasks(/*maxNum*/1, + /*flags*/ 0, userId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to query recent tasks!"); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + + if ((recentTasks != null) && (!recentTasks.getList().isEmpty())) { + ActivityManager.RecentTaskInfo task = recentTasks.getList().get(0); + if (packageName.equals(task.topActivityInfo.packageName)) { + taskInfo = new TaskInfo(); + taskInfo.frontTaskId = task.taskId; + taskInfo.isResizeable = + (task.topActivityInfo.resizeMode != RESIZE_MODE_UNRESIZEABLE); + taskInfo.displayId = task.displayId; + taskInfo.userId = task.userId; + taskInfo.isFixedOrientationLandscape = + ActivityInfo.isFixedOrientationLandscape( + task.topActivityInfo.screenOrientation); + taskInfo.isFixedOrientationPortrait = + ActivityInfo.isFixedOrientationPortrait( + task.topActivityInfo.screenOrientation); + } else { + Log.e(TAG, "Recent task package name: " + task.topActivityInfo.packageName + + " doesn't match with camera client package name: " + packageName); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + } else { + Log.e(TAG, "Recent task list is empty!"); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + // TODO: Modify the sensor orientation in camera characteristics along with any 3A // regions in capture requests/results to account for thea physical rotation. The // former is somewhat tricky as it assumes that camera clients always check for the // current value by retrieving the camera characteristics from the camera device. - TaskInfo taskInfo = mTaskStackListener.getFrontTaskInfo(packageName); if ((taskInfo != null) && (CompatChanges.isChangeEnabled( OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS, packageName, UserHandle.getUserHandleForUid(taskInfo.userId)))) { @@ -673,12 +657,6 @@ public class CameraServiceProxy extends SystemService CameraStatsJobService.schedule(mContext); try { - ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener); - } catch (RemoteException e) { - Log.e(TAG, "Failed to register task stack listener!"); - } - - try { int[] displayIds = WindowManagerGlobal.getWindowManagerService() .registerDisplayWindowListener(mDisplayWindowListener); for (int i = 0; i < displayIds.length; i++) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7ed897dfdbf9..7455cecee65b 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -266,7 +266,6 @@ import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; @@ -615,12 +614,6 @@ public class NotificationManagerService extends SystemService { private NotificationRecordLogger mNotificationRecordLogger; private InstanceIdSequence mNotificationInstanceIdSequence; private Set<String> mMsgPkgsAllowedAsConvos = new HashSet(); - protected static final String ACTION_ENABLE_NAS = - "android.server.notification.action.ENABLE_NAS"; - protected static final String ACTION_DISABLE_NAS = - "android.server.notification.action.DISABLE_NAS"; - protected static final String ACTION_LEARNMORE_NAS = - "android.server.notification.action.LEARNMORE_NAS"; static class Archive { final SparseArray<Boolean> mEnabled; @@ -755,95 +748,25 @@ public class NotificationManagerService extends SystemService { setDefaultAssistantForUser(userId); } - protected void migrateDefaultNASShowNotificationIfNecessary() { + protected void migrateDefaultNAS() { final List<UserInfo> activeUsers = mUm.getUsers(); for (UserInfo userInfo : activeUsers) { int userId = userInfo.getUserHandle().getIdentifier(); if (isNASMigrationDone(userId) || mUm.isManagedProfile(userId)) { continue; } - if (mAssistants.hasUserSet(userId)) { - ComponentName defaultFromConfig = mAssistants.getDefaultFromConfig(); - List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId); - if (allowedComponents.size() == 0) { - setNASMigrationDone(userId); - mAssistants.clearDefaults(); - continue; - } else if (allowedComponents.contains(defaultFromConfig)) { - setNASMigrationDone(userId); - mAssistants.resetDefaultFromConfig(); - continue; - } - // TODO(b/192450820): re-enable when "user set" isn't over triggering - //User selected different NAS, need onboarding - /*enqueueNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, - createNASUpgradeNotification(userId), userId);*/ - } - } - } - - protected Notification createNASUpgradeNotification(int userId) { - final Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - getContext().getResources().getString(R.string.global_action_settings)); - int title = R.string.nas_upgrade_notification_title; - int content = R.string.nas_upgrade_notification_content; - - Intent onboardingIntent = new Intent(Settings.ACTION_NOTIFICATION_ASSISTANT_SETTINGS); - onboardingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - - Intent enableIntent = new Intent(ACTION_ENABLE_NAS); - enableIntent.putExtra(Intent.EXTRA_USER_ID, userId); - PendingIntent enableNASPendingIntent = PendingIntent.getBroadcast(getContext(), - 0, enableIntent, PendingIntent.FLAG_IMMUTABLE); - - Intent disableIntent = new Intent(ACTION_DISABLE_NAS); - disableIntent.putExtra(Intent.EXTRA_USER_ID, userId); - PendingIntent disableNASPendingIntent = PendingIntent.getBroadcast(getContext(), - 0, disableIntent, PendingIntent.FLAG_IMMUTABLE); - - Intent learnMoreIntent = new Intent(ACTION_LEARNMORE_NAS); - learnMoreIntent.putExtra(Intent.EXTRA_USER_ID, userId); - PendingIntent learnNASPendingIntent = PendingIntent.getBroadcast(getContext(), - 0, learnMoreIntent, PendingIntent.FLAG_IMMUTABLE); - - Notification.Action enableNASAction = new Notification.Action.Builder( - 0, - getContext().getResources().getString( - R.string.nas_upgrade_notification_enable_action), - enableNASPendingIntent).build(); - - Notification.Action disableNASAction = new Notification.Action.Builder( - 0, - getContext().getResources().getString( - R.string.nas_upgrade_notification_disable_action), - disableNASPendingIntent).build(); - - Notification.Action learnMoreNASAction = new Notification.Action.Builder( - 0, - getContext().getResources().getString( - R.string.nas_upgrade_notification_learn_more_action), - learnNASPendingIntent).build(); - - - return new Notification.Builder(getContext(), SystemNotificationChannels.SYSTEM_CHANGES) - .setAutoCancel(false) - .setOngoing(true) - .setTicker(getContext().getResources().getString(title)) - .setSmallIcon(R.drawable.ic_settings_24dp) - .setContentTitle(getContext().getResources().getString(title)) - .setContentText(getContext().getResources().getString(content)) - .setContentIntent(PendingIntent.getActivity(getContext(), 0, onboardingIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)) - .setLocalOnly(true) - .setStyle(new Notification.BigTextStyle()) - .addAction(enableNASAction) - .addAction(disableNASAction) - .addAction(learnMoreNASAction) - .build(); + List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId); + if (allowedComponents.size() == 0) { // user set to none + Slog.d(TAG, "NAS Migration: user set to none, disable new NAS setting"); + setNASMigrationDone(userId); + mAssistants.clearDefaults(); + } else { + Slog.d(TAG, "Reset NAS setting and migrate to new default"); + resetAssistantUserSet(userId); + // migrate to new default and set migration done + mAssistants.resetDefaultAssistantsIfNecessary(); + } + } } @VisibleForTesting @@ -1861,41 +1784,6 @@ public class NotificationManagerService extends SystemService { } }; - private final BroadcastReceiver mNASIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1); - if (ACTION_ENABLE_NAS.equals(action)) { - mAssistants.resetDefaultFromConfig(); - setNotificationAssistantAccessGrantedForUserInternal( - CollectionUtils.firstOrNull(mAssistants.getDefaultComponents()), - userId, true, true); - setNASMigrationDone(userId); - cancelNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId); - } else if (ACTION_DISABLE_NAS.equals(action)) { - //Set default NAS to be null if user selected none during migration - mAssistants.clearDefaults(); - setNotificationAssistantAccessGrantedForUserInternal( - null, userId, true, true); - setNASMigrationDone(userId); - cancelNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId); - } else if (ACTION_LEARNMORE_NAS.equals(action)) { - Intent i = new Intent(getContext(), NASLearnMoreActivity.class); - i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - getContext().sendBroadcastAsUser( - new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.of(userId)); - getContext().startActivity(i); - } - } - }; - private final class SettingsObserver extends ContentObserver { private final Uri NOTIFICATION_BADGING_URI = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING); @@ -2407,12 +2295,6 @@ public class NotificationManagerService extends SystemService { IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED); getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter); - - IntentFilter nasFilter = new IntentFilter(); - nasFilter.addAction(ACTION_ENABLE_NAS); - nasFilter.addAction(ACTION_DISABLE_NAS); - nasFilter.addAction(ACTION_LEARNMORE_NAS); - getContext().registerReceiver(mNASIntentReceiver, nasFilter); } /** @@ -2424,7 +2306,6 @@ public class NotificationManagerService extends SystemService { getContext().unregisterReceiver(mNotificationTimeoutReceiver); getContext().unregisterReceiver(mRestoreReceiver); getContext().unregisterReceiver(mLocaleChangeReceiver); - getContext().unregisterReceiver(mNASIntentReceiver); if (mDeviceConfigChangedListener != null) { DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener); @@ -2691,7 +2572,7 @@ public class NotificationManagerService extends SystemService { mConditionProviders.onBootPhaseAppsCanStart(); mHistoryManager.onBootPhaseAppsCanStart(); registerDeviceConfigChange(); - migrateDefaultNASShowNotificationIfNecessary(); + migrateDefaultNAS(); } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis()); } @@ -5229,10 +5110,6 @@ public class NotificationManagerService extends SystemService { public void setNASMigrationDoneAndResetDefault(int userId, boolean loadFromConfig) { checkCallerIsSystem(); setNASMigrationDone(userId); - cancelNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId); if (loadFromConfig) { mAssistants.resetDefaultFromConfig(); } else { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ba11e9c26dfe..7a70bfe91248 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -24299,24 +24299,24 @@ public class PackageManagerService extends IPackageManager.Stub } enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, true /* checkShell */, "stop package"); - boolean shouldUnhibernate = false; // writer synchronized (mLock) { final PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null && ps.getStopped(userId) && !stopped) { - shouldUnhibernate = true; - } if (!shouldFilterApplicationLocked(ps, callingUid, userId) && mSettings.setPackageStoppedStateLPw(this, packageName, stopped, userId)) { scheduleWritePackageRestrictionsLocked(userId); } } - if (shouldUnhibernate) { + // If this would cause the app to leave force-stop, then also make sure to unhibernate the + // app if needed. + if (!stopped) { mHandler.post(() -> { AppHibernationManagerInternal ah = mInjector.getLocalService(AppHibernationManagerInternal.class); - ah.setHibernatingForUser(packageName, userId, false); - ah.setHibernatingGlobally(packageName, false); + if (ah != null && ah.isHibernatingForUser(packageName, userId)) { + ah.setHibernatingForUser(packageName, userId, false); + ah.setHibernatingGlobally(packageName, false); + } }); } } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 2be29d4a5ab4..dfff76d3a044 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -50,6 +50,10 @@ import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID; import static android.util.MathUtils.constrain; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO; @@ -4387,8 +4391,9 @@ public class StatsPullAtomService extends SystemService { final int userId = userInfo.getUserHandle().getIdentifier(); if (isAccessibilityShortcutUser(mContext, userId)) { - final int software_shortcut_type = Settings.Secure.getIntForUser(resolver, - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId); + final int software_shortcut_type = convertToAccessibilityShortcutType( + Settings.Secure.getIntForUser(resolver, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId)); final String software_shortcut_list = Settings.Secure.getStringForUser(resolver, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId); final int software_shortcut_service_num = countAccessibilityServices( @@ -4509,6 +4514,19 @@ public class StatsPullAtomService extends SystemService { && !TextUtils.isEmpty(software_string); } + private int convertToAccessibilityShortcutType(int shortcutType) { + switch (shortcutType) { + case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; + case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; + case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; + default: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; + } + } + // Thermal event received from vendor thermal management subsystem private static final class ThermalEventListener extends IThermalEventListener.Stub { @Override diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 7dec4e785f5c..51c3b33b0e79 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -77,6 +77,7 @@ import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.Process; import android.os.SystemClock; +import android.provider.Settings; import android.util.ArraySet; import android.util.Slog; @@ -782,8 +783,19 @@ public class VcnGatewayConnection extends StateMachine { // TODO(b/179091925): Move the delayed-message handling to BaseState // If underlying is null, all underlying networks have been lost. Disconnect VCN after a - // timeout. + // timeout (or immediately if in airplane mode, since the device user has indicated that + // the radios should all be turned off). if (underlying == null) { + if (mDeps.isAirplaneModeOn(mVcnContext)) { + sendMessageAndAcquireWakeLock( + EVENT_UNDERLYING_NETWORK_CHANGED, + TOKEN_ALL, + new EventUnderlyingNetworkChangedInfo(null)); + sendDisconnectRequestedAndAcquireWakelock( + DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */); + return; + } + setDisconnectRequestAlarm(); } else { // Received a new Network so any previous alarm is irrelevant - cancel + clear it, @@ -2414,6 +2426,12 @@ public class VcnGatewayConnection extends StateMachine { validationStatusCallback); } + /** Checks if airplane mode is enabled. */ + public boolean isAirplaneModeOn(@NonNull VcnContext vcnContext) { + return Settings.Global.getInt(vcnContext.getContext().getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0; + } + /** Gets the elapsed real time since boot, in millis. */ public long getElapsedRealTime() { return SystemClock.elapsedRealtime(); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 7d5125f2e988..393d101e9830 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -160,6 +160,7 @@ import static com.android.server.wm.ActivityRecordProto.NUM_DRAWN_WINDOWS; import static com.android.server.wm.ActivityRecordProto.NUM_INTERESTING_WINDOWS; import static com.android.server.wm.ActivityRecordProto.PIP_AUTO_ENTER_ENABLED; import static com.android.server.wm.ActivityRecordProto.PROC_ID; +import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS; import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN; import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE; import static com.android.server.wm.ActivityRecordProto.STARTING_DISPLAYED; @@ -261,6 +262,7 @@ import android.content.Intent; import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ConstrainDisplayApisConfig; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -596,6 +598,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private CompatDisplayInsets mCompatDisplayInsets; + private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig; + boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session IVoiceInteractionSession voiceSession; // Voice interaction session for this activity @@ -1162,8 +1166,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (info.configChanges != 0) { pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges)); } - pw.println(prefix + "neverSandboxDisplayApis=" + info.neverSandboxDisplayApis()); - pw.println(prefix + "alwaysSandboxDisplayApis=" + info.alwaysSandboxDisplayApis()); + pw.println(prefix + "neverSandboxDisplayApis=" + info.neverSandboxDisplayApis( + sConstrainDisplayApisConfig)); + pw.println(prefix + "alwaysSandboxDisplayApis=" + info.alwaysSandboxDisplayApis( + sConstrainDisplayApisConfig)); } if (mLastParentBeforePip != null) { pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId); @@ -1769,6 +1775,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A info.windowLayout.windowLayoutAffinity = uid + ":" + info.windowLayout.windowLayoutAffinity; } + // Initialize once, when we know all system services are available. + if (sConstrainDisplayApisConfig == null) { + sConstrainDisplayApisConfig = new ConstrainDisplayApisConfig(); + } stateNotNeeded = (aInfo.flags & FLAG_STATE_NOT_NEEDED) != 0; nonLocalizedLabel = aInfo.nonLocalizedLabel; labelRes = aInfo.labelRes; @@ -6565,6 +6575,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON) { return false; } + // Choose the default behavior for Launcher and SystemUI when the SplashScreen style is + // not specified in the ActivityOptions. + if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME) { + return false; + } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) { + return true; + } } if (sourceRecord == null) { sourceRecord = searchCandidateLaunchingActivity(); @@ -6574,14 +6591,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return sourceRecord.mSplashScreenStyleEmpty; } - // If this activity was launched from a system surface, never use an empty splash screen + // If this activity was launched from Launcher or System for first start, never use an + // empty splash screen. // Need to check sourceRecord before in case this activity is launched from service. - if (launchedFromSystemSurface()) { - return false; - } - - // Otherwise use empty. - return true; + return !startActivity || !(mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEM + || mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME); } private int getSplashscreenTheme() { @@ -7461,8 +7475,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A + "should create compatDisplayInsets = %s", getUid(), mTmpBounds, - info.neverSandboxDisplayApis(), - info.alwaysSandboxDisplayApis(), + info.neverSandboxDisplayApis(sConstrainDisplayApisConfig), + info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig), !matchParentBounds(), mCompatDisplayInsets != null, shouldCreateCompatDisplayInsets()); @@ -8023,11 +8037,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return false; } // Never apply sandboxing to an app that should be explicitly excluded from the config. - if (info != null && info.neverSandboxDisplayApis()) { + if (info.neverSandboxDisplayApis(sConstrainDisplayApisConfig)) { return false; } // Always apply sandboxing to an app that should be explicitly included from the config. - if (info != null && info.alwaysSandboxDisplayApis()) { + if (info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig)) { return true; } // Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it @@ -9043,6 +9057,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A proto.write(PIP_AUTO_ENTER_ENABLED, pictureInPictureArgs.isAutoEnterEnabled()); proto.write(IN_SIZE_COMPAT_MODE, inSizeCompatMode()); proto.write(MIN_ASPECT_RATIO, getMinAspectRatio()); + // Only record if max bounds sandboxing is applied, if the caller has the necessary + // permission to access the device configs. + proto.write(PROVIDES_MAX_BOUNDS, providesMaxBounds()); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 2cf23c5e6f7f..436a325559e6 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -72,6 +72,8 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_USER_ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.ANIMATE; +import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW; +import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY; import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME; import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; @@ -1275,7 +1277,7 @@ class ActivityStarter { // This is used to block background activity launch even if the app is still // visible to user after user clicking home button. - final boolean appSwitchAllowed = mService.getBalAppSwitchesAllowed(); + final int appSwitchState = mService.getBalAppSwitchesState(); // don't abort if the callingUid has a visible window or is a persistent system process final int callingUidProcState = mService.mActiveUids.getUidState(callingUid); @@ -1288,7 +1290,9 @@ class ActivityStarter { // Normal apps with visible app window will be allowed to start activity if app switching // is allowed, or apps like live wallpaper with non app visible window will be allowed. - if (((appSwitchAllowed || mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) + final boolean appSwitchAllowedOrFg = + appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY; + if (((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) && callingUidHasAnyVisibleWindow) || isCallingUidPersistentSystemProcess) { if (DEBUG_ACTIVITY_STARTS) { @@ -1398,7 +1402,7 @@ class ActivityStarter { // don't abort if the callerApp or other processes of that uid are allowed in any way if (callerApp != null) { // first check the original calling process - if (callerApp.areBackgroundActivityStartsAllowed(appSwitchAllowed)) { + if (callerApp.areBackgroundActivityStartsAllowed(appSwitchState)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed: callerApp process (pid = " + callerApp.getPid() + ", uid = " + callerAppUid + ") is allowed"); @@ -1412,7 +1416,7 @@ class ActivityStarter { for (int i = uidProcesses.size() - 1; i >= 0; i--) { final WindowProcessController proc = uidProcesses.valueAt(i); if (proc != callerApp - && proc.areBackgroundActivityStartsAllowed(appSwitchAllowed)) { + && proc.areBackgroundActivityStartsAllowed(appSwitchState)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed: process " + proc.getPid() @@ -1426,7 +1430,7 @@ class ActivityStarter { // anything that has fallen through would currently be aborted Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage + "; callingUid: " + callingUid - + "; appSwitchAllowed: " + appSwitchAllowed + + "; appSwitchState: " + appSwitchState + "; isCallingUidForeground: " + isCallingUidForeground + "; callingUidHasAnyVisibleWindow: " + callingUidHasAnyVisibleWindow + "; callingUidProcState: " + DebugUtils.valueToString(ActivityManager.class, diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 60a514e4d612..c8227d953009 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -504,7 +504,27 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { * Whether normal application switches are allowed; a call to {@link #stopAppSwitches() * disables this. */ - private volatile boolean mAppSwitchesAllowed = true; + private volatile int mAppSwitchesState = APP_SWITCH_ALLOW; + + // The duration of resuming foreground app switch from disallow. + private static final long RESUME_FG_APP_SWITCH_MS = 500; + + /** App switch is not allowed. */ + static final int APP_SWITCH_DISALLOW = 0; + + /** App switch is allowed only if the activity launch was requested by a foreground app. */ + static final int APP_SWITCH_FG_ONLY = 1; + + /** App switch is allowed. */ + static final int APP_SWITCH_ALLOW = 2; + + @IntDef({ + APP_SWITCH_DISALLOW, + APP_SWITCH_FG_ONLY, + APP_SWITCH_ALLOW, + }) + @Retention(RetentionPolicy.SOURCE) + @interface AppSwitchState {} /** * Last stop app switches time, apps finished before this time cannot start background activity @@ -1247,7 +1267,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (topFocusedRootTask != null && topFocusedRootTask.getTopResumedActivity() != null && topFocusedRootTask.getTopResumedActivity().info.applicationInfo.uid == Binder.getCallingUid()) { - mAppSwitchesAllowed = true; + mAppSwitchesState = APP_SWITCH_ALLOW; } } return pir.sendInner(0, fillInIntent, resolvedType, allowlistToken, null, null, @@ -2158,8 +2178,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** * Return true if app switching is allowed. */ - boolean getBalAppSwitchesAllowed() { - return mAppSwitchesAllowed; + @AppSwitchState int getBalAppSwitchesState() { + return mAppSwitchesState; } /** Register an {@link AnrController} to control the ANR dialog behavior */ @@ -3680,8 +3700,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void stopAppSwitches() { mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "stopAppSwitches"); synchronized (mGlobalLock) { - mAppSwitchesAllowed = false; + mAppSwitchesState = APP_SWITCH_DISALLOW; mLastStopAppSwitchesTime = SystemClock.uptimeMillis(); + mH.removeMessages(H.RESUME_FG_APP_SWITCH_MSG); + mH.sendEmptyMessageDelayed(H.RESUME_FG_APP_SWITCH_MSG, RESUME_FG_APP_SWITCH_MS); } } @@ -3689,7 +3711,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void resumeAppSwitches() { mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "resumeAppSwitches"); synchronized (mGlobalLock) { - mAppSwitchesAllowed = true; + mAppSwitchesState = APP_SWITCH_ALLOW; + mH.removeMessages(H.RESUME_FG_APP_SWITCH_MSG); } } @@ -5170,6 +5193,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { static final int REPORT_TIME_TRACKER_MSG = 1; static final int UPDATE_PROCESS_ANIMATING_STATE = 2; static final int END_POWER_MODE_UNKNOWN_VISIBILITY_MSG = 3; + static final int RESUME_FG_APP_SWITCH_MSG = 4; static final int FIRST_ACTIVITY_TASK_MSG = 100; static final int FIRST_SUPERVISOR_TASK_MSG = 200; @@ -5207,6 +5231,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } break; + case RESUME_FG_APP_SWITCH_MSG: { + synchronized (mGlobalLock) { + if (mAppSwitchesState == APP_SWITCH_DISALLOW) { + mAppSwitchesState = APP_SWITCH_FG_ONLY; + } + } + } + break; } } } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index b9353e1f9a0c..421a1c916fdc 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -1634,21 +1634,21 @@ public class AppTransition implements Dump { } @TransitionType int getKeyguardTransition() { + if (mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_GOING_AWAY) != -1) { + return TRANSIT_KEYGUARD_GOING_AWAY; + } + final int unoccludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE); + final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE); + // No keyguard related transition requests. + if (unoccludeIndex == -1 && occludeIndex == -1) { + return TRANSIT_NONE; + } // In case we unocclude Keyguard and occlude it again, meaning that we never actually // unoccclude/occlude Keyguard, but just run a normal transition. - final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE); - if (occludeIndex != -1 - && occludeIndex < mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE)) { + if (unoccludeIndex != -1 && unoccludeIndex < occludeIndex) { return TRANSIT_NONE; } - - for (int i = 0; i < mNextAppTransitionRequests.size(); ++i) { - final @TransitionType int transit = mNextAppTransitionRequests.get(i); - if (isKeyguardTransit(transit)) { - return transit; - } - } - return TRANSIT_NONE; + return unoccludeIndex != -1 ? TRANSIT_KEYGUARD_UNOCCLUDE : TRANSIT_KEYGUARD_OCCLUDE; } @TransitionType int getFirstAppTransition() { diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java index 71a10df34d30..0afd87282783 100644 --- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java +++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java @@ -20,6 +20,8 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVIT import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS; +import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW; +import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY; import android.annotation.NonNull; import android.annotation.Nullable; @@ -70,13 +72,13 @@ class BackgroundLaunchProcessController { } boolean areBackgroundActivityStartsAllowed(int pid, int uid, String packageName, - boolean appSwitchAllowed, boolean isCheckingForFgsStart, + int appSwitchState, boolean isCheckingForFgsStart, boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges, long lastStopAppSwitchesTime, long lastActivityLaunchTime, long lastActivityFinishTime) { // If app switching is not allowed, we ignore all the start activity grace period // exception so apps cannot start itself in onPause() after pressing home button. - if (appSwitchAllowed) { + if (appSwitchState == APP_SWITCH_ALLOW) { // Allow if any activity in the caller has either started or finished very recently, and // it must be started or finished after last stop app switches time. final long now = SystemClock.uptimeMillis(); @@ -111,7 +113,8 @@ class BackgroundLaunchProcessController { return true; } // Allow if the caller has an activity in any foreground task. - if (appSwitchAllowed && hasActivityInVisibleTask) { + if (hasActivityInVisibleTask + && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "[Process(" + pid + ")] Activity start allowed: process has activity in foreground task"); diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java index b54208d11974..9ad30da1cb54 100644 --- a/services/core/java/com/android/server/wm/PinnedTaskController.java +++ b/services/core/java/com/android/server/wm/PinnedTaskController.java @@ -255,25 +255,27 @@ class PinnedTaskController { mDestRotatedBounds = null; mPipTransaction = null; final Rect areaBounds = taskArea.getBounds(); - if (pipTx != null) { + if (pipTx != null && pipTx.mPosition != null) { // The transaction from recents animation is in old rotation. So the position needs to // be rotated. - float dx = pipTx.mPositionX; - float dy = pipTx.mPositionY; + float dx = pipTx.mPosition.x; + float dy = pipTx.mPosition.y; final Matrix matrix = pipTx.getMatrix(); if (pipTx.mRotation == 90) { - dx = pipTx.mPositionY; - dy = areaBounds.right - pipTx.mPositionX; + dx = pipTx.mPosition.y; + dy = areaBounds.right - pipTx.mPosition.x; matrix.postRotate(-90); } else if (pipTx.mRotation == -90) { - dx = areaBounds.bottom - pipTx.mPositionY; - dy = pipTx.mPositionX; + dx = areaBounds.bottom - pipTx.mPosition.y; + dy = pipTx.mPosition.x; matrix.postRotate(90); } matrix.postTranslate(dx, dy); final SurfaceControl leash = pinnedTask.getSurfaceControl(); - t.setMatrix(leash, matrix, new float[9]) - .setCornerRadius(leash, pipTx.mCornerRadius); + t.setMatrix(leash, matrix, new float[9]); + if (pipTx.hasCornerRadiusSet()) { + t.setCornerRadius(leash, pipTx.mCornerRadius); + } Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy); return; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index fd4b63e26403..5dd8ef39e8e7 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -264,13 +264,6 @@ public class RecentsAnimationController implements DeathRecipient { "finish(%b): mCanceled=%b", moveHomeToTop, mCanceled); final long token = Binder.clearCallingIdentity(); try { - synchronized (mService.getWindowManagerLock()) { - // Remove all new task targets. - for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) { - removeTaskInternal(mPendingNewTaskTargets.get(i)); - } - } - // Note, the callback will handle its own synchronization, do not lock on WM lock // prior to calling the callback mCallbacks.onAnimationFinished(moveHomeToTop @@ -760,7 +753,7 @@ public class RecentsAnimationController implements DeathRecipient { // the task-id with the leaf id. final Task leafTask = task.getTopLeafTask(); int taskId = leafTask.mTaskId; - TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task, + TaskAnimationAdapter adapter = addAnimation(task, !recentTaskIds.get(taskId), true /* hidden */, finishedCallback); mPendingNewTaskTargets.add(taskId); return adapter.createRemoteAnimationTarget(taskId); @@ -1013,6 +1006,7 @@ public class RecentsAnimationController implements DeathRecipient { taskAdapter.onCleanup(); } // Should already be empty, but clean-up pending task-appears in-case they weren't sent. + mPendingNewTaskTargets.clear(); mPendingTaskAppears.clear(); for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a53a8cdea0a7..c88dbf719d94 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -321,6 +321,11 @@ class Task extends TaskFragment { */ boolean mInResumeTopActivity = false; + /** + * Used to identify if the activity that is installed from device's system image. + */ + boolean mIsEffectivelySystemApp; + int mCurrentUser; String affinity; // The affinity name for this task, or null; may change identity. @@ -568,13 +573,24 @@ class Task extends TaskFragment { if (r.finishing) return false; - // Set this as the candidate root since it isn't finishing. - mRoot = r; + if (mRoot == null || mRoot.finishing) { + // Set this as the candidate root since it isn't finishing. + mRoot = r; + } + + final int uid = mRoot == r ? effectiveUid : r.info.applicationInfo.uid; + if (ignoreRelinquishIdentity + || (mRoot.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0 + || (mRoot.info.applicationInfo.uid != Process.SYSTEM_UID + && !mRoot.info.applicationInfo.isSystemApp() + && mRoot.info.applicationInfo.uid != uid)) { + // No need to relinquish identity, end search. + return true; + } - // Only end search if we are ignore relinquishing identity or we are not relinquishing. - return ignoreRelinquishIdentity - || mNeverRelinquishIdentity - || (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0; + // Relinquish to next activity + mRoot = r; + return false; } } @@ -999,7 +1015,15 @@ class Task extends TaskFragment { * @param info The activity info which could be different from {@code r.info} if set. */ void setIntent(ActivityRecord r, @Nullable Intent intent, @Nullable ActivityInfo info) { - if (this.intent == null || !mNeverRelinquishIdentity) { + boolean updateIdentity = false; + if (this.intent == null) { + updateIdentity = true; + } else if (!mNeverRelinquishIdentity) { + final ActivityInfo activityInfo = info != null ? info : r.info; + updateIdentity = (effectiveUid == Process.SYSTEM_UID || mIsEffectivelySystemApp + || effectiveUid == activityInfo.applicationInfo.uid); + } + if (updateIdentity) { mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; mCallingFeatureId = r.launchedFromFeatureId; @@ -1012,14 +1036,7 @@ class Task extends TaskFragment { private void setIntent(Intent _intent, ActivityInfo info) { if (!isLeafTask()) return; - if (info.applicationInfo.uid == Process.SYSTEM_UID - || info.applicationInfo.isSystemApp()) { - // Only allow the apps that pre-installed on the system image to apply - // relinquishTaskIdentity - mNeverRelinquishIdentity = (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0; - } else { - mNeverRelinquishIdentity = true; - } + mNeverRelinquishIdentity = (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0; affinity = info.taskAffinity; if (intent == null) { // If this task already has an intent associated with it, don't set the root @@ -1028,6 +1045,7 @@ class Task extends TaskFragment { rootAffinity = affinity; } effectiveUid = info.applicationInfo.uid; + mIsEffectivelySystemApp = info.applicationInfo.isSystemApp(); stringName = null; if (info.targetActivity == null) { @@ -1451,11 +1469,11 @@ class Task extends TaskFragment { } /** Called when an {@link ActivityRecord} is added as a descendant */ - void onDescendantActivityAdded(boolean hadChild, int activityType, ActivityRecord r) { + void onDescendantActivityAdded(boolean hadActivity, int activityType, ActivityRecord r) { warnForNonLeafTask("onDescendantActivityAdded"); // Only set this based on the first activity - if (!hadChild) { + if (!hadActivity) { if (r.getActivityType() == ACTIVITY_TYPE_UNDEFINED) { // Normally non-standard activity type for the activity record will be set when the // object is created, however we delay setting the standard application type until @@ -3141,14 +3159,6 @@ class Task extends TaskFragment { } @Override - boolean fillsParent() { - // From the perspective of policy, we still want to report that this task fills parent - // in fullscreen windowing mode even it doesn't match parent bounds because there will be - // letterbox around its real content. - return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds(); - } - - @Override void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) { final int count = mChildren.size(); boolean isLeafTask = true; @@ -4581,14 +4591,15 @@ class Task extends TaskFragment { } super.setWindowingMode(windowingMode); - // Try reparent pinned activity back to its original task after onConfigurationChanged - // cascade finishes. This is done on Task level instead of - // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP, - // we set final windowing mode on the ActivityRecord first and then on its Task when - // the exit PiP transition finishes. Meanwhile, the exit transition is always - // performed on its original task, reparent immediately in ActivityRecord breaks it. - if (currentMode == WINDOWING_MODE_PINNED) { - if (topActivity != null && topActivity.getLastParentBeforePip() != null) { + if (currentMode == WINDOWING_MODE_PINNED && topActivity != null) { + // Try reparent pinned activity back to its original task after + // onConfigurationChanged cascade finishes. This is done on Task level instead of + // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit + // PiP, we set final windowing mode on the ActivityRecord first and then on its + // Task when the exit PiP transition finishes. Meanwhile, the exit transition is + // always performed on its original task, reparent immediately in ActivityRecord + // breaks it. + if (topActivity.getLastParentBeforePip() != null) { // Do not reparent if the pinned task is in removal, indicated by the // force hidden flag. if (!isForceHidden()) { @@ -4601,6 +4612,11 @@ class Task extends TaskFragment { } } } + // Resume app-switches-allowed flag when exiting from pinned mode since + // it does not follow the ActivityStarter path. + if (topActivity.shouldBeVisible()) { + mAtmService.resumeAppSwitches(); + } } if (creating) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 510ca7775263..ef143c21ff95 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -102,6 +102,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; @@ -1670,8 +1671,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { boolean isAddingActivity = child.asActivityRecord() != null; final Task task = isAddingActivity ? getTask() : null; - // If this task had any child before we added this one. - boolean taskHadChild = task != null && task.hasChild(); + // If this task had any activity before we added this one. + boolean taskHadActivity = task != null && task.getActivity(Objects::nonNull) != null; // getActivityType() looks at the top child, so we need to read the type before adding // a new child in case the new child is on top and UNDEFINED. final int activityType = task != null ? task.getActivityType() : ACTIVITY_TYPE_UNDEFINED; @@ -1680,7 +1681,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (isAddingActivity && task != null) { child.asActivityRecord().inHistory = true; - task.onDescendantActivityAdded(taskHadChild, activityType, child.asActivityRecord()); + task.onDescendantActivityAdded(taskHadActivity, activityType, child.asActivityRecord()); } } @@ -2190,14 +2191,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { TaskFragmentInfo getTaskFragmentInfo() { List<IBinder> childActivities = new ArrayList<>(); for (int i = 0; i < getChildCount(); i++) { - WindowContainer wc = getChildAt(i); - if (mTaskFragmentOrganizerUid != INVALID_UID - && wc.asActivityRecord() != null - && wc.asActivityRecord().info.processName.equals( - mTaskFragmentOrganizerProcessName) - && wc.asActivityRecord().getUid() == mTaskFragmentOrganizerUid) { + final WindowContainer wc = getChildAt(i); + final ActivityRecord ar = wc.asActivityRecord(); + if (mTaskFragmentOrganizerUid != INVALID_UID && ar != null + && ar.info.processName.equals(mTaskFragmentOrganizerProcessName) + && ar.getUid() == mTaskFragmentOrganizerUid && !ar.finishing) { // Only includes Activities that belong to the organizer process for security. - childActivities.add(wc.asActivityRecord().appToken); + childActivities.add(ar.appToken); } } final Point positionInParent = new Point(); @@ -2346,6 +2346,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { return true; } + @Override + boolean fillsParent() { + // From the perspective of policy, we still want to report that this task fills parent + // in fullscreen windowing mode even it doesn't match parent bounds because there will be + // letterbox around its real content. + return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds(); + } + boolean dump(String prefix, FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient, String dumpPackage, final boolean needSep, Runnable header) { boolean printed = false; diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 29c27f9f3af6..c7fdefc412cc 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -24,6 +24,7 @@ import static com.android.server.wm.WindowOrganizerController.configurationsAreE import android.annotation.IntDef; import android.annotation.Nullable; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -579,4 +580,26 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr event.mException); } } + + // TODO(b/204399167): change to push the embedded state to the client side + @Override + public boolean isActivityEmbedded(IBinder activityToken) { + synchronized (mGlobalLock) { + final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken); + if (activity == null) { + return false; + } + final TaskFragment taskFragment = activity.getOrganizedTaskFragment(); + if (taskFragment == null) { + return false; + } + final Task parentTask = taskFragment.getTask(); + if (parentTask != null) { + final Rect taskBounds = parentTask.getBounds(); + final Rect taskFragBounds = taskFragment.getBounds(); + return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds); + } + return false; + } + } } diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java index cc527136eb51..7956a112539e 100644 --- a/services/core/java/com/android/server/wm/WindowContextListenerController.java +++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java @@ -173,7 +173,7 @@ class WindowContextListenerController { @VisibleForTesting class WindowContextListenerImpl implements WindowContainerListener { - @NonNull private final IBinder mClientToken; + @NonNull private final IWindowToken mClientToken; private final int mOwnerUid; @NonNull private WindowContainer<?> mContainer; /** @@ -193,7 +193,7 @@ class WindowContextListenerController { private WindowContextListenerImpl(IBinder clientToken, WindowContainer<?> container, int ownerUid, @WindowType int type, @Nullable Bundle options) { - mClientToken = clientToken; + mClientToken = IWindowToken.Stub.asInterface(clientToken); mContainer = Objects.requireNonNull(container); mOwnerUid = ownerUid; mType = type; @@ -205,7 +205,7 @@ class WindowContextListenerController { mDeathRecipient = deathRecipient; } catch (RemoteException e) { ProtoLog.e(WM_ERROR, "Could not register window container listener token=%s, " - + "container=%s", mClientToken, mContainer); + + "container=%s", clientToken, mContainer); } } @@ -228,17 +228,17 @@ class WindowContextListenerController { } private void register() { + final IBinder token = mClientToken.asBinder(); if (mDeathRecipient == null) { - throw new IllegalStateException("Invalid client token: " + mClientToken); + throw new IllegalStateException("Invalid client token: " + token); } - mListeners.putIfAbsent(mClientToken, this); + mListeners.putIfAbsent(token, this); mContainer.registerWindowContainerListener(this); - reportConfigToWindowTokenClient(); } private void unregister() { mContainer.unregisterWindowContainerListener(this); - mListeners.remove(mClientToken); + mListeners.remove(mClientToken.asBinder()); } private void clear() { @@ -258,19 +258,24 @@ class WindowContextListenerController { private void reportConfigToWindowTokenClient() { if (mDeathRecipient == null) { - throw new IllegalStateException("Invalid client token: " + mClientToken); + throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder()); + } + final DisplayContent dc = mContainer.getDisplayContent(); + if (!dc.isReady()) { + // Do not report configuration when booting. The latest configuration will be sent + // when WindowManagerService#displayReady(). + return; } // If the display of window context associated window container is suspended, don't // report the configuration update. Note that we still dispatch the configuration update // to WindowProviderService to make it compatible with Service#onConfigurationChanged. // Service always receives #onConfigurationChanged callback regardless of display state. - if (!isWindowProviderService(mOptions) - && isSuspendedState(mContainer.getDisplayContent().getDisplayInfo().state)) { + if (!isWindowProviderService(mOptions) && isSuspendedState(dc.getDisplayInfo().state)) { mHasPendingConfiguration = true; return; } final Configuration config = mContainer.getConfiguration(); - final int displayId = mContainer.getDisplayContent().getDisplayId(); + final int displayId = dc.getDisplayId(); if (mLastReportedConfig == null) { mLastReportedConfig = new Configuration(); } @@ -282,9 +287,8 @@ class WindowContextListenerController { mLastReportedConfig.setTo(config); mLastReportedDisplay = displayId; - IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(mClientToken); try { - windowTokenClient.onConfigurationChanged(config, displayId); + mClientToken.onConfigurationChanged(config, displayId); } catch (RemoteException e) { ProtoLog.w(WM_ERROR, "Could not report config changes to the window token client."); } @@ -294,7 +298,7 @@ class WindowContextListenerController { @Override public void onRemoved() { if (mDeathRecipient == null) { - throw new IllegalStateException("Invalid client token: " + mClientToken); + throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder()); } final WindowToken windowToken = mContainer.asWindowToken(); if (windowToken != null && windowToken.isFromClient()) { @@ -312,9 +316,8 @@ class WindowContextListenerController { } } mDeathRecipient.unlinkToDeath(); - IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(mClientToken); try { - windowTokenClient.onWindowTokenRemoved(); + mClientToken.onWindowTokenRemoved(); } catch (RemoteException e) { ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client."); } @@ -323,7 +326,7 @@ class WindowContextListenerController { @Override public String toString() { - return "WindowContextListenerImpl{clientToken=" + mClientToken + ", " + return "WindowContextListenerImpl{clientToken=" + mClientToken.asBinder() + ", " + "container=" + mContainer + "}"; } @@ -337,11 +340,11 @@ class WindowContextListenerController { } void linkToDeath() throws RemoteException { - mClientToken.linkToDeath(this, 0); + mClientToken.asBinder().linkToDeath(this, 0); } void unlinkToDeath() { - mClientToken.unlinkToDeath(this, 0); + mClientToken.asBinder().unlinkToDeath(this, 0); } } } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 1cfbe07d3f16..3ccb06ccef15 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -512,19 +512,19 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio */ @HotPath(caller = HotPath.START_SERVICE) public boolean areBackgroundFgsStartsAllowed() { - return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesAllowed(), + return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesState(), true /* isCheckingForFgsStart */); } - boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed) { - return areBackgroundActivityStartsAllowed(appSwitchAllowed, + boolean areBackgroundActivityStartsAllowed(int appSwitchState) { + return areBackgroundActivityStartsAllowed(appSwitchState, false /* isCheckingForFgsStart */); } - private boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed, + private boolean areBackgroundActivityStartsAllowed(int appSwitchState, boolean isCheckingForFgsStart) { return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid, mInfo.packageName, - appSwitchAllowed, isCheckingForFgsStart, hasActivityInVisibleTask(), + appSwitchState, isCheckingForFgsStart, hasActivityInVisibleTask(), mInstrumentingWithBackgroundActivityStartPrivileges, mAtm.getLastStopAppSwitchesTime(), mLastActivityLaunchTime, mLastActivityFinishTime); diff --git a/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java b/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java index 49d5e50e0345..d3353cd6adc7 100644 --- a/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java +++ b/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java @@ -31,6 +31,7 @@ import android.os.CancellationSignal; import com.android.server.LocalServices; import com.android.server.people.PeopleServiceInternal; +import com.android.server.pm.PackageManagerService; /** * If a {@link ConversationStatus} is added to the system with an expiration time, remove that @@ -50,6 +51,7 @@ public class ConversationStatusExpirationBroadcastReceiver extends BroadcastRece final PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_CODE, new Intent(ACTION) + .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME) .setData(new Uri.Builder().scheme(SCHEME) .appendPath(getKey(userId, pkg, conversationId, status)) .build()) diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 18f1267b890f..0dd4f5b338b4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; @@ -1854,6 +1855,36 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test + public void testUpdateOomAdj_DoAll_BoundByPersService_Cycle_Branch_Capability() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + bindService(app, client, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); + ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, + MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); + bindService(client, client2, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); + bindService(client2, app, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); + ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, + MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); + client3.mState.setMaxAdj(PERSISTENT_PROC_ADJ); + bindService(app, client3, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); + ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP(); + lru.clear(); + lru.add(app); + lru.add(client); + lru.add(client2); + lru.add(client3); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + + assertEquals(PROCESS_CAPABILITY_ALL, client.mState.getSetCapability()); + assertEquals(PROCESS_CAPABILITY_ALL, client2.mState.getSetCapability()); + assertEquals(PROCESS_CAPABILITY_ALL, app.mState.getSetCapability()); + } + + @SuppressWarnings("GuardedBy") + @Test public void testUpdateOomAdj_DoAll_Provider_Cycle_Branch_2() { ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt index fd97557480bb..e053dc3fd32e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt @@ -29,6 +29,7 @@ import com.android.server.apphibernation.AppHibernationManagerInternal import com.android.server.apphibernation.AppHibernationService import com.android.server.extendedtestutils.wheneverStatic import com.android.server.testutils.whenever +import org.junit.Assert import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -100,8 +101,11 @@ class PackageManagerServiceHibernationTests { rule.system().dataAppDirectory) val pm = createPackageManagerService() rule.system().validateFinalState() - val ps = pm.getPackageSetting(TEST_PACKAGE_NAME) - ps!!.setStopped(true, TEST_USER_ID) + + TestableLooper.get(this).processAllMessages() + + whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(true) pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID) @@ -112,6 +116,31 @@ class PackageManagerServiceHibernationTests { } @Test + fun testExitForceStop_nonExistingAppHibernationManager_doesNotThrowException() { + whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java)) + .thenReturn(null) + + rule.system().stageScanExistingPackage( + TEST_PACKAGE_NAME, + 1L, + rule.system().dataAppDirectory) + val pm = createPackageManagerService() + rule.system().validateFinalState() + + TestableLooper.get(this).processAllMessages() + + whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(true) + + try { + pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID) + TestableLooper.get(this).processAllMessages() + } catch (e: Exception) { + Assert.fail("Method throws exception when AppHibernationManager is not ready.\n$e") + } + } + + @Test fun testGetOptimizablePackages_ExcludesGloballyHibernatingPackages() { rule.system().stageScanExistingPackage( TEST_PACKAGE_NAME, diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index 4c638d669019..bb3eb81df6ed 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -16,6 +16,13 @@ <configuration description="Runs Frameworks Services Tests."> <option name="test-suite-tag" value="apct" /> <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push-file" key="SimpleServiceTestApp3.apk" + value="/data/local/tmp/cts/content/SimpleServiceTestApp3.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java index 8b6b7c235c44..1d6ed038b86d 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java @@ -296,14 +296,6 @@ public class SystemActionPerformerTest { } @Test - public void testToggleSplitScreen_legacy() { - setupWithRealContext(); - mSystemActionPerformer.performSystemAction( - AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN); - verify(mMockStatusBarManagerInternal).toggleSplitScreen(); - } - - @Test public void testScreenshot_requestsFromScreenshotHelper_legacy() { setupWithMockContext(); mSystemActionPerformer.performSystemAction( diff --git a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java index 10f4c05eb6d8..e6a8dea973c9 100644 --- a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -26,7 +27,9 @@ import android.app.Instrumentation; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; +import android.os.IBinder; import android.os.SystemClock; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; @@ -42,6 +45,8 @@ import org.junit.runner.RunWith; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Build/Install/Run: @@ -69,6 +74,12 @@ public final class ServiceRestarterTest { private static final int ACTION_STOPPKG = 8; private static final int ACTION_ALL = ACTION_START | ACTION_KILL | ACTION_WAIT | ACTION_STOPPKG; + private static final String LOCAL_APK_BASE_PATH = "/data/local/tmp/cts/content/"; + private static final String TEST_PACKAGE3_APK = "SimpleServiceTestApp3.apk"; + private static final String ACTION_SERVICE_WITH_DEP_PKG = + "com.android.servicestests.apps.simpleservicetestapp.ACTION_SERVICE_WITH_DEP_PKG"; + private static final String EXTRA_TARGET_PACKAGE = "target_package"; + private Context mContext; private Instrumentation mInstrumentation; private int mTestPackage1Uid; @@ -199,6 +210,83 @@ public final class ServiceRestarterTest { return res; } + @Test + public void testServiceWithDepPkgStopped() throws Exception { + final CountDownLatch[] latchHolder = new CountDownLatch[1]; + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + latchHolder[0].countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + latchHolder[0].countDown(); + } + }; + + final long timeout = 5_000; + final long shortTimeout = 2_000; + final Intent intent = new Intent(ACTION_SERVICE_WITH_DEP_PKG); + final String testPkg = TEST_PACKAGE2_NAME; + final String libPkg = TEST_PACKAGE3_NAME; + final String apkPath = LOCAL_APK_BASE_PATH + TEST_PACKAGE3_APK; + final ActivityManager am = mContext.getSystemService(ActivityManager.class); + + intent.setComponent(ComponentName.unflattenFromString(testPkg + "/" + TEST_SERVICE_NAME)); + intent.putExtra(EXTRA_TARGET_PACKAGE, libPkg); + try { + executeShellCmd("am service-restart-backoff disable " + testPkg); + + latchHolder[0] = new CountDownLatch(1); + assertTrue("Unable to bind to test service in " + testPkg, + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)); + assertTrue("Timed out to bind service in " + testPkg, + latchHolder[0].await(timeout, TimeUnit.MILLISECONDS)); + + Thread.sleep(shortTimeout); + assertTrue(libPkg + " should be a dependency package of " + testPkg, + isPackageDependency(testPkg, libPkg)); + + // Force-stop lib package, the test service should be restarted. + latchHolder[0] = new CountDownLatch(2); + am.forceStopPackage(libPkg); + assertTrue("Test service in didn't restart in " + testPkg, + latchHolder[0].await(timeout, TimeUnit.MILLISECONDS)); + + Thread.sleep(shortTimeout); + + // Re-install the lib package, the test service should be restarted. + latchHolder[0] = new CountDownLatch(2); + assertTrue("Unable to install package " + apkPath, installPackage(apkPath)); + assertTrue("Test service in didn't restart in " + testPkg, + latchHolder[0].await(timeout, TimeUnit.MILLISECONDS)); + + Thread.sleep(shortTimeout); + + // Force-stop the service package, the test service should not be restarted. + latchHolder[0] = new CountDownLatch(2); + am.forceStopPackage(testPkg); + assertFalse("Test service should not be restarted in " + testPkg, + latchHolder[0].await(timeout * 2, TimeUnit.MILLISECONDS)); + } finally { + executeShellCmd("am service-restart-backoff enable " + testPkg); + mContext.unbindService(conn); + am.forceStopPackage(testPkg); + } + } + + private boolean isPackageDependency(String pkgName, String libPackage) throws Exception { + final String output = SystemUtil.runShellCommand("dumpsys activity processes " + pkgName); + final Matcher matcher = Pattern.compile("packageDependencies=\\{.*?\\b" + + libPackage + "\\b.*?\\}").matcher(output); + return matcher.find(); + } + + private boolean installPackage(String apkPath) throws Exception { + return executeShellCmd("pm install -t " + apkPath).equals("Success\n"); + } + private void startServiceAndWait(String pkgName, MyUidImportanceListener uidListener, long timeout) throws Exception { final Intent intent = new Intent(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index b3f7587df612..b255a35c512e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -302,6 +302,65 @@ public class AuthSessionTest { testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null)); } + // TODO (b/208484275) : Enable these tests + // @Test + // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(false); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult()); + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + + // @Test + // public void testPreAuth_cannotAuthAndPrivacyEnabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(true); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED, + // preAuthInfo.getCanAuthenticateResult()); + // // Even though canAuth returns privacy enabled, we should still be able to authenticate. + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + + // @Test + // public void testPreAuth_canAuthAndPrivacyEnabledCredentialEnabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(true); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = + // createPromptInfo(Authenticators.BIOMETRIC_STRONG + // | Authenticators. DEVICE_CREDENTIAL); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult()); + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + private void testInvokesCancel(Consumer<AuthSession> sessionConsumer) throws RemoteException { final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class); @@ -331,7 +390,8 @@ public class AuthSessionTest { userId, promptInfo, TEST_PACKAGE, - checkDevicePolicyManager); + checkDevicePolicyManager, + mContext); } private AuthSession createAuthSession(List<BiometricSensor> sensors, diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml index 78afb7b72c04..3cc105ebb746 100644 --- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml +++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml @@ -18,6 +18,7 @@ package="com.android.servicestests.apps.simpleservicetestapp"> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <application> <service android:name=".SimpleService" diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java index 4e981b22cd32..b8654d7f4e74 100644 --- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java +++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java @@ -17,8 +17,10 @@ package com.android.servicestests.apps.simpleservicetestapp; import android.app.Service; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; @@ -33,6 +35,9 @@ public class SimpleService extends Service { private static final String TEST_CLASS = "com.android.servicestests.apps.simpleservicetestapp.SimpleService"; + private static final String ACTION_SERVICE_WITH_DEP_PKG = + "com.android.servicestests.apps.simpleservicetestapp.ACTION_SERVICE_WITH_DEP_PKG"; + private static final String EXTRA_CALLBACK = "callback"; private static final String EXTRA_COMMAND = "command"; private static final String EXTRA_FLAGS = "flags"; @@ -118,6 +123,21 @@ public class SimpleService extends Service { @Override public IBinder onBind(Intent intent) { + if (ACTION_SERVICE_WITH_DEP_PKG.equals(intent.getAction())) { + final String targetPkg = intent.getStringExtra(EXTRA_TARGET_PACKAGE); + Log.i(TAG, "SimpleService.onBind: " + ACTION_SERVICE_WITH_DEP_PKG + " " + targetPkg); + if (targetPkg != null) { + Context pkgContext = null; + try { + pkgContext = createPackageContext(targetPkg, + Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to create package context for " + pkgContext, e); + } + // This effectively loads the target package as a dependency. + pkgContext.getClassLoader(); + } + } return mBinder; } } 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 0ad119df6b55..c994b41be593 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -61,10 +61,6 @@ import static android.service.notification.NotificationListenerService.FLAG_FILT import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; -import static com.android.server.notification.NotificationManagerService.ACTION_DISABLE_NAS; -import static com.android.server.notification.NotificationManagerService.ACTION_ENABLE_NAS; -import static com.android.server.notification.NotificationManagerService.ACTION_LEARNMORE_NAS; - import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -325,7 +321,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock MultiRateLimiter mToastRateLimiter; BroadcastReceiver mPackageIntentReceiver; - BroadcastReceiver mNASIntentReceiver; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @@ -553,14 +548,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED) && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) { mPackageIntentReceiver = broadcastReceivers.get(i); - } else if (filter.hasAction(ACTION_ENABLE_NAS) - && filter.hasAction(ACTION_DISABLE_NAS) - && filter.hasAction(ACTION_LEARNMORE_NAS)) { - mNASIntentReceiver = broadcastReceivers.get(i); } } assertNotNull("package intent receiver should exist", mPackageIntentReceiver); - assertNotNull("nas intent receiver should exist", mNASIntentReceiver); // Pretend the shortcut exists List<ShortcutInfo> shortcutInfos = new ArrayList<>(); @@ -655,16 +645,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageIntentReceiver.onReceive(getContext(), intent); } - private void simulateNASUpgradeBroadcast(String action, int uid) { - final Bundle extras = new Bundle(); - extras.putInt(Intent.EXTRA_USER_ID, uid); - - final Intent intent = new Intent(action); - intent.putExtras(extras); - - mNASIntentReceiver.onReceive(getContext(), intent); - } - private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() { ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>(); changed.put(true, new ArrayList<>()); @@ -6042,7 +6022,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testNASSettingUpgrade_userSetNull_noOnBoarding() throws RemoteException { + public void testNASSettingUpgrade_userSetNull() throws RemoteException { ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component1"); TestableNotificationManagerService service = spy(mService); int userId = 11; @@ -6055,14 +6035,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArrayList<>()); when(mAssistants.hasUserSet(userId)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); + service.migrateDefaultNAS(); assertTrue(service.isNASMigrationDone(userId)); - verify(service, times(0)).createNASUpgradeNotification(eq(userId)); verify(mAssistants, times(1)).clearDefaults(); } @Test - public void testNASSettingUpgrade_userSetSameDefault_noOnBoarding() throws RemoteException { + public void testNASSettingUpgrade_userSet() throws RemoteException { ComponentName defaultComponent = ComponentName.unflattenFromString("package/Component1"); TestableNotificationManagerService service = spy(mService); int userId = 11; @@ -6075,55 +6054,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArrayList(Arrays.asList(defaultComponent))); when(mAssistants.hasUserSet(userId)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); - assertTrue(service.isNASMigrationDone(userId)); - verify(service, times(0)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(1)).resetDefaultFromConfig(); - } - - @Test - public void testNASSettingUpgrade_userSetDifferentDefault_showOnboarding() - throws RemoteException { - ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1"); - ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component2"); - TestableNotificationManagerService service = spy(mService); - int userId = 11; - setUsers(new int[]{userId}); - setNASMigrationDone(false, userId); - when(mAssistants.getDefaultComponents()) - .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))); - when(mAssistants.getDefaultFromConfig()) - .thenReturn(newDefaultComponent); - when(mAssistants.getAllowedComponents(anyInt())) - .thenReturn(Arrays.asList(oldDefaultComponent)); - when(mAssistants.hasUserSet(userId)).thenReturn(true); - - service.migrateDefaultNASShowNotificationIfNecessary(); - assertFalse(service.isNASMigrationDone(userId)); - //TODO(b/192450820) - //verify(service, times(1)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - - //Test user clear data before enable/disable from onboarding notification - ArrayMap<Boolean, ArrayList<ComponentName>> changedListeners = - generateResetComponentValues(); - when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners); - ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>(); - changes.put(true, new ArrayList(Arrays.asList(newDefaultComponent))); - changes.put(false, new ArrayList()); - when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changes); - - //Clear data - service.getBinderService().clearData("package", userId, false); - //Test migrate flow again - service.migrateDefaultNASShowNotificationIfNecessary(); - - //The notification should be still there - assertFalse(service.isNASMigrationDone(userId)); - //TODO(b/192450820) - //verify(service, times(2)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - assertEquals(oldDefaultComponent, service.getApprovedAssistant(userId)); + service.migrateDefaultNAS(); + verify(mAssistants, times(1)).setUserSet(userId, false); + //resetDefaultAssistantsIfNecessary should invoke from readPolicyXml() and migration + verify(mAssistants, times(2)).resetDefaultAssistantsIfNecessary(); } @Test @@ -6143,24 +6077,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))); when(mAssistants.getDefaultFromConfig()) .thenReturn(newDefaultComponent); - //User1: need onboarding + //User1: set different NAS when(mAssistants.getAllowedComponents(userId1)) .thenReturn(Arrays.asList(oldDefaultComponent)); - //User2: no onboarding + //User2: set to none when(mAssistants.getAllowedComponents(userId2)) - .thenReturn(Arrays.asList(newDefaultComponent)); + .thenReturn(new ArrayList<>()); when(mAssistants.hasUserSet(userId1)).thenReturn(true); when(mAssistants.hasUserSet(userId2)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); - assertFalse(service.isNASMigrationDone(userId1)); + service.migrateDefaultNAS(); + // user1's setting get reset + verify(mAssistants, times(1)).setUserSet(userId1, false); + verify(mAssistants, times(0)).setUserSet(eq(userId2), anyBoolean()); assertTrue(service.isNASMigrationDone(userId2)); - //TODO(b/192450820) - //verify(service, times(1)).createNASUpgradeNotification(any(Integer.class)); - // only user2's default get updated - verify(mAssistants, times(1)).resetDefaultFromConfig(); } @Test @@ -6180,7 +6112,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))); when(mAssistants.getDefaultFromConfig()) .thenReturn(newDefaultComponent); - //Both profiles: need onboarding + //Both profiles: set different NAS when(mAssistants.getAllowedComponents(userId1)) .thenReturn(Arrays.asList(oldDefaultComponent)); when(mAssistants.getAllowedComponents(userId2)) @@ -6189,13 +6121,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.hasUserSet(userId1)).thenReturn(true); when(mAssistants.hasUserSet(userId2)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); + service.migrateDefaultNAS(); assertFalse(service.isNASMigrationDone(userId1)); assertFalse(service.isNASMigrationDone(userId2)); - - // TODO(b/192450820): only user1 get notification - //verify(service, times(1)).createNASUpgradeNotification(eq(userId1)); - //verify(service, times(0)).createNASUpgradeNotification(eq(userId2)); } @@ -6223,79 +6151,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { //Clear data service.getBinderService().clearData("package", userId, false); //Test migrate flow again - service.migrateDefaultNASShowNotificationIfNecessary(); - - //TODO(b/192450820): The notification should not appear again - //verify(service, times(0)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - } - - @Test - public void testNASUpgradeNotificationDisableBroadcast_multiProfile() { - int userId1 = 11; - int userId2 = 12; - setUsers(new int[]{userId1, userId2}); - when(mUm.isManagedProfile(userId2)).thenReturn(true); - when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1, userId2}); - - TestableNotificationManagerService service = spy(mService); - setNASMigrationDone(false, userId1); - setNASMigrationDone(false, userId2); + service.migrateDefaultNAS(); - simulateNASUpgradeBroadcast(ACTION_DISABLE_NAS, userId1); - - assertTrue(service.isNASMigrationDone(userId1)); - assertTrue(service.isNASMigrationDone(userId2)); - // User disabled the NAS from notification, the default stored in xml should be null - // rather than the new default - verify(mAssistants, times(1)).clearDefaults(); - verify(mAssistants, times(0)).resetDefaultFromConfig(); + //Migration should not happen again + verify(mAssistants, times(0)).setUserSet(userId, false); + verify(mAssistants, times(0)).clearDefaults(); + //resetDefaultAssistantsIfNecessary should only invoke once from readPolicyXml() + verify(mAssistants, times(1)).resetDefaultAssistantsIfNecessary(); - //TODO(b/192450820):No more notification after disabled - //service.migrateDefaultNASShowNotificationIfNecessary(); - //verify(service, times(0)).createNASUpgradeNotification(anyInt()); } - @Test - public void testNASUpgradeNotificationEnableBroadcast_multiUser() { - int userId1 = 11; - int userId2 = 12; - setUsers(new int[]{userId1, userId2}); - when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1}); - - TestableNotificationManagerService service = spy(mService); - setNASMigrationDone(false, userId1); - setNASMigrationDone(false, userId2); - - simulateNASUpgradeBroadcast(ACTION_ENABLE_NAS, userId1); - - assertTrue(service.isNASMigrationDone(userId1)); - assertFalse(service.isNASMigrationDone(userId2)); - verify(mAssistants, times(1)).resetDefaultFromConfig(); - - //TODO(b/192450820) - //service.migrateDefaultNASShowNotificationIfNecessary(); - //verify(service, times(0)).createNASUpgradeNotification(eq(userId1)); - } - - @Test - public void testNASUpgradeNotificationLearnMoreBroadcast() { - int userId = 11; - setUsers(new int[]{userId}); - TestableNotificationManagerService service = spy(mService); - setNASMigrationDone(false, userId); - doNothing().when(mContext).startActivity(any()); - - simulateNASUpgradeBroadcast(ACTION_LEARNMORE_NAS, userId); - - verify(mContext, times(1)).startActivity(any(Intent.class)); - assertFalse(service.isNASMigrationDone(userId)); - //TODO(b/192450820) - //verify(service, times(0)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - } - - private void setNASMigrationDone(boolean done, int userId) { Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.NAS_SETTINGS_UPDATED, done ? 1 : 0, userId); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index c0959d311ed5..2ea7fdaf6348 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -22,6 +22,9 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; @@ -92,6 +95,7 @@ public class AppTransitionTests extends WindowTestsBase { final ActivityRecord activity = createActivityRecord(dc); mDc.prepareAppTransition(TRANSIT_OPEN); + mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE); mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY); mDc.mOpeningApps.add(activity); assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY, @@ -102,6 +106,22 @@ public class AppTransitionTests extends WindowTestsBase { } @Test + public void testKeyguardUnoccludeOcclude() { + final DisplayContent dc = createNewDisplay(Display.STATE_ON); + final ActivityRecord activity = createActivityRecord(dc); + + mDc.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE); + mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE); + mDc.mOpeningApps.add(activity); + assertEquals(TRANSIT_NONE, + AppTransitionController.getTransitCompatType(mDc.mAppTransition, + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /*skipAppTransitionAnimation*/)); + + } + + @Test public void testKeyguardKeep() { final DisplayContent dc = createNewDisplay(Display.STATE_ON); final ActivityRecord activity = createActivityRecord(dc); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 8c1045d995d6..bfaa8150cb99 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -899,22 +899,21 @@ public class TaskTests extends WindowTestsBase { /** * Test that root activity index is reported correctly when looking for the 'effective root' in - * case when bottom activity is finishing. Ignore the relinquishing task identity if it's not a - * system activity even with the FLAG_RELINQUISH_TASK_IDENTITY. + * case when bottom activities are relinquishing task identity or finishing. */ @Test public void testFindRootIndex_effectiveRoot_finishingAndRelinquishing() { - final Task task = getTestTask(); + final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final Task task = activity0.getTask(); // Add extra two activities. Mark the one on the bottom with "relinquishTaskIdentity" and // one above as finishing. - final ActivityRecord activity0 = task.getBottomMostActivity(); activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); activity1.finishing = true; new ActivityBuilder(mAtm).setTask(task).build(); assertEquals("The first non-finishing activity and non-relinquishing task identity " - + "must be reported.", task.getChildAt(0), task.getRootActivity( + + "must be reported.", task.getChildAt(2), task.getRootActivity( false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); } @@ -934,21 +933,21 @@ public class TaskTests extends WindowTestsBase { } /** - * Test that the root activity index is reported correctly when looking for the - * 'effective root' for the case when all non-system activities have relinquishTaskIdentity set. + * Test that the topmost activity index is reported correctly when looking for the + * 'effective root' for the case when all activities have relinquishTaskIdentity set. */ @Test public void testFindRootIndex_effectiveRoot_relinquishingMultipleActivities() { - final Task task = getTestTask(); + final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final Task task = activity0.getTask(); // Set relinquishTaskIdentity for all activities in the task - final ActivityRecord activity0 = task.getBottomMostActivity(); activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; - assertEquals("The topmost activity in the task must be reported.", task.getChildAt(0), - task.getRootActivity(false /*ignoreRelinquishIdentity*/, - true /*setToBottomIfNone*/)); + assertEquals("The topmost activity in the task must be reported.", + task.getChildAt(task.getChildCount() - 1), task.getRootActivity( + false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); } /** Test that bottom-most activity is reported in {@link Task#getRootActivity()}. */ @@ -1086,14 +1085,14 @@ public class TaskTests extends WindowTestsBase { } /** - * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with non-system - * activity that relinquishes task identity. + * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with activity that + * relinquishes task identity. */ @Test public void testGetTaskForActivity_onlyRoot_relinquishTaskIdentity() { - final Task task = getTestTask(); + final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final Task task = activity0.getTask(); // Make the current root activity relinquish task identity - final ActivityRecord activity0 = task.getBottomMostActivity(); activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; // Add an extra activity on top - this will be the new root final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); @@ -1102,7 +1101,7 @@ public class TaskTests extends WindowTestsBase { assertEquals(task.mTaskId, ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */)); - assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID, + assertEquals(task.mTaskId, ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */)); assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID, ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */)); @@ -1189,6 +1188,46 @@ public class TaskTests extends WindowTestsBase { verify(task).setIntent(eq(activity0)); } + /** + * Test {@link Task#updateEffectiveIntent()} when activity with relinquishTaskIdentity but + * another with different uid. This should make the task use the root activity when updating the + * intent. + */ + @Test + public void testUpdateEffectiveIntent_relinquishingWithDifferentUid() { + final ActivityRecord activity0 = new ActivityBuilder(mAtm) + .setActivityFlags(FLAG_RELINQUISH_TASK_IDENTITY).setCreateTask(true).build(); + final Task task = activity0.getTask(); + + // Add an extra activity on top + new ActivityBuilder(mAtm).setUid(11).setTask(task).build(); + + spyOn(task); + task.updateEffectiveIntent(); + verify(task).setIntent(eq(activity0)); + } + + /** + * Test {@link Task#updateEffectiveIntent()} with activities set as relinquishTaskIdentity. + * This should make the task use the topmost activity when updating the intent. + */ + @Test + public void testUpdateEffectiveIntent_relinquishingMultipleActivities() { + final ActivityRecord activity0 = new ActivityBuilder(mAtm) + .setActivityFlags(FLAG_RELINQUISH_TASK_IDENTITY).setCreateTask(true).build(); + final Task task = activity0.getTask(); + // Add an extra activity on top + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + + // Add an extra activity on top + final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); + + spyOn(task); + task.updateEffectiveIntent(); + verify(task).setIntent(eq(activity2)); + } + @Test public void testSaveLaunchingStateWhenConfigurationChanged() { LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index c9a8947ab5ef..7b5f0b180c6e 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -118,6 +118,8 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testNullNetworkDoesNotTriggerDisconnect() throws Exception { + doReturn(false).when(mDeps).isAirplaneModeOn(any()); + mGatewayConnection .getUnderlyingNetworkTrackerCallback() .onSelectedUnderlyingNetworkChanged(null); @@ -129,6 +131,19 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } @Test + public void testNullNetworkAirplaneModeDisconnects() throws Exception { + doReturn(true).when(mDeps).isAirplaneModeOn(any()); + + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(null); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + verify(mIkeSession).kill(); + } + + @Test public void testNewNetworkTriggersMigration() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() |