diff options
126 files changed, 1776 insertions, 1085 deletions
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java index 1c40a06c1c86..42fb24f570d2 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java @@ -217,7 +217,7 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase } @Override - public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException { + public void onTasksAppeared(RemoteAnimationTarget[] app) throws RemoteException { /* no-op */ } }; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 40880c19f0a0..e3a6dd07b526 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -113,7 +113,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors(); method public static void resumeAppSwitches() throws android.os.RemoteException; method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setStopBackgroundUsersOnSwitch(int); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setStopUserOnSwitch(int); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean); method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle(); @@ -128,9 +128,9 @@ package android.app { field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0 field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4 field public static final int PROCESS_STATE_TOP = 2; // 0x2 - field public static final int STOP_BG_USERS_ON_SWITCH_DEFAULT = -1; // 0xffffffff - field public static final int STOP_BG_USERS_ON_SWITCH_FALSE = 0; // 0x0 - field public static final int STOP_BG_USERS_ON_SWITCH_TRUE = 1; // 0x1 + field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff + field public static final int STOP_USER_ON_SWITCH_FALSE = 0; // 0x0 + field public static final int STOP_USER_ON_SWITCH_TRUE = 1; // 0x1 } public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable { @@ -785,6 +785,7 @@ package android.content.pm { field public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 1.7777778f; field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f; + field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2 } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 50a562b66f16..f0deecac96c5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4081,45 +4081,46 @@ public class ActivityManager { * @hide */ @TestApi - public static final int STOP_BG_USERS_ON_SWITCH_DEFAULT = -1; + public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; /** - * Overrides value defined by the platform and stop background users on switch. + * Overrides value defined by the platform and stop user on switch. * * @hide */ @TestApi - public static final int STOP_BG_USERS_ON_SWITCH_TRUE = 1; + public static final int STOP_USER_ON_SWITCH_TRUE = 1; /** - * Overrides value defined by the platform and don't stop background users on switch. + * Overrides value defined by the platform and don't stop user on switch. * * @hide */ @TestApi - public static final int STOP_BG_USERS_ON_SWITCH_FALSE = 0; + public static final int STOP_USER_ON_SWITCH_FALSE = 0; /** @hide */ - @IntDef(prefix = { "STOP_BG_USERS_ON_SWITCH_" }, value = { - STOP_BG_USERS_ON_SWITCH_DEFAULT, - STOP_BG_USERS_ON_SWITCH_TRUE, - STOP_BG_USERS_ON_SWITCH_FALSE + @IntDef(prefix = { "STOP_USER_ON_SWITCH_" }, value = { + STOP_USER_ON_SWITCH_DEFAULT, + STOP_USER_ON_SWITCH_TRUE, + STOP_USER_ON_SWITCH_FALSE }) - public @interface StopBgUsersOnSwitch {} + public @interface StopUserOnSwitch {} /** - * Sets whether background users should be stopped when the current user is switched. + * Sets whether the current foreground user (and its profiles) should be stopped after switched + * out. * - * <p>Should only be used on tests. + * <p>Should only be used on tests. Doesn't apply to {@link UserHandle#SYSTEM system user}. * * @hide */ @TestApi @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) - public void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) { + public void setStopUserOnSwitch(@StopUserOnSwitch int value) { try { - getService().setStopBackgroundUsersOnSwitch(value); + getService().setStopUserOnSwitch(value); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index b416c95329b2..7be4c3e1465b 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -16,7 +16,7 @@ package android.app; -import static android.app.ActivityManager.StopBgUsersOnSwitch; +import static android.app.ActivityManager.StopUserOnSwitch; import android.annotation.NonNull; import android.annotation.Nullable; @@ -663,9 +663,10 @@ public abstract class ActivityManagerInternal { @Nullable VoiceInteractionManagerProvider provider); /** - * Sets whether background users should be stopped when the current user is switched. + * Sets whether the current foreground user (and its profiles) should be stopped after switched + * out. */ - public abstract void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value); + public abstract void setStopUserOnSwitch(@StopUserOnSwitch int value); /** * Provides the interface to communicate between voice interaction manager service and diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 81f0b4483806..431755e092e3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -29,7 +29,6 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE; import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS; import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets; import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; @@ -315,7 +314,7 @@ public final class ActivityThread extends ClientTransactionHandler @UnsupportedAppUsage private ContextImpl mSystemContext; - private final SparseArray<ContextImpl> mDisplaySystemUiContexts = new SparseArray<>(); + private ContextImpl mSystemUiContext; @UnsupportedAppUsage static volatile IPackageManager sPackageManager; @@ -2612,26 +2611,22 @@ public final class ActivityThread extends ClientTransactionHandler } @Override - @NonNull public ContextImpl getSystemUiContext() { - return getSystemUiContext(DEFAULT_DISPLAY); + synchronized (this) { + if (mSystemUiContext == null) { + mSystemUiContext = ContextImpl.createSystemUiContext(getSystemContext()); + } + return mSystemUiContext; + } } /** - * Gets the context instance base on system resources & display information which used for UI. + * Create the context instance base on system resources & display information which used for UI. * @param displayId The ID of the display where the UI is shown. * @see ContextImpl#createSystemUiContext(ContextImpl, int) */ - @NonNull - public ContextImpl getSystemUiContext(int displayId) { - synchronized (this) { - ContextImpl systemUiContext = mDisplaySystemUiContexts.get(displayId); - if (systemUiContext == null) { - systemUiContext = ContextImpl.createSystemUiContext(getSystemContext(), displayId); - mDisplaySystemUiContexts.put(displayId, systemUiContext); - } - return systemUiContext; - } + public ContextImpl createSystemUiContext(int displayId) { + return ContextImpl.createSystemUiContext(getSystemUiContext(), displayId); } public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { @@ -3750,7 +3745,7 @@ public final class ActivityThread extends ClientTransactionHandler if (pkgName != null && !pkgName.isEmpty() && r.packageInfo.mPackageName.contains(pkgName)) { for (int id : dm.getDisplayIds()) { - if (id != DEFAULT_DISPLAY) { + if (id != Display.DEFAULT_DISPLAY) { Display display = dm.getCompatibleDisplay(id, appContext.getResources()); appContext = (ContextImpl) appContext.createDisplayContext(display); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index ed496c61bddb..b5ed1717496e 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2618,10 +2618,7 @@ class ContextImpl extends Context { overrideConfig, display.getDisplayAdjustments().getCompatibilityInfo(), mResources.getLoaders())); context.mDisplay = display; - // Inherit context type if the container is from System or System UI context to bypass - // UI context check. - context.mContextType = mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI - ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI : CONTEXT_TYPE_DISPLAY_CONTEXT; + context.mContextType = CONTEXT_TYPE_DISPLAY_CONTEXT; // Display contexts and any context derived from a display context should always override // the display that would otherwise be inherited from mToken (or the global configuration if // mToken is null). @@ -2674,8 +2671,7 @@ class ContextImpl extends Context { // Step 2. Create the base context of the window context, it will also create a Resources // associated with the WindowTokenClient and set the token to the base context. - final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, - display.getDisplayId()); + final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, display); // Step 3. Create a WindowContext instance and set it as the outer context of the base // context to make the service obtained by #getSystemService(String) able to query @@ -2700,7 +2696,9 @@ class ContextImpl extends Context { if (display == null) { throw new IllegalArgumentException("Display must not be null"); } - return createWindowContextBase(token, display.getDisplayId()); + final ContextImpl tokenContext = createWindowContextBase(token, display); + tokenContext.setResources(createWindowContextResources(tokenContext)); + return tokenContext; } /** @@ -2708,13 +2706,13 @@ class ContextImpl extends Context { * window. * * @param token The token to associate with {@link Resources} - * @param displayId The ID of {@link Display} to associate with. + * @param display The {@link Display} to associate with. * * @see #createWindowContext(Display, int, Bundle) * @see #createTokenContext(IBinder, Display) */ @UiContext - ContextImpl createWindowContextBase(@NonNull IBinder token, int displayId) { + ContextImpl createWindowContextBase(@NonNull IBinder token, @NonNull Display display) { ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams, mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), @@ -2728,8 +2726,8 @@ class ContextImpl extends Context { baseContext.setResources(windowContextResources); // Associate the display with window context resources so that configuration update from // the server side will also apply to the display's metrics. - baseContext.mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId, - windowContextResources); + baseContext.mDisplay = ResourcesManager.getInstance() + .getAdjustedDisplay(display.getDisplayId(), windowContextResources); return baseContext; } @@ -2965,16 +2963,6 @@ class ContextImpl extends Context { mContentCaptureOptions = options; } - @Override - protected void finalize() throws Throwable { - // If token is a WindowTokenClient, the Context is usually associated with a - // WindowContainer. We should detach from WindowContainer when the Context is finalized. - if (mToken instanceof WindowTokenClient) { - ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded(); - } - super.finalize(); - } - @UnsupportedAppUsage static ContextImpl createSystemContext(ActivityThread mainThread) { LoadedApk packageInfo = new LoadedApk(mainThread); @@ -2995,15 +2983,24 @@ class ContextImpl extends Context { * @param displayId The ID of the display where the UI is shown. */ static ContextImpl createSystemUiContext(ContextImpl systemContext, int displayId) { - final WindowTokenClient token = new WindowTokenClient(); - ContextImpl context = systemContext.createWindowContextBase(token, displayId); - token.attachContext(context); - token.attachToDisplayContent(displayId); + final LoadedApk packageInfo = systemContext.mPackageInfo; + ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, + ContextParams.EMPTY, null, null, null, null, null, 0, null, null); + context.setResources(createResources(null, packageInfo, null, displayId, null, + packageInfo.getCompatibilityInfo(), null)); + context.updateDisplay(displayId); context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI; - return context; } + /** + * The overloaded method of {@link #createSystemUiContext(ContextImpl, int)}. + * Uses {@Code Display.DEFAULT_DISPLAY} as the target display. + */ + static ContextImpl createSystemUiContext(ContextImpl systemContext) { + return createSystemUiContext(systemContext, Display.DEFAULT_DISPLAY); + } + @UnsupportedAppUsage static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { return createAppContext(mainThread, packageInfo, null); @@ -3209,13 +3206,7 @@ class ContextImpl extends Context { @Override public IBinder getWindowContextToken() { - switch (mContextType) { - case CONTEXT_TYPE_WINDOW_CONTEXT: - case CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI: - return mToken; - default: - return null; - } + return mContextType == CONTEXT_TYPE_WINDOW_CONTEXT ? mToken : null; } private void checkMode(int mode) { diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 601ec9a152ca..0210a94eafcc 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -341,7 +341,7 @@ interface IActivityManager { @UnsupportedAppUsage boolean switchUser(int userid); @UnsupportedAppUsage - void setStopBackgroundUsersOnSwitch(int value); + void setStopUserOnSwitch(int value); boolean removeTask(int taskId); @UnsupportedAppUsage void registerProcessObserver(in IProcessObserver observer); diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 11c01e61911c..22b1c03fba50 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -240,6 +240,14 @@ public class WallpaperManager { */ public static final String EXTRA_NEW_WALLPAPER_ID = "android.service.wallpaper.extra.ID"; + /** + * Extra passed on {@link Intent.ACTION_WALLPAPER_CHANGED} indicating if wallpaper was set from + * a foreground app. + * @hide + */ + public static final String EXTRA_FROM_FOREGROUND_APP = + "android.service.wallpaper.extra.FROM_FOREGROUND_APP"; + // flags for which kind of wallpaper to act on /** @hide */ diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index a0d2977cf09a..01875eda2eca 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -16,6 +16,7 @@ package android.app.admin; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.ComponentName; @@ -77,6 +78,13 @@ public abstract class DevicePolicyManagerInternal { OnCrossProfileWidgetProvidersChangeListener listener); /** + * @param userHandle the handle of the user whose profile owner is being fetched. + * @return the configured supervision app if it exists and is the device owner or policy owner. + */ + public abstract @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent( + @NonNull UserHandle userHandle); + + /** * Checks if an app with given uid is an active device owner of its user. * * <p>This takes the DPMS lock. DO NOT call from PM/UM/AM with their lock held. diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index a02fdd303dd8..b5298fc9e1c7 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -995,10 +995,9 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM * OVERRIDE_MIN_ASPECT_RATIO_LARGE * - * If OVERRIDE_MIN_ASPECT_RATIO is applied, and the activity's orientation is fixed to - * portrait, the min aspect ratio given in the app's manifest will be overridden to the - * largest enabled aspect ratio treatment unless the app's manifest value is higher. - * TODO(b/203647190): add OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY instead of portrait by default + * If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's manifest + * will be overridden to the largest enabled aspect ratio treatment unless the app's manifest + * value is higher. * @hide */ @ChangeId @@ -1008,6 +1007,19 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // buganizer id /** + * 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. + * @hide + */ + @ChangeId + @Overridable + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S_V2) + @TestApi + public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // buganizer id + + /** * This change id sets the activity's min aspect ratio to a medium value as defined by * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE. * @@ -1336,9 +1348,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { */ @SizeChangesSupportMode public int supportsSizeChanges() { - if (CompatChanges.isChangeEnabled(FORCE_NON_RESIZE_APP, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(FORCE_NON_RESIZE_APP)) { return SIZE_CHANGES_UNSUPPORTED_OVERRIDE; } @@ -1346,9 +1356,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { return SIZE_CHANGES_SUPPORTED_METADATA; } - if (CompatChanges.isChangeEnabled(FORCE_RESIZE_APP, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(FORCE_RESIZE_APP)) { return SIZE_CHANGES_SUPPORTED_OVERRIDE; } @@ -1360,9 +1368,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @hide */ public boolean neverSandboxDisplayApis() { - return CompatChanges.isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid)) + return isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS) || ConstrainDisplayApisConfig.neverConstrainDisplayApis(applicationInfo); } @@ -1371,9 +1377,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @hide */ public boolean alwaysSandboxDisplayApis() { - return CompatChanges.isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid)) + return isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS) || ConstrainDisplayApisConfig.alwaysConstrainDisplayApis(applicationInfo); } @@ -1403,31 +1407,28 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @hide */ public float getMinAspectRatio(@ScreenOrientation int orientation) { - // TODO(b/203647190): check orientation only if OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY - // In case the activity's orientation isn't fixed to portrait, OVERRIDE_MIN_ASPECT_RATIO - // shouldn't be applied. - if (applicationInfo == null || !CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid)) - || !isFixedOrientationPortrait(orientation)) { + if (applicationInfo == null || !isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || ( + isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY) + && !isFixedOrientationPortrait(orientation))) { return mMinAspectRatio; } - if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) { return Math.max(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, mMinAspectRatio); } - if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) { return Math.max(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, mMinAspectRatio); } return mMinAspectRatio; } + private boolean isChangeEnabled(long changeId) { + return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName, + UserHandle.getUserHandleForUid(applicationInfo.uid)); + } + /** @hide */ public float getManifestMinAspectRatio() { return mMinAspectRatio; @@ -1495,9 +1496,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @hide */ public boolean shouldCheckMinWidthHeightForMultiWindow() { - return CompatChanges.isChangeEnabled(CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid)); + return isChangeEnabled(CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW); } public void dump(Printer pw, String prefix) { diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index c8c122da4ab8..6b5bec99e674 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -408,6 +408,19 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Flag to decide if authentication should ignore enrollment state. + * Defaults to false (not ignoring enrollment state) + * @param ignoreEnrollmentState + * @return This builder. + * @hide + */ + @NonNull + public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) { + mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState); + return this; + } + + /** * Creates a {@link BiometricPrompt}. * * @return An instance of {@link BiometricPrompt}. diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index 339c654f4d2f..e6b762a64384 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -45,6 +45,7 @@ public class PromptInfo implements Parcelable { private boolean mReceiveSystemEvents; @NonNull private List<Integer> mAllowedSensorIds = new ArrayList<>(); private boolean mAllowBackgroundAuthentication; + private boolean mIgnoreEnrollmentState; public PromptInfo() { @@ -66,6 +67,7 @@ public class PromptInfo implements Parcelable { mReceiveSystemEvents = in.readBoolean(); mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader()); mAllowBackgroundAuthentication = in.readBoolean(); + mIgnoreEnrollmentState = in.readBoolean(); } public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() { @@ -102,6 +104,7 @@ public class PromptInfo implements Parcelable { dest.writeBoolean(mReceiveSystemEvents); dest.writeList(mAllowedSensorIds); dest.writeBoolean(mAllowBackgroundAuthentication); + dest.writeBoolean(mIgnoreEnrollmentState); } public boolean containsTestConfigurations() { @@ -192,6 +195,10 @@ public class PromptInfo implements Parcelable { mAllowBackgroundAuthentication = allow; } + public void setIgnoreEnrollmentState(boolean ignoreEnrollmentState) { + mIgnoreEnrollmentState = ignoreEnrollmentState; + } + // Getters public CharSequence getTitle() { @@ -261,4 +268,8 @@ public class PromptInfo implements Parcelable { public boolean isAllowBackgroundAuthentication() { return mAllowBackgroundAuthentication; } + + public boolean isIgnoreEnrollmentState() { + return mIgnoreEnrollmentState; + } } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index a3d595c23095..fe04e5d35784 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -531,7 +531,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) { - authenticate(crypto, cancel, callback, handler, mContext.getUserId()); + authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, mContext.getUserId(), flags); } /** @@ -541,7 +541,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback, Handler handler, int userId) { - authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, userId); + authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, userId, 0 /* flags */); } /** @@ -550,7 +550,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing */ @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, - @NonNull AuthenticationCallback callback, Handler handler, int sensorId, int userId) { + @NonNull AuthenticationCallback callback, Handler handler, int sensorId, int userId, + int flags) { FrameworkStatsLog.write(FrameworkStatsLog.AUTH_DEPRECATED_API_USED, AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE, @@ -566,6 +567,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing return; } + final boolean ignoreEnrollmentState = flags == 0 ? false : true; + if (mService != null) { try { useHandler(handler); @@ -573,7 +576,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing mCryptoObject = crypto; final long operationId = crypto != null ? crypto.getOpId() : 0; final long authId = mService.authenticate(mToken, operationId, sensorId, userId, - mServiceReceiver, mContext.getOpPackageName()); + mServiceReceiver, mContext.getOpPackageName(), ignoreEnrollmentState); if (cancel != null) { cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId)); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index de94b2fbb5b5..ba1dc6da62a6 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -52,7 +52,8 @@ interface IFingerprintService { // permission. This is effectively deprecated, since it only comes through FingerprintManager // now. A requestId is returned that can be used to cancel this operation. long authenticate(IBinder token, long operationId, int sensorId, int userId, - IFingerprintServiceReceiver receiver, String opPackageName); + IFingerprintServiceReceiver receiver, String opPackageName, + boolean shouldIgnoreEnrollmentState); // Uses the fingerprint hardware to detect for the presence of a finger, without giving details // about accept/reject/lockout. A requestId is returned that can be used to cancel this diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 2d263a54b874..c77399f692f0 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1575,7 +1575,7 @@ public abstract class WallpaperService extends Service { void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages, float xOffsetStep) { // to save creating a runnable, check twice - long current = SystemClock.elapsedRealtime(); + long current = System.currentTimeMillis(); long lapsed = current - currentPage.getLastUpdateTime(); // Always update the page when the last update time is <= 0 // This is important especially when the device first boots @@ -1768,6 +1768,7 @@ public abstract class WallpaperService extends Service { return; } } + processLocalColors(mPendingXOffset, mPendingYOffset); } /** diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl index aca17e448b82..c7fd38092ec7 100644 --- a/core/java/android/view/IRecentsAnimationRunner.aidl +++ b/core/java/android/view/IRecentsAnimationRunner.aidl @@ -63,5 +63,5 @@ oneway interface IRecentsAnimationRunner { * Called when the task of an activity that has been started while the recents animation * was running becomes ready for control. */ - void onTaskAppeared(in RemoteAnimationTarget app) = 3; + void onTasksAppeared(in RemoteAnimationTarget[] app) = 3; } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index ae32a481691a..9e41e4d2906c 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -866,23 +866,6 @@ interface IWindowManager void attachWindowContextToWindowToken(IBinder clientToken, IBinder token); /** - * Attaches a {@code clientToken} to associate with DisplayContent. - * <p> - * Note that this API should be invoked after calling - * {@link android.window.WindowTokenClient#attachContext(Context)} - * </p> - * - * @param clientToken {@link android.window.WindowContext#getWindowContextToken() - * the WindowContext's token} - * @param displayId The display associated with the window context - * - * @return the DisplayContent's {@link android.app.res.Configuration} if the Context is - * attached to the DisplayContent successfully. {@code null}, otherwise. - * @throws android.view.WindowManager.InvalidDisplayException if the display ID is invalid - */ - Configuration attachToDisplayContent(IBinder clientToken, int displayId); - - /** * Detaches {@link android.window.WindowContext} from the window manager node it's currently * attached to. It is no-op if the WindowContext is not attached to a window manager node. * diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 51cd95e42742..8764ccc9d5f8 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2441,6 +2441,20 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000; /** + * Flag to indicate that this window will be excluded while computing the magnifiable region + * on the un-scaled screen coordinate, which could avoid the cutout on the magnification + * border. It should be used for unmagnifiable overlays. + * + * </p><p> + * Note unlike {@link #PRIVATE_FLAG_NOT_MAGNIFIABLE}, this flag doesn't affect the ability + * of magnification. If you want to the window to be unmagnifiable and doesn't lead to the + * cutout, you need to combine both of them. + * </p><p> + * @hide + */ + public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 0x00200000; + + /** * Flag to prevent the window from being magnified by the accessibility magnifier. * * TODO(b/190623172): This is a temporary solution and need to find out another way instead. @@ -2551,6 +2565,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE, SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, + PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, PRIVATE_FLAG_NOT_MAGNIFIABLE, PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION, PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, @@ -2632,6 +2647,10 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, name = "IS_ROUNDED_CORNERS_OVERLAY"), @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, + equals = PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, + name = "EXCLUDE_FROM_SCREEN_MAGNIFICATION"), + @ViewDebug.FlagToString( mask = PRIVATE_FLAG_NOT_MAGNIFIABLE, equals = PRIVATE_FLAG_NOT_MAGNIFIABLE, name = "NOT_MAGNIFIABLE"), diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 2357d13c8d41..f724285df9dc 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -5735,24 +5735,44 @@ public class RemoteViews implements Parcelable, Filter { return previousLayoutId == getLayoutId() && mViewId == overrideId; } - // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls - // should set it to false. - private void reapply(Context context, View v, InteractionHandler handler, SizeF size, - ColorResources colorResources, boolean topLevel) { - + /** + * Returns the RemoteViews that should be used in the reapply operation. + * + * If the current RemoteViews has multiple layout, this will select the correct one. + * + * @throws RuntimeException If the current RemoteViews should not be reapplied onto the provided + * View. + */ + private RemoteViews getRemoteViewsToReapply(Context context, View v, @Nullable SizeF size) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); // In the case that a view has this RemoteViews applied in one orientation or size, is // persisted across change, and has the RemoteViews re-applied in a different situation // (orientation or size), we throw an exception, since the layouts may be completely // unrelated. - if (hasMultipleLayouts()) { + // If the ViewID has been changed on the view, or is changed by the RemoteViews, we also + // may throw an exception, as the RemoteViews will probably not apply properly. + // However, we need to let potentially unrelated RemoteViews apply, as this lack of testing + // is already used in production code in some apps. + if (hasMultipleLayouts() + || rvToApply.mViewId != View.NO_ID + || v.getTag(R.id.remote_views_override_id) != null) { if (!rvToApply.canRecycleView(v)) { throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + " that does not share the same root layout id."); } } + return rvToApply; + } + + // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls + // should set it to false. + private void reapply(Context context, View v, InteractionHandler handler, SizeF size, + ColorResources colorResources, boolean topLevel) { + + RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); + rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources); // If the parent of the view is has is a root, resolve the recycling. @@ -5789,17 +5809,7 @@ public class RemoteViews implements Parcelable, Filter { public CancellationSignal reapplyAsync(Context context, View v, Executor executor, OnViewAppliedListener listener, InteractionHandler handler, SizeF size, ColorResources colorResources) { - RemoteViews rvToApply = getRemoteViewsToApply(context, size); - - // In the case that a view has this RemoteViews applied in one orientation, is persisted - // across orientation change, and has the RemoteViews re-applied in the new orientation, - // we throw an exception, since the layouts may be completely unrelated. - if (hasMultipleLayouts()) { - if (!rvToApply.canRecycleView(v)) { - throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + - " that does not share the same root layout id."); - } - } + RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), context, listener, handler, colorResources, v, true /* topLevel */) diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index 17b675f93f86..5aa623388574 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -19,10 +19,13 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteException; import android.view.IWindowManager; import android.view.WindowManager.LayoutParams.WindowType; +import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; @@ -35,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting; * @hide */ public class WindowContextController { + private final IWindowManager mWms; /** * {@code true} to indicate that the {@code mToken} is associated with a * {@link com.android.server.wm.DisplayArea}. Note that {@code mToken} is able to attach a @@ -52,7 +56,14 @@ public class WindowContextController { * {@link Context#getWindowContextToken()}. */ public WindowContextController(@NonNull WindowTokenClient token) { + this(token, WindowManagerGlobal.getWindowManagerService()); + } + + /** Used for test only. DO NOT USE it in production code. */ + @VisibleForTesting + public WindowContextController(@NonNull WindowTokenClient token, IWindowManager mockWms) { mToken = token; + mWms = mockWms; } /** @@ -69,7 +80,19 @@ public class WindowContextController { throw new IllegalStateException("A Window Context can be only attached to " + "a DisplayArea once."); } - mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options); + try { + final Configuration configuration = mWms.attachWindowContextToDisplayArea(mToken, type, + displayId, options); + if (configuration != null) { + mAttachedToDisplayArea = true; + // Send the DisplayArea's configuration to WindowContext directly instead of + // waiting for dispatching from WMS. + mToken.onConfigurationChanged(configuration, displayId, + false /* shouldReportConfigChange */); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -97,14 +120,22 @@ public class WindowContextController { throw new IllegalStateException("The Window Context should have been attached" + " to a DisplayArea."); } - mToken.attachToWindowToken(windowToken); + try { + mWms.attachWindowContextToWindowToken(mToken, windowToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** Detaches the window context from the node it's currently associated with. */ public void detachIfNeeded() { if (mAttachedToDisplayArea) { - mToken.detachFromWindowContainerIfNeeded(); - mAttachedToDisplayArea = false; + try { + mWms.detachWindowContextFromWindowContainer(mToken); + mAttachedToDisplayArea = false; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } } } diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index b331a9e81e27..f3e3859b4256 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -21,7 +21,6 @@ import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.ActivityThread; import android.app.IWindowToken; import android.app.ResourcesManager; @@ -32,11 +31,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.IBinder; -import android.os.RemoteException; import android.util.Log; -import android.view.IWindowManager; -import android.view.WindowManager.LayoutParams.WindowType; -import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; @@ -64,14 +59,10 @@ public class WindowTokenClient extends IWindowToken.Stub { private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); - private IWindowManager mWms; - private final Configuration mConfiguration = new Configuration(); private boolean mShouldDumpConfigForIme; - private boolean mAttachToWindowContainer; - /** * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient} * can only attach one {@link Context}. @@ -93,88 +84,6 @@ public class WindowTokenClient extends IWindowToken.Stub { } /** - * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}. - * - * @param type The window type of the {@link WindowContext} - * @param displayId The {@link Context#getDisplayId() ID of display} to associate with - * @param options The window context launched option - * @return {@code true} if attaching successfully. - */ - public boolean attachToDisplayArea(@WindowType int type, int displayId, - @Nullable Bundle options) { - try { - final Configuration configuration = getWindowManagerService() - .attachWindowContextToDisplayArea(this, type, displayId, options); - if (configuration == null) { - return false; - } - onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); - mAttachToWindowContainer = true; - return true; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}. - * - * @param displayId The {@link Context#getDisplayId() ID of display} to associate with - * @return {@code true} if attaching successfully. - */ - public boolean attachToDisplayContent(int displayId) { - final IWindowManager wms = getWindowManagerService(); - // #createSystemUiContext may call this method before WindowManagerService is initialized. - if (wms == null) { - return false; - } - try { - final Configuration configuration = wms.attachToDisplayContent(this, displayId); - if (configuration == null) { - return false; - } - onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); - mAttachToWindowContainer = true; - return true; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Attaches this {@link WindowTokenClient} to a {@code windowToken}. - * - * @param windowToken the window token to associated with - */ - public void attachToWindowToken(IBinder windowToken) { - try { - getWindowManagerService().attachWindowContextToWindowToken(this, windowToken); - mAttachToWindowContainer = true; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */ - public void detachFromWindowContainerIfNeeded() { - if (!mAttachToWindowContainer) { - return; - } - try { - getWindowManagerService().detachWindowContextFromWindowContainer(this); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - private IWindowManager getWindowManagerService() { - if (mWms == null) { - mWms = WindowManagerGlobal.getWindowManagerService(); - } - return mWms; - } - - /** * Called when {@link Configuration} updates from the server side receive. * * @param newConfig the updated {@link Configuration} diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 32db1866c151..90646a8674b6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1930,8 +1930,9 @@ STREAM_MUSIC as if it's on TV platform. --> <bool name="config_single_volume">false</bool> - <!-- Flag indicating whether the volume panel should show remote sessions. --> - <bool name="config_volumeShowRemoteSessions">true</bool> + <!-- Flag indicating whether platform level volume adjustments are enabled for remote sessions + on grouped devices. --> + <bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool> <!-- Flag indicating that an outbound call must have a call capable phone account that has declared it can process the call's handle. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 758990df7159..9bb92e6c93cc 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4444,7 +4444,7 @@ <java-symbol type="dimen" name="config_wallpaperDimAmount" /> - <java-symbol type="bool" name="config_volumeShowRemoteSessions" /> + <java-symbol type="bool" name="config_volumeAdjustmentForRemoteGroupSessions" /> <java-symbol type="integer" name="config_customizedMaxCachedProcesses" /> diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java index 52cb9f318dd0..a6e351d9cee7 100644 --- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java @@ -24,13 +24,16 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.content.res.Configuration; import android.os.Binder; import android.platform.test.annotations.Presubmit; +import android.view.IWindowManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -56,14 +59,17 @@ import org.mockito.MockitoAnnotations; public class WindowContextControllerTest { private WindowContextController mController; @Mock + private IWindowManager mMockWms; + @Mock private WindowTokenClient mMockToken; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mController = new WindowContextController(mMockToken); + mController = new WindowContextController(mMockToken, mMockWms); doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean()); - doReturn(true).when(mMockToken).attachToDisplayArea(anyInt(), anyInt(), any()); + doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(), + anyInt(), anyInt(), any()); } @Test(expected = IllegalStateException.class) @@ -75,10 +81,10 @@ public class WindowContextControllerTest { } @Test - public void testDetachIfNeeded_NotAttachedYet_DoNothing() { + public void testDetachIfNeeded_NotAttachedYet_DoNothing() throws Exception { mController.detachIfNeeded(); - verify(mMockToken, never()).detachFromWindowContainerIfNeeded(); + verify(mMockWms, never()).detachWindowContextFromWindowContainer(any()); } @Test @@ -87,6 +93,8 @@ public class WindowContextControllerTest { null /* options */); assertThat(mController.mAttachedToDisplayArea).isTrue(); + verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY), + eq(false) /* shouldReportConfigChange */); mController.detachIfNeeded(); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java index 1e9fda6599d5..44af1a9fd780 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -73,17 +73,55 @@ class SplitContainer { static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) { final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule; final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule) - && ((SplitPairRule) splitRule).shouldFinishPrimaryWithSecondary(); + && ((SplitPairRule) splitRule).getFinishPrimaryWithSecondary() + != SplitRule.FINISH_NEVER; return shouldFinishPrimaryWithSecondary || isPlaceholderContainer; } static boolean shouldFinishSecondaryWithPrimary(@NonNull SplitRule splitRule) { final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule; final boolean shouldFinishSecondaryWithPrimary = (splitRule instanceof SplitPairRule) - && ((SplitPairRule) splitRule).shouldFinishSecondaryWithPrimary(); + && ((SplitPairRule) splitRule).getFinishSecondaryWithPrimary() + != SplitRule.FINISH_NEVER; return shouldFinishSecondaryWithPrimary || isPlaceholderContainer; } + static boolean shouldFinishAssociatedContainerWhenStacked(int finishBehavior) { + return finishBehavior == SplitRule.FINISH_ALWAYS; + } + + static boolean shouldFinishAssociatedContainerWhenAdjacent(int finishBehavior) { + return finishBehavior == SplitRule.FINISH_ALWAYS + || finishBehavior == SplitRule.FINISH_ADJACENT; + } + + static int getFinishPrimaryWithSecondaryBehavior(@NonNull SplitRule splitRule) { + if (splitRule instanceof SplitPlaceholderRule) { + return ((SplitPlaceholderRule) splitRule).getFinishPrimaryWithSecondary(); + } + if (splitRule instanceof SplitPairRule) { + return ((SplitPairRule) splitRule).getFinishPrimaryWithSecondary(); + } + return SplitRule.FINISH_NEVER; + } + + static int getFinishSecondaryWithPrimaryBehavior(@NonNull SplitRule splitRule) { + if (splitRule instanceof SplitPlaceholderRule) { + return SplitRule.FINISH_ALWAYS; + } + if (splitRule instanceof SplitPairRule) { + return ((SplitPairRule) splitRule).getFinishSecondaryWithPrimary(); + } + return SplitRule.FINISH_NEVER; + } + + static boolean isStickyPlaceholderRule(@NonNull SplitRule splitRule) { + if (!(splitRule instanceof SplitPlaceholderRule)) { + return false; + } + return ((SplitPlaceholderRule) splitRule).isSticky(); + } + @Override public String toString() { return "SplitContainer{" 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 9014102d3f55..68c19041940c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -16,6 +16,12 @@ package androidx.window.extensions.embedding; +import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; +import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; +import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule; +import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent; +import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; @@ -460,6 +466,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return false; } + if (isStickyPlaceholderRule(splitContainer.getSplitRule())) { + // The placeholder should remain after it was first shown. + return false; + } + if (mPresenter.shouldShowSideBySide(splitContainer)) { return false; } @@ -643,6 +654,52 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return false; } + /** + * Checks whether the associated container should be destroyed together with a finishing + * container. There is a case when primary containers for placeholders should be retained + * despite the rule configuration to finish primary with secondary - if they are marked as + * 'sticky' and the placeholder was finished when fully overlapping the primary container. + * @return {@code true} if the associated container should be retained (and not be finished). + */ + boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer, + @NonNull TaskFragmentContainer associatedContainer) { + SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer, + finishingContainer); + if (splitContainer == null) { + // Containers are not in the same split, no need to retain. + return false; + } + // Find the finish behavior for the associated container + int finishBehavior; + SplitRule splitRule = splitContainer.getSplitRule(); + if (finishingContainer == splitContainer.getPrimaryContainer()) { + finishBehavior = getFinishSecondaryWithPrimaryBehavior(splitRule); + } else { + finishBehavior = getFinishPrimaryWithSecondaryBehavior(splitRule); + } + // Decide whether the associated container should be retained based on the current + // presentation mode. + if (mPresenter.shouldShowSideBySide(splitContainer)) { + return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior); + } else { + return !shouldFinishAssociatedContainerWhenStacked(finishBehavior); + } + } + + /** + * @see #shouldRetainAssociatedContainer(TaskFragmentContainer, TaskFragmentContainer) + */ + boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer, + @NonNull Activity associatedActivity) { + TaskFragmentContainer associatedContainer = getContainerWithActivity( + associatedActivity.getActivityToken()); + if (associatedContainer == null) { + return false; + } + + return shouldRetainAssociatedContainer(finishingContainer, associatedContainer); + } + private final class LifecycleCallbacks implements ActivityLifecycleCallbacks { @Override 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 6805fde685b0..a1a53bc93781 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -228,6 +228,9 @@ class TaskFragmentContainer { // Finish dependent containers for (TaskFragmentContainer container : mContainersToFinishOnExit) { + if (controller.shouldRetainAssociatedContainer(this, container)) { + continue; + } container.finish(true /* shouldFinishDependent */, presenter, wct, controller); } @@ -235,6 +238,9 @@ class TaskFragmentContainer { // Finish associated activities for (Activity activity : mActivitiesToFinishOnExit) { + if (controller.shouldRetainAssociatedActivity(this, activity)) { + continue; + } activity.finish(); } mActivitiesToFinishOnExit.clear(); diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex 830d13dd6dc5..d6678bf9b320 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/res/layout/pip_menu.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml index 9fe024748610..7dc2f31e9871 100644 --- a/libs/WindowManager/Shell/res/layout/pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/pip_menu.xml @@ -65,25 +65,28 @@ <LinearLayout android:id="@+id/top_end_container" android:layout_gravity="top|end" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> + <ImageButton android:id="@+id/settings" android:layout_width="@dimen/pip_action_size" android:layout_height="@dimen/pip_action_size" android:contentDescription="@string/pip_phone_settings" + android:layout_gravity="top|start" android:gravity="center" android:src="@drawable/pip_ic_settings" android:background="?android:selectableItemBackgroundBorderless" /> <ImageButton - android:id="@+id/dismiss" + android:id="@+id/enter_split" android:layout_width="@dimen/pip_action_size" android:layout_height="@dimen/pip_action_size" - android:contentDescription="@string/pip_phone_close" + android:layout_gravity="top|start" android:gravity="center" - android:src="@drawable/pip_ic_close_white" + android:contentDescription="@string/pip_phone_enter_split" + android:src="@drawable/pip_expand" android:background="?android:selectableItemBackgroundBorderless" /> </LinearLayout> @@ -97,4 +100,14 @@ android:padding="@dimen/pip_resize_handle_padding" android:src="@drawable/pip_resize_handle" android:background="?android:selectableItemBackgroundBorderless" /> + + <ImageButton + android:id="@+id/dismiss" + android:layout_width="@dimen/pip_action_size" + android:layout_height="@dimen/pip_action_size" + android:contentDescription="@string/pip_phone_close" + android:layout_gravity="top|end" + android:gravity="center" + android:src="@drawable/pip_ic_close_white" + android:background="?android:selectableItemBackgroundBorderless" /> </FrameLayout> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 764854af3b3f..c88fc16e218e 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -24,6 +24,9 @@ <!-- Label for PIP settings button [CHAR LIMIT=NONE]--> <string name="pip_phone_settings">Settings</string> + <!-- Label for the PIP enter split button [CHAR LIMIT=NONE] --> + <string name="pip_phone_enter_split">Enter split screen</string> + <!-- Title of menu shown over picture-in-picture. Used for accessibility. --> <string name="pip_menu_title">Menu</string> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index f7af4e1dd1d1..caa532761f1c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1036,10 +1036,9 @@ public class BubbleController { // notification, so that the bubble will be re-created if shouldBubbleUp returns // true. mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP); - } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble - && !entry.getRanking().isSuspended()) { + } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) { entry.setFlagBubble(true); - onEntryUpdated(entry, true /* shouldBubbleUp */); + onEntryUpdated(entry, shouldBubbleUp && !entry.getRanking().isSuspended()); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index b80dcd063589..711a0ac76702 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -43,6 +43,7 @@ import com.android.wm.shell.pip.tv.TvPipController; import com.android.wm.shell.pip.tv.TvPipMenuController; import com.android.wm.shell.pip.tv.TvPipNotificationController; import com.android.wm.shell.pip.tv.TvPipTransition; +import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; import java.util.Optional; @@ -160,13 +161,14 @@ public abstract class TvPipModule { PipTransitionController pipTransitionController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<LegacySplitScreenController> splitScreenOptional, + Optional<SplitScreenController> newSplitScreenOptional, DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { return new PipTaskOrganizer(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm, tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper, - pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger, - shellTaskOrganizer, mainExecutor); + pipTransitionController, splitScreenOptional, newSplitScreenOptional, + displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 944dfed57cb6..ec701470354c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -55,6 +55,7 @@ import com.android.wm.shell.pip.phone.PipAppOpsListener; import com.android.wm.shell.pip.phone.PipController; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.pip.phone.PipTouchHandler; +import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm; import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm; import com.android.wm.shell.transition.Transitions; @@ -215,14 +216,15 @@ public class WMShellModule { PipSurfaceTransactionHelper pipSurfaceTransactionHelper, PipTransitionController pipTransitionController, Optional<LegacySplitScreenController> splitScreenOptional, + Optional<SplitScreenController> newSplitScreenOptional, DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { return new PipTaskOrganizer(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm, menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper, - pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger, - shellTaskOrganizer, mainExecutor); + pipTransitionController, splitScreenOptional, newSplitScreenOptional, + displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index b6e5804a64dd..6cc5f09827af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -77,6 +77,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PipMotionHelper; +import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; @@ -126,7 +127,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private final int mExitAnimationDuration; private final int mCrossFadeAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; - private final Optional<LegacySplitScreenController> mSplitScreenOptional; + private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional; + private final Optional<SplitScreenController> mSplitScreenOptional; protected final ShellTaskOrganizer mTaskOrganizer; protected final ShellExecutor mMainExecutor; @@ -252,7 +254,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @NonNull PipAnimationController pipAnimationController, @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper, @NonNull PipTransitionController pipTransitionController, - Optional<LegacySplitScreenController> splitScreenOptional, + Optional<LegacySplitScreenController> legacySplitScreenOptional, + Optional<SplitScreenController> splitScreenOptional, @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, @NonNull ShellTaskOrganizer shellTaskOrganizer, @@ -274,6 +277,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipAnimationController = pipAnimationController; mPipUiEventLoggerLogger = pipUiEventLogger; mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; + mLegacySplitScreenOptional = legacySplitScreenOptional; mSplitScreenOptional = splitScreenOptional; mTaskOrganizer = shellTaskOrganizer; mMainExecutor = mainExecutor; @@ -373,8 +377,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, * activity render it's final configuration while the Task is still in PiP. * - setWindowingMode to undefined at the end of transition * @param animationDurationMs duration in millisecond for the exiting PiP transition + * @param requestEnterSplit whether the enterSplit button is pressed on PiP or not. + * Indicate the user wishes to directly put PiP into split screen + * mode. */ - public void exitPip(int animationDurationMs) { + public void exitPip(int animationDurationMs, boolean requestEnterSplit) { if (!mPipTransitionState.isInPip() || mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP || mToken == null) { @@ -387,7 +394,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN); final WindowContainerTransaction wct = new WindowContainerTransaction(); final Rect destinationBounds = mPipBoundsState.getDisplayBounds(); - final int direction = syncWithSplitScreenBounds(destinationBounds) + final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit) ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN : TRANSITION_DIRECTION_LEAVE_PIP; final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); @@ -396,7 +403,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // We set to fullscreen here for now, but later it will be set to UNDEFINED for // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit. wct.setActivityWindowingMode(mToken, - direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN + direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && !requestEnterSplit ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_FULLSCREEN); wct.setBounds(mToken, destinationBounds); @@ -435,7 +442,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, wct.setWindowingMode(mToken, getOutPipWindowingMode()); // Simply reset the activity mode set prior to the animation running. wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); - mSplitScreenOptional.ifPresent(splitScreen -> { + mLegacySplitScreenOptional.ifPresent(splitScreen -> { if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { wct.reparent(mToken, splitScreen.getSecondaryRoot(), true /* onTop */); } @@ -1165,6 +1172,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @PipAnimationController.TransitionDirection int direction, @PipAnimationController.AnimationType int type) { final Rect preResizeBounds = new Rect(mPipBoundsState.getBounds()); + final boolean isPipTopLeft = isPipTopLeft(); mPipBoundsState.setBounds(destinationBounds); if (direction == TRANSITION_DIRECTION_REMOVE_STACK) { removePipImmediately(); @@ -1210,10 +1218,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, null /* callback */, false /* withStartDelay */); }); } else { - applyFinishBoundsResize(wct, direction); + applyFinishBoundsResize(wct, direction, isPipTopLeft); } } else { - applyFinishBoundsResize(wct, direction); + applyFinishBoundsResize(wct, direction, isPipTopLeft); } finishResizeForMenu(destinationBounds); @@ -1241,7 +1249,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } else if (isOutPipDirection(direction)) { // If we are animating to fullscreen or split screen, then we need to reset the // override bounds on the task to ensure that the task "matches" the parent's bounds. - taskBounds = null; + if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { + taskBounds = destinationBounds; + } else { + taskBounds = null; + } applyWindowingModeChangeOnExit(wct, direction); } else { // Just a resize in PIP @@ -1261,8 +1273,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, * applying it. */ public void applyFinishBoundsResize(@NonNull WindowContainerTransaction wct, - @PipAnimationController.TransitionDirection int direction) { - mTaskOrganizer.applyTransaction(wct); + @PipAnimationController.TransitionDirection int direction, boolean wasPipTopLeft) { + if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { + mSplitScreenOptional.get().enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct); + } else { + mTaskOrganizer.applyTransaction(wct); + } + } + + private boolean isPipTopLeft() { + final Rect topLeft = new Rect(); + final Rect bottomRight = new Rect(); + mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight); + + return topLeft.contains(mPipBoundsState.getBounds()); } /** @@ -1347,18 +1371,27 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** - * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split - * screen. + * Sync with {@link LegacySplitScreenController} or {@link SplitScreenController} on destination + * bounds if PiP is going to split screen. * * @param destinationBoundsOut contain the updated destination bounds if applicable * @return {@code true} if destinationBounds is altered for split screen */ - private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut) { - if (!mSplitScreenOptional.isPresent()) { + private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit) { + if (enterSplit && mSplitScreenOptional.isPresent()) { + final Rect topLeft = new Rect(); + final Rect bottomRight = new Rect(); + mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight); + final boolean isPipTopLeft = isPipTopLeft(); + destinationBoundsOut.set(isPipTopLeft ? topLeft : bottomRight); + return true; + } + + if (!mLegacySplitScreenOptional.isPresent()) { return false; } - LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.get(); + LegacySplitScreenController legacySplitScreen = mLegacySplitScreenOptional.get(); if (!legacySplitScreen.isDividerVisible()) { // fail early if system is not in split screen mode return false; 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 ae8c1b6f8c1a..5687f4d62444 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 @@ -95,6 +95,11 @@ public class PhonePipMenuController implements PipMenuController { * Called when the PIP requested to show the menu. */ void onPipShowMenu(); + + /** + * Called when the PIP requested to enter Split. + */ + void onEnterSplit(); } private final Matrix mMoveTransform = new Matrix(); @@ -458,6 +463,10 @@ public class PhonePipMenuController implements PipMenuController { mListeners.forEach(Listener::onPipDismiss); } + void onEnterSplit() { + mListeners.forEach(Listener::onEnterSplit); + } + /** * @return the best set of actions to show in the PiP menu. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java index 47a8c67a22e6..69ae45d12795 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java @@ -151,7 +151,7 @@ public class PipAccessibilityInteractionConnection { result = true; break; case AccessibilityNodeInfo.ACTION_EXPAND: - mMotionHelper.expandLeavePip(); + mMotionHelper.expandLeavePip(false /* skipAnimation */); result = true; break; default: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 8c431f08a385..10bc7e250cc8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -482,7 +482,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb false /* fromShelfAdjustment */, wct /* windowContainerTransaction */); if (wct != null) { - mPipTaskOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_SAME); + mPipTaskOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_SAME, + false /* wasPipTopLeft */); } }; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java index 3eeba6eb5366..06446573840c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java @@ -18,8 +18,6 @@ package com.android.wm.shell.pip.phone; import android.content.Context; import android.graphics.Rect; -import android.util.Log; -import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -34,6 +32,7 @@ public class PipMenuIconsAlgorithm { protected ViewGroup mViewRoot; protected ViewGroup mTopEndContainer; protected View mDragHandle; + protected View mEnterSplitButton; protected View mSettingsButton; protected View mDismissButton; @@ -44,14 +43,13 @@ public class PipMenuIconsAlgorithm { * Bind the necessary views. */ public void bindViews(ViewGroup viewRoot, ViewGroup topEndContainer, View dragHandle, - View settingsButton, View dismissButton) { + View enterSplitButton, View settingsButton, View dismissButton) { mViewRoot = viewRoot; mTopEndContainer = topEndContainer; mDragHandle = dragHandle; + mEnterSplitButton = enterSplitButton; mSettingsButton = settingsButton; mDismissButton = dismissButton; - - bindInitialViewState(); } /** @@ -72,22 +70,4 @@ public class PipMenuIconsAlgorithm { v.setLayoutParams(params); } } - - /** Calculate the initial state of the menu icons. Called when the menu is first created. */ - private void bindInitialViewState() { - if (mViewRoot == null || mTopEndContainer == null || mDragHandle == null - || mSettingsButton == null || mDismissButton == null) { - Log.e(TAG, "One of the required views is null."); - return; - } - // The menu view layout starts out with the settings button aligned at the top|end of the - // view group next to the dismiss button. On phones, the settings button should be aligned - // to the top|start of the view, so move it to parent view group to then align it to the - // top|start of the menu. - mTopEndContainer.removeView(mSettingsButton); - mViewRoot.addView(mSettingsButton); - - setLayoutGravity(mDragHandle, Gravity.START | Gravity.TOP); - setLayoutGravity(mSettingsButton, Gravity.START | Gravity.TOP); - } } 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 8ef2b6b12030..7bbebe5bf287 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 @@ -99,7 +99,7 @@ public class PipMenuView extends FrameLayout { private static final float MENU_BACKGROUND_ALPHA = 0.3f; private static final float DISABLED_ACTION_ALPHA = 0.54f; - private static final boolean ENABLE_RESIZE_HANDLE = false; + private static final boolean ENABLE_ENTER_SPLIT = false; private int mMenuState; private boolean mAllowMenuTimeout = true; @@ -139,7 +139,7 @@ public class PipMenuView extends FrameLayout { protected View mViewRoot; protected View mSettingsButton; protected View mDismissButton; - protected View mResizeHandle; + protected View mEnterSplitButton; protected View mTopEndContainer; protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm; @@ -177,14 +177,23 @@ public class PipMenuView extends FrameLayout { } }); - mResizeHandle = findViewById(R.id.resize_handle); - mResizeHandle.setAlpha(0); + mEnterSplitButton = findViewById(R.id.enter_split); + mEnterSplitButton.setAlpha(0); + mEnterSplitButton.setOnClickListener(v -> { + if (mMenuContainer.getAlpha() != 0) { + enterSplit(); + } + }); + + findViewById(R.id.resize_handle).setAlpha(0); + mActionsGroup = findViewById(R.id.actions_group); mBetweenActionPaddingLand = getResources().getDimensionPixelSize( R.dimen.pip_between_action_padding_land); mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext); mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer, - mResizeHandle, mSettingsButton, mDismissButton); + findViewById(R.id.resize_handle), mEnterSplitButton, mSettingsButton, + mDismissButton); mDismissFadeOutDurationMs = context.getResources() .getInteger(R.integer.config_pipExitAnimationDuration); @@ -268,14 +277,13 @@ public class PipMenuView extends FrameLayout { mSettingsButton.getAlpha(), 1f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 1f); - ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA, - mResizeHandle.getAlpha(), - ENABLE_RESIZE_HANDLE && showResizeHandle ? 1f : 0f); + ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, + mEnterSplitButton.getAlpha(), ENABLE_ENTER_SPLIT ? 1f : 0f); if (menuState == MENU_STATE_FULL) { mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, - resizeAnim); + enterSplitAnim); } else { - mMenuContainerAnimator.playTogether(resizeAnim); + mMenuContainerAnimator.playTogether(enterSplitAnim); } mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS); @@ -328,7 +336,7 @@ public class PipMenuView extends FrameLayout { mMenuContainer.setAlpha(0f); mSettingsButton.setAlpha(0f); mDismissButton.setAlpha(0f); - mResizeHandle.setAlpha(0f); + mEnterSplitButton.setAlpha(0f); } void pokeMenu() { @@ -368,9 +376,10 @@ public class PipMenuView extends FrameLayout { mSettingsButton.getAlpha(), 0f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 0f); - ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA, - mResizeHandle.getAlpha(), 0f); - mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim); + ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, + mEnterSplitButton.getAlpha(), 0f); + mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, + enterSplitAnim); mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT); mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType)); mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { @@ -522,6 +531,14 @@ public class PipMenuView extends FrameLayout { } } + private void enterSplit() { + // Do not notify menu visibility when hiding the menu, the controller will do this when it + // handles the message + hideMenu(mController::onEnterSplit, false /* notifyMenuVisibility */, true /* resize */, + ANIM_TYPE_HIDE); + } + + private void showSettings() { final Pair<ComponentName, Integer> topPipActivityInfo = PipUtils.getTopPipActivity(mContext); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index dbd09fd7b265..c634b7f220b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -338,22 +338,29 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Resizes the pinned stack back to unknown windowing mode, which could be freeform or * * fullscreen depending on the display area's windowing mode. */ - void expandLeavePip() { - expandLeavePip(false /* skipAnimation */); + void expandLeavePip(boolean skipAnimation) { + expandLeavePip(skipAnimation, false /* enterSplit */); + } + + /** + * Resizes the pinned task to split-screen mode. + */ + void expandIntoSplit() { + expandLeavePip(false, true /* enterSplit */); } /** * Resizes the pinned stack back to unknown windowing mode, which could be freeform or * fullscreen depending on the display area's windowing mode. */ - void expandLeavePip(boolean skipAnimation) { + private void expandLeavePip(boolean skipAnimation, boolean enterSplit) { if (DEBUG) { Log.d(TAG, "exitPip: skipAnimation=" + skipAnimation + " callers=\n" + Debug.getCallers(5, " ")); } cancelPhysicsAnimation(); mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */); - mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION); + mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION, enterSplit); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 9f2f6a575aca..570fd5eab9f6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -139,7 +139,12 @@ public class PipTouchHandler { @Override public void onPipExpand() { - mMotionHelper.expandLeavePip(); + mMotionHelper.expandLeavePip(false /* skipAnimation */); + } + + @Override + public void onEnterSplit() { + mMotionHelper.expandIntoSplit(); } @Override @@ -899,7 +904,7 @@ public class PipTouchHandler { // Expand to fullscreen if this is a double tap // the PiP should be frozen until the transition ends setTouchEnabled(false); - mMotionHelper.expandLeavePip(); + mMotionHelper.expandLeavePip(false /* skipAnimation */); } } else if (mMenuState != MENU_STATE_FULL) { if (mPipBoundsState.isStashed()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index a2e9b64046fd..00083d986dbe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -219,7 +219,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public void movePipToFullscreen() { if (DEBUG) Log.d(TAG, "movePipToFullscreen(), state=" + stateToName(mState)); - mPipTaskOrganizer.exitPip(mResizeAnimationDuration); + mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */); onPipDisappeared(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 836a6f610bbd..7cf3bafe499a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -41,10 +41,13 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.util.GroupedRecentTaskInfo; +import com.android.wm.shell.util.StagedSplitBounds; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Manages the recent task list from the system, caching it as necessary. @@ -62,6 +65,13 @@ public class RecentTasksController implements TaskStackListenerCallback, // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1) private final SparseIntArray mSplitTasks = new SparseIntArray(); + /** + * Maps taskId to {@link StagedSplitBounds} for both taskIDs. + * Meaning there will be two taskId integers mapping to the same object. + * If there's any ordering to the pairing than we can probably just get away with only one + * taskID mapping to it, leaving both for consistency with {@link #mSplitTasks} for now. + */ + private final Map<Integer, StagedSplitBounds> mTaskSplitBoundsMap = new HashMap<>(); /** * Creates {@link RecentTasksController}, returns {@code null} if the feature is not @@ -97,15 +107,20 @@ public class RecentTasksController implements TaskStackListenerCallback, /** * Adds a split pair. This call does not validate the taskIds, only that they are not the same. */ - public void addSplitPair(int taskId1, int taskId2) { + public void addSplitPair(int taskId1, int taskId2, StagedSplitBounds splitBounds) { if (taskId1 == taskId2) { return; } // Remove any previous pairs removeSplitPair(taskId1); removeSplitPair(taskId2); + mTaskSplitBoundsMap.remove(taskId1); + mTaskSplitBoundsMap.remove(taskId2); + mSplitTasks.put(taskId1, taskId2); mSplitTasks.put(taskId2, taskId1); + mTaskSplitBoundsMap.put(taskId1, splitBounds); + mTaskSplitBoundsMap.put(taskId2, splitBounds); } /** @@ -116,6 +131,8 @@ public class RecentTasksController implements TaskStackListenerCallback, if (pairedTaskId != INVALID_TASK_ID) { mSplitTasks.delete(taskId); mSplitTasks.delete(pairedTaskId); + mTaskSplitBoundsMap.remove(taskId); + mTaskSplitBoundsMap.remove(pairedTaskId); } } @@ -203,7 +220,8 @@ public class RecentTasksController implements TaskStackListenerCallback, if (pairedTaskId != INVALID_TASK_ID) { final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); rawMapping.remove(pairedTaskId); - recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo)); + recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo, + mTaskSplitBoundsMap.get(pairedTaskId))); } else { recentTasks.add(new GroupedRecentTaskInfo(taskInfo)); } 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 04058ed6388c..7457be2d0871 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 @@ -202,11 +202,25 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return moveToSideStage(task, sideStagePosition); } + public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition, + WindowContainerTransaction wct) { + final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); + if (task == null) { + throw new IllegalArgumentException("Unknown taskId" + taskId); + } + return moveToSideStage(task, sideStagePosition, wct); + } + public boolean moveToSideStage(ActivityManager.RunningTaskInfo task, @SplitPosition int sideStagePosition) { return mStageCoordinator.moveToSideStage(task, sideStagePosition); } + public boolean moveToSideStage(ActivityManager.RunningTaskInfo task, + @SplitPosition int sideStagePosition, WindowContainerTransaction wct) { + return mStageCoordinator.moveToSideStage(task, sideStagePosition, wct); + } + public boolean removeFromSideStage(int taskId) { return mStageCoordinator.removeFromSideStage(taskId); } @@ -224,6 +238,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT); } + public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) { + moveToSideStage(taskId, + leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); + } + public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) { mStageCoordinator.exitSplitScreen(toTopTaskId, 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 8471e1e58109..95886c8f3deb 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 @@ -96,6 +96,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.StagedSplitBounds; import java.io.PrintWriter; import java.util.ArrayList; @@ -280,6 +281,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, boolean moveToSideStage(ActivityManager.RunningTaskInfo task, @SplitPosition int sideStagePosition) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + return moveToSideStage(task, sideStagePosition, wct); + } + + boolean moveToSideStage(ActivityManager.RunningTaskInfo task, + @SplitPosition int sideStagePosition, WindowContainerTransaction wct) { final WindowContainerTransaction evictWct = new WindowContainerTransaction(); setSideStagePosition(sideStagePosition, wct); mSideStage.evictAllChildren(evictWct); @@ -691,11 +697,25 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } mRecentTasks.ifPresent(recentTasks -> { + Rect topLeftBounds = mSplitLayout.getBounds1(); + Rect bottomRightBounds = mSplitLayout.getBounds2(); int mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId(); int sideStageTopTaskId = mSideStage.getTopVisibleChildTaskId(); + boolean sideStageTopLeft = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; + int leftTopTaskId; + int rightBottomTaskId; + if (sideStageTopLeft) { + leftTopTaskId = sideStageTopTaskId; + rightBottomTaskId = mainStageTopTaskId; + } else { + leftTopTaskId = mainStageTopTaskId; + rightBottomTaskId = sideStageTopTaskId; + } + StagedSplitBounds splitBounds = new StagedSplitBounds(topLeftBounds, bottomRightBounds, + leftTopTaskId, rightBottomTaskId); if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) { // Update the pair for the top tasks - recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId); + recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds); } }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java index 0331ba19defe..603d05d78fc0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java @@ -30,25 +30,34 @@ import androidx.annotation.Nullable; public class GroupedRecentTaskInfo implements Parcelable { public @NonNull ActivityManager.RecentTaskInfo mTaskInfo1; public @Nullable ActivityManager.RecentTaskInfo mTaskInfo2; + public @Nullable StagedSplitBounds mStagedSplitBounds; public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1) { - this(task1, null); + this(task1, null, null); } public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1, - @Nullable ActivityManager.RecentTaskInfo task2) { + @Nullable ActivityManager.RecentTaskInfo task2, + @Nullable StagedSplitBounds stagedSplitBounds) { mTaskInfo1 = task1; mTaskInfo2 = task2; + mStagedSplitBounds = stagedSplitBounds; } GroupedRecentTaskInfo(Parcel parcel) { mTaskInfo1 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); mTaskInfo2 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); + mStagedSplitBounds = parcel.readTypedObject(StagedSplitBounds.CREATOR); } @Override public String toString() { - return "Task1: " + getTaskInfo(mTaskInfo1) + ", Task2: " + getTaskInfo(mTaskInfo2); + String taskString = "Task1: " + getTaskInfo(mTaskInfo1) + + ", Task2: " + getTaskInfo(mTaskInfo2); + if (mStagedSplitBounds != null) { + taskString += ", SplitBounds: " + mStagedSplitBounds.toString(); + } + return taskString; } private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) { @@ -67,6 +76,7 @@ public class GroupedRecentTaskInfo implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeTypedObject(mTaskInfo1, flags); parcel.writeTypedObject(mTaskInfo2, flags); + parcel.writeTypedObject(mStagedSplitBounds, flags); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java new file mode 100644 index 000000000000..aadf792c572f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java @@ -0,0 +1,114 @@ +/* + * 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.wm.shell.util; + +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Container of various information needed to display split screen + * tasks/leashes/etc in Launcher + */ +public class StagedSplitBounds implements Parcelable { + public final Rect leftTopBounds; + public final Rect rightBottomBounds; + /** This rect represents the actual gap between the two apps */ + public final Rect visualDividerBounds; + // This class is orientation-agnostic, so we compute both for later use + public final float topTaskPercent; + public final float leftTaskPercent; + /** + * If {@code true}, that means at the time of creation of this object, the + * split-screened apps were vertically stacked. This is useful in scenarios like + * rotation where the bounds won't change, but this variable can indicate what orientation + * the bounds were originally in + */ + public final boolean appsStackedVertically; + public final int leftTopTaskId; + public final int rightBottomTaskId; + + public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds, + int leftTopTaskId, int rightBottomTaskId) { + this.leftTopBounds = leftTopBounds; + this.rightBottomBounds = rightBottomBounds; + this.leftTopTaskId = leftTopTaskId; + this.rightBottomTaskId = rightBottomTaskId; + + if (rightBottomBounds.top > leftTopBounds.top) { + // vertical apps, horizontal divider + this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom, + leftTopBounds.right, rightBottomBounds.top); + appsStackedVertically = true; + } else { + // horizontal apps, vertical divider + this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top, + rightBottomBounds.left, leftTopBounds.bottom); + appsStackedVertically = false; + } + + leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right; + topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom; + } + + public StagedSplitBounds(Parcel parcel) { + leftTopBounds = parcel.readTypedObject(Rect.CREATOR); + rightBottomBounds = parcel.readTypedObject(Rect.CREATOR); + visualDividerBounds = parcel.readTypedObject(Rect.CREATOR); + topTaskPercent = parcel.readFloat(); + leftTaskPercent = parcel.readFloat(); + appsStackedVertically = parcel.readBoolean(); + leftTopTaskId = parcel.readInt(); + rightBottomTaskId = parcel.readInt(); + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeTypedObject(leftTopBounds, flags); + parcel.writeTypedObject(rightBottomBounds, flags); + parcel.writeTypedObject(visualDividerBounds, flags); + parcel.writeFloat(topTaskPercent); + parcel.writeFloat(leftTaskPercent); + parcel.writeBoolean(appsStackedVertically); + parcel.writeInt(leftTopTaskId); + parcel.writeInt(rightBottomTaskId); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n" + + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n" + + "Divider: " + visualDividerBounds + "\n" + + "AppsVertical? " + appsStackedVertically; + } + + public static final Creator<StagedSplitBounds> CREATOR = new Creator<StagedSplitBounds>() { + @Override + public StagedSplitBounds createFromParcel(Parcel in) { + return new StagedSplitBounds(in); + } + + @Override + public StagedSplitBounds[] newArray(int size) { + return new StagedSplitBounds[size]; + } + }; +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 0270093da938..0172cf324eea 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -50,6 +50,7 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PhonePipMenuController; +import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; import org.junit.Test; @@ -75,7 +76,8 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Mock private PipTransitionController mMockPipTransitionController; @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper; @Mock private PipUiEventLogger mMockPipUiEventLogger; - @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen; + @Mock private Optional<LegacySplitScreenController> mMockOptionalLegacySplitScreen; + @Mock private Optional<SplitScreenController> mMockOptionalSplitScreen; @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; private TestShellExecutor mMainExecutor; private PipBoundsState mPipBoundsState; @@ -99,8 +101,9 @@ public class PipTaskOrganizerTest extends ShellTestCase { mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState, mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController, mMockPipSurfaceTransactionHelper, - mMockPipTransitionController, mMockOptionalSplitScreen, mMockDisplayController, - mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor)); + mMockPipTransitionController, mMockOptionalLegacySplitScreen, + mMockOptionalSplitScreen, mMockDisplayController, mMockPipUiEventLogger, + mMockShellTaskOrganizer, mMainExecutor)); mMainExecutor.flushAll(); preparePipTaskOrg(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index a1e12319ac70..19a5417aace6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -20,6 +20,8 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; @@ -31,10 +33,9 @@ import static org.mockito.Mockito.verify; import static java.lang.Integer.MAX_VALUE; import android.app.ActivityManager; -import android.app.WindowConfiguration; import android.content.Context; +import android.graphics.Rect; import android.view.SurfaceControl; -import android.window.TaskAppearedInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -45,6 +46,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.util.GroupedRecentTaskInfo; +import com.android.wm.shell.util.StagedSplitBounds; import org.junit.Before; import org.junit.Test; @@ -106,8 +108,11 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] - mRecentTasksController.addSplitPair(t2.taskId, t4.taskId); - mRecentTasksController.addSplitPair(t3.taskId, t5.taskId); + StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 4); + StagedSplitBounds pair2Bounds = new StagedSplitBounds(new Rect(), new Rect(), 3, 5); + + mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); + mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -126,7 +131,8 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3); // Add a pair - mRecentTasksController.addSplitPair(t2.taskId, t3.taskId); + StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 3); + mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds); reset(mRecentTasksController); // Remove one of the tasks and ensure the pair is removed @@ -201,10 +207,23 @@ public class RecentTasksControllerTest extends ShellTestCase { int[] flattenedTaskIds = new int[recentTasks.size() * 2]; for (int i = 0; i < recentTasks.size(); i++) { GroupedRecentTaskInfo pair = recentTasks.get(i); - flattenedTaskIds[2 * i] = pair.mTaskInfo1.taskId; + int taskId1 = pair.mTaskInfo1.taskId; + flattenedTaskIds[2 * i] = taskId1; flattenedTaskIds[2 * i + 1] = pair.mTaskInfo2 != null ? pair.mTaskInfo2.taskId : -1; + + if (pair.mTaskInfo2 != null) { + assertNotNull(pair.mStagedSplitBounds); + int leftTopTaskId = pair.mStagedSplitBounds.leftTopTaskId; + int bottomRightTaskId = pair.mStagedSplitBounds.rightBottomTaskId; + // Unclear if pairs are ordered by split position, most likely not. + assertTrue(leftTopTaskId == taskId1 || leftTopTaskId == pair.mTaskInfo2.taskId); + assertTrue(bottomRightTaskId == taskId1 + || bottomRightTaskId == pair.mTaskInfo2.taskId); + } else { + assertNull(pair.mStagedSplitBounds); + } } assertTrue("Expected: " + Arrays.toString(expectedTaskIds) + " Received: " + Arrays.toString(flattenedTaskIds), diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java new file mode 100644 index 000000000000..ad73c56950bd --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java @@ -0,0 +1,94 @@ +package com.android.wm.shell.recents; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.graphics.Rect; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.wm.shell.util.StagedSplitBounds; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StagedSplitBoundsTest { + private static final int DEVICE_WIDTH = 100; + private static final int DEVICE_LENGTH = 200; + private static final int DIVIDER_SIZE = 20; + private static final int TASK_ID_1 = 4; + private static final int TASK_ID_2 = 9; + + // Bounds in screen space + private final Rect mTopRect = new Rect(); + private final Rect mBottomRect = new Rect(); + private final Rect mLeftRect = new Rect(); + private final Rect mRightRect = new Rect(); + + @Before + public void setup() { + mTopRect.set(0, 0, DEVICE_WIDTH, DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2); + mBottomRect.set(0, DEVICE_LENGTH / 2 + DIVIDER_SIZE / 2, + DEVICE_WIDTH, DEVICE_LENGTH); + mLeftRect.set(0, 0, DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, DEVICE_LENGTH); + mRightRect.set(DEVICE_WIDTH / 2 + DIVIDER_SIZE / 2, 0, + DEVICE_WIDTH, DEVICE_LENGTH); + } + + @Test + public void testVerticalStacked() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + assertTrue(ssb.appsStackedVertically); + } + + @Test + public void testHorizontalStacked() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + assertFalse(ssb.appsStackedVertically); + } + + @Test + public void testHorizontalDividerBounds() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + Rect dividerBounds = ssb.visualDividerBounds; + assertEquals(0, dividerBounds.left); + assertEquals(DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2, dividerBounds.top); + assertEquals(DEVICE_WIDTH, dividerBounds.right); + assertEquals(DEVICE_LENGTH / 2 + DIVIDER_SIZE / 2, dividerBounds.bottom); + } + + @Test + public void testVerticalDividerBounds() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + Rect dividerBounds = ssb.visualDividerBounds; + assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left); + assertEquals(0, dividerBounds.top); + assertEquals(DEVICE_WIDTH / 2 + DIVIDER_SIZE / 2, dividerBounds.right); + assertEquals(DEVICE_LENGTH, dividerBounds.bottom); + } + + @Test + public void testEqualVerticalTaskPercent() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH; + assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01); + } + + @Test + public void testEqualHorizontalTaskPercent() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH; + assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01); + } +} diff --git a/libs/hwui/pipeline/skia/FunctorDrawable.h b/libs/hwui/pipeline/skia/FunctorDrawable.h index 9bbd0a92600b..29ef2b82919d 100644 --- a/libs/hwui/pipeline/skia/FunctorDrawable.h +++ b/libs/hwui/pipeline/skia/FunctorDrawable.h @@ -34,6 +34,8 @@ namespace skiapipeline { */ class FunctorDrawable : public SkDrawable { public: + constexpr static const char* const TYPE_NAME = "FunctorDrawable"; + FunctorDrawable(int functor, SkCanvas* canvas) : mBounds(canvas->getLocalClipBounds()) , mWebViewHandle(WebViewFunctorManager::instance().handleFor(functor)) {} @@ -48,6 +50,8 @@ public: mWebViewHandle->onRemovedFromTree(); } + const char* getTypeName() const override { return TYPE_NAME; } + protected: virtual SkRect onGetBounds() override { return mBounds; } diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp index 6777c00c4655..41e36874b862 100644 --- a/libs/hwui/pipeline/skia/TransformCanvas.cpp +++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ #include "TransformCanvas.h" + +#include "FunctorDrawable.h" #include "HolePunch.h" #include "SkData.h" #include "SkDrawable.h" @@ -35,7 +37,17 @@ void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkDa } void TransformCanvas::onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) { - drawable->draw(this, matrix); + // TransformCanvas filters all drawing commands while maintaining the current + // clip stack and transformation. We need to draw most SkDrawables, since their + // draw calls may call methods that affect the clip stack and transformation. (Any + // actual draw commands will then be filtered out.) But FunctorDrawables are used + // as leaf nodes which issue self-contained OpenGL/Vulkan commands. These won't + // affect the clip stack + transformation, and in some cases cause problems (e.g. if + // the surface only has an alpha channel). See b/203960959 + const auto* drawableName = drawable->getTypeName(); + if (drawableName == nullptr || strcmp(drawableName, FunctorDrawable::TYPE_NAME) != 0) { + drawable->draw(this, matrix); + } } bool TransformCanvas::onFilter(SkPaint& paint) const { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 3c43f4a637ba..a383c1e2b680 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -70,6 +70,7 @@ public class InfoMediaManager extends MediaManager { MediaRouter2Manager mRouterManager; @VisibleForTesting String mPackageName; + private final boolean mVolumeAdjustmentForRemoteGroupSessions; private MediaDevice mCurrentConnectedDevice; private LocalBluetoothManager mBluetoothManager; @@ -83,6 +84,9 @@ public class InfoMediaManager extends MediaManager { if (!TextUtils.isEmpty(packageName)) { mPackageName = packageName; } + + mVolumeAdjustmentForRemoteGroupSessions = context.getResources().getBoolean( + com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); } @Override @@ -387,7 +391,9 @@ public class InfoMediaManager extends MediaManager { @TargetApi(Build.VERSION_CODES.R) boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) { - return false; + return sessionInfo.isSystemSession() // System sessions are not remote + || mVolumeAdjustmentForRemoteGroupSessions + || sessionInfo.getSelectedRoutes().size() <= 1; } private void refreshDevices() { diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index 1844288796cc..0b3eccfd3a91 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -85,9 +85,10 @@ public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) { val camSeed = Cam.fromInt(seedArgb) val hue = camSeed.hue val chroma = camSeed.chroma.coerceAtLeast(ACCENT1_CHROMA) + val tertiaryHue = wrapDegrees((hue + ACCENT3_HUE_SHIFT).toInt()) accent1 = Shades.of(hue, chroma).toList() accent2 = Shades.of(hue, ACCENT2_CHROMA).toList() - accent3 = Shades.of(hue + ACCENT3_HUE_SHIFT, ACCENT3_CHROMA).toList() + accent3 = Shades.of(tertiaryHue.toFloat(), ACCENT3_CHROMA).toList() neutral1 = Shades.of(hue, NEUTRAL1_CHROMA).toList() neutral2 = Shades.of(hue, NEUTRAL2_CHROMA).toList() } diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 4d2986f40a33..7216ac6e5702 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -266,8 +266,6 @@ <item name="android:paddingTop">12dp</item> <item name="android:paddingHorizontal">24dp</item> <item name="android:textSize">24sp</item> - <item name="android:singleLine">true</item> - <item name="android:ellipsize">marquee</item> </style> <style name="TextAppearance.AuthCredential.Subtitle"> @@ -275,8 +273,6 @@ <item name="android:paddingTop">8dp</item> <item name="android:paddingHorizontal">24dp</item> <item name="android:textSize">16sp</item> - <item name="android:singleLine">true</item> - <item name="android:ellipsize">marquee</item> </style> <style name="TextAppearance.AuthCredential.Description"> @@ -284,8 +280,6 @@ <item name="android:paddingTop">8dp</item> <item name="android:paddingHorizontal">24dp</item> <item name="android:textSize">14sp</item> - <item name="android:singleLine">true</item> - <item name="android:ellipsize">marquee</item> </style> <style name="TextAppearance.AuthCredential.Error"> diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt index ee6dea5364f4..91a391272be7 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt @@ -20,6 +20,11 @@ package com.android.systemui.flags */ interface FlagReader { /** Returns a boolean value for the given flag. */ + fun isEnabled(flag: BooleanFlag): Boolean { + return flag.default + } + + /** Returns a boolean value for the given flag. */ fun isEnabled(id: Int, def: Boolean): Boolean { return def } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java deleted file mode 100644 index 323b20e41a5c..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java +++ /dev/null @@ -1,45 +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. - */ - -package com.android.systemui.shared.recents.model; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * A group task in the recent tasks list. - * TODO: Move this into Launcher - */ -public class GroupTask { - public @NonNull Task task1; - public @Nullable Task task2; - - public GroupTask(@NonNull Task t1, @Nullable Task t2) { - task1 = t1; - task2 = t2; - } - - public GroupTask(@NonNull GroupTask group) { - task1 = new Task(group.task1); - task2 = group.task2 != null - ? new Task(group.task2) - : null; - } - - public boolean containsTask(int taskId) { - return task1.key.id == taskId || (task2 != null && task2.key.id == taskId); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 3f2ff742d072..3128ffdbc67b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -23,17 +23,14 @@ import android.app.ActivityManager.TaskDescription; import android.app.TaskInfo; import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.view.ViewDebug; -import com.android.systemui.shared.recents.utilities.Utilities; +import androidx.annotation.Nullable; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Objects; /** @@ -202,8 +199,8 @@ public class Task { * The icon is the task description icon (if provided), which falls back to the activity icon, * which can then fall back to the application icon. */ - public Drawable icon; - public ThumbnailData thumbnail; + @Nullable public Drawable icon; + @Nullable public ThumbnailData thumbnail; @ViewDebug.ExportedProperty(category="recents") @Deprecated public String title; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index b95123d2fa41..38eded878014 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -195,8 +195,13 @@ public class ActivityManagerWrapper { } @Override - public void onTaskAppeared(RemoteAnimationTarget app) { - animationHandler.onTaskAppeared(new RemoteAnimationTargetCompat(app)); + public void onTasksAppeared(RemoteAnimationTarget[] apps) { + final RemoteAnimationTargetCompat[] compats = + new RemoteAnimationTargetCompat[apps.length]; + for (int i = 0; i < apps.length; ++i) { + compats[i] = new RemoteAnimationTargetCompat(apps[i]); + } + animationHandler.onTasksAppeared(compats); } }; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java index a74de2e0c085..48f1b76c1d50 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java @@ -39,5 +39,5 @@ public interface RecentsAnimationListener { * Called when the task of an activity that has been started while the recents animation * was running becomes ready for control. */ - void onTaskAppeared(RemoteAnimationTargetCompat app); + void onTasksAppeared(RemoteAnimationTargetCompat[] app); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 99b6aed497cc..954cf9fd81a8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -53,6 +53,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; import com.android.systemui.shared.recents.model.ThumbnailData; +import java.util.ArrayList; import java.util.concurrent.Executor; /** @@ -127,7 +128,7 @@ public class RemoteTransitionCompat implements Parcelable { mToken = transition; // This transition is for opening recents, so recents is on-top. We want to draw // the current going-away task on top of recents, though, so move it to front - WindowContainerToken pausingTask = null; + final ArrayList<WindowContainerToken> pausingTasks = new ArrayList<>(); WindowContainerToken pipTask = null; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); @@ -138,7 +139,8 @@ public class RemoteTransitionCompat implements Parcelable { if (taskInfo == null) { continue; } - pausingTask = taskInfo.token; + // Add to front since we are iterating backwards. + pausingTasks.add(0, taskInfo.token); if (taskInfo.pictureInPictureParams != null && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) { pipTask = taskInfo.token; @@ -150,7 +152,7 @@ public class RemoteTransitionCompat implements Parcelable { t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1); } t.apply(); - mRecentsSession.setup(controller, info, finishedCallback, pausingTask, pipTask, + mRecentsSession.setup(controller, info, finishedCallback, pausingTasks, pipTask, leashMap, mToken); recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0), new Rect()); @@ -198,18 +200,18 @@ public class RemoteTransitionCompat implements Parcelable { static class RecentsControllerWrap extends RecentsAnimationControllerCompat { private RecentsAnimationControllerCompat mWrapped = null; private IRemoteTransitionFinishedCallback mFinishCB = null; - private WindowContainerToken mPausingTask = null; + private ArrayList<WindowContainerToken> mPausingTasks = null; private WindowContainerToken mPipTask = null; private TransitionInfo mInfo = null; - private SurfaceControl mOpeningLeash = null; + private ArrayList<SurfaceControl> mOpeningLeashes = null; private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null; private PictureInPictureSurfaceTransaction mPipTransaction = null; private IBinder mTransition = null; void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info, - IRemoteTransitionFinishedCallback finishCB, WindowContainerToken pausingTask, - WindowContainerToken pipTask, ArrayMap<SurfaceControl, SurfaceControl> leashMap, - IBinder transition) { + IRemoteTransitionFinishedCallback finishCB, + ArrayList<WindowContainerToken> pausingTasks, WindowContainerToken pipTask, + ArrayMap<SurfaceControl, SurfaceControl> leashMap, IBinder transition) { if (mInfo != null) { throw new IllegalStateException("Trying to run a new recents animation while" + " recents is already active."); @@ -217,7 +219,7 @@ public class RemoteTransitionCompat implements Parcelable { mWrapped = wrapped; mInfo = info; mFinishCB = finishCB; - mPausingTask = pausingTask; + mPausingTasks = pausingTasks; mPipTask = pipTask; mLeashMap = leashMap; mTransition = transition; @@ -226,36 +228,57 @@ public class RemoteTransitionCompat implements Parcelable { @SuppressLint("NewApi") boolean merge(TransitionInfo info, SurfaceControl.Transaction t, RecentsAnimationListener recents) { - TransitionInfo.Change openingTask = null; + ArrayList<TransitionInfo.Change> openingTasks = null; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { if (change.getTaskInfo() != null) { - if (openingTask != null) { - Log.w(TAG, " Expecting to merge a task-open, but got >1 opening " - + "tasks"); + if (openingTasks == null) { + openingTasks = new ArrayList<>(); } - openingTask = change; + openingTasks.add(change); } } } - if (openingTask == null) return false; - mOpeningLeash = openingTask.getLeash(); - if (openingTask.getContainer().equals(mPausingTask)) { - // In this case, we are "returning" to the already running app, so just consume + if (openingTasks == null) return false; + int pauseMatches = 0; + for (int i = 0; i < openingTasks.size(); ++i) { + if (mPausingTasks.contains(openingTasks.get(i).getContainer())) { + ++pauseMatches; + } + if (openingTasks.get(i).getContainer().equals(mPausingTasks.get(i))) { + // In this case, we are "returning" to an already running app, so just consume + // the merge and do nothing. + } + } + if (pauseMatches > 0) { + if (pauseMatches != mPausingTasks.size()) { + // We are not really "returning" properly... something went wrong. + throw new IllegalStateException("\"Concelling\" a recents transitions by " + + "unpausing " + pauseMatches + " apps after pausing " + + mPausingTasks.size() + " apps."); + } + // In this case, we are "returning" to an already running app, so just consume // the merge and do nothing. return true; } - // We are receiving a new opening task, so convert to onTaskAppeared. final int layer = mInfo.getChanges().size() * 3; - final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat( - openingTask, layer, mInfo, t); - mLeashMap.put(mOpeningLeash, target.leash.mSurfaceControl); - t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash()); - t.setLayer(target.leash.mSurfaceControl, layer); - t.hide(target.leash.mSurfaceControl); - t.apply(); - recents.onTaskAppeared(target); + mOpeningLeashes = new ArrayList<>(); + final RemoteAnimationTargetCompat[] targets = + new RemoteAnimationTargetCompat[openingTasks.size()]; + for (int i = 0; i < openingTasks.size(); ++i) { + mOpeningLeashes.add(openingTasks.get(i).getLeash()); + // We are receiving new opening tasks, so convert to onTasksAppeared. + final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat( + openingTasks.get(i), layer, mInfo, t); + mLeashMap.put(mOpeningLeashes.get(i), target.leash.mSurfaceControl); + t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash()); + t.setLayer(target.leash.mSurfaceControl, layer); + t.hide(target.leash.mSurfaceControl); + t.apply(); + targets[i] = target; + } + recents.onTasksAppeared(targets); return true; } @@ -292,21 +315,26 @@ public class RemoteTransitionCompat implements Parcelable { } if (mWrapped != null) mWrapped.finish(toHome, sendUserLeaveHint); try { - if (!toHome && mPausingTask != null && mOpeningLeash == null) { + if (!toHome && mPausingTasks != null && mOpeningLeashes == null) { // The gesture went back to opening the app rather than continuing with // recents, so end the transition by moving the app back to the top (and also // re-showing it's task). final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reorder(mPausingTask, true /* onTop */); final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.show(mInfo.getChange(mPausingTask).getLeash()); + for (int i = mPausingTasks.size() - 1; i >= 0; ++i) { + // reverse order so that index 0 ends up on top + wct.reorder(mPausingTasks.get(i), true /* onTop */); + t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash()); + } mFinishCB.onTransitionFinished(wct, t); } else { - if (mOpeningLeash != null) { + if (mOpeningLeashes != null) { // TODO: the launcher animation should handle this final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.show(mOpeningLeash); - t.setAlpha(mOpeningLeash, 1.f); + for (int i = 0; i < mOpeningLeashes.size(); ++i) { + t.show(mOpeningLeashes.get(i)); + t.setAlpha(mOpeningLeashes.get(i), 1.f); + } t.apply(); } if (mPipTask != null && mPipTransaction != null) { @@ -339,9 +367,9 @@ public class RemoteTransitionCompat implements Parcelable { // Reset all members. mWrapped = null; mFinishCB = null; - mPausingTask = null; + mPausingTasks = null; mInfo = null; - mOpeningLeash = null; + mOpeningLeashes = null; mLeashMap = null; mTransition = null; } diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java index ef046193473d..acfa3c84a4ba 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java @@ -26,13 +26,16 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Resources; import android.os.Bundle; import android.util.Log; +import androidx.annotation.BoolRes; import androidx.annotation.NonNull; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.util.settings.SecureSettings; @@ -62,14 +65,19 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { private final FlagManager mFlagManager; private final SecureSettings mSecureSettings; + private final Resources mResources; private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>(); @Inject - public FeatureFlagManager(FlagManager flagManager, - SecureSettings secureSettings, Context context, + public FeatureFlagManager( + FlagManager flagManager, + Context context, + SecureSettings secureSettings, + @Main Resources resources, DumpManager dumpManager) { mFlagManager = flagManager; mSecureSettings = secureSettings; + mResources = resources; IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_SET_FLAG); filter.addAction(ACTION_GET_FLAGS); @@ -77,17 +85,32 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { dumpManager.registerDumpable(TAG, this); } - /** Return a {@link BooleanFlag}'s value. */ @Override - public boolean isEnabled(int id, boolean defaultValue) { + public boolean isEnabled(BooleanFlag flag) { + int id = flag.getId(); if (!mBooleanFlagCache.containsKey(id)) { - Boolean result = isEnabledInternal(id); - mBooleanFlagCache.put(id, result == null ? defaultValue : result); + boolean def = flag.getDefault(); + if (flag.hasResourceOverride()) { + try { + def = isEnabledInOverlay(flag.getResourceOverride()); + } catch (Resources.NotFoundException e) { + // no-op + } + } + + mBooleanFlagCache.put(id, isEnabled(id, def)); } return mBooleanFlagCache.get(id); } + /** Return a {@link BooleanFlag}'s value. */ + @Override + public boolean isEnabled(int id, boolean defaultValue) { + Boolean result = isEnabledInternal(id); + return result == null ? defaultValue : result; + } + /** Returns the stored value or null if not set. */ private Boolean isEnabledInternal(int id) { try { @@ -98,6 +121,10 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { return null; } + private boolean isEnabledInOverlay(@BoolRes int resId) { + return mResources.getBoolean(resId); + } + /** Set whether a given {@link BooleanFlag} is enabled or not. */ @Override public void setEnabled(int id, boolean value) { diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java index 6ff175f589a6..0934b32a71e4 100644 --- a/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java +++ b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java @@ -16,7 +16,6 @@ package com.android.systemui.flags; -import android.content.Context; import android.util.SparseBooleanArray; import androidx.annotation.NonNull; @@ -24,7 +23,6 @@ import androidx.annotation.NonNull; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.util.settings.SecureSettings; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -41,8 +39,7 @@ import javax.inject.Inject; public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { SparseBooleanArray mAccessedFlags = new SparseBooleanArray(); @Inject - public FeatureFlagManager( - SecureSettings secureSettings, Context context, DumpManager dumpManager) { + public FeatureFlagManager(DumpManager dumpManager) { dumpManager.registerDumpable("SysUIFlags", this); } @@ -53,6 +50,11 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { public void removeListener(Listener run) {} @Override + public boolean isEnabled(BooleanFlag flag) { + return isEnabled(flag.getId(), flag.getDefault()); + } + + @Override public boolean isEnabled(int key, boolean defaultValue) { mAccessedFlags.append(key, defaultValue); return defaultValue; diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index a383cab94c36..ac463ebc1c97 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -31,7 +31,6 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.ViewController; @@ -49,7 +48,6 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie private final StatusBarStateController mStatusBarStateController; private final BroadcastDispatcher mBroadcastDispatcher; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final KeyguardBypassController mBypassController; private final BatteryController mBatteryController; private final int mDozingColor = Color.WHITE; private int mLockScreenColor; @@ -71,14 +69,12 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie BroadcastDispatcher broadcastDispatcher, BatteryController batteryController, KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardBypassController bypassController, @Main Resources resources ) { super(view); mStatusBarStateController = statusBarStateController; mBroadcastDispatcher = broadcastDispatcher; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mBypassController = bypassController; mBatteryController = batteryController; mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER); diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java index ef3104a21708..2a0c2855c3b2 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java @@ -27,7 +27,6 @@ import android.util.AttributeSet; import android.widget.TextView; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import java.util.Calendar; import java.util.Locale; @@ -111,6 +110,28 @@ public class AnimatableClockView extends TextView { super.onDetachedFromWindow(); } + int getDozingWeight() { + if (useBoldedVersion()) { + return mDozingWeight + 100; + } + return mDozingWeight; + } + + int getLockScreenWeight() { + if (useBoldedVersion()) { + return mLockScreenWeight + 100; + } + return mLockScreenWeight; + } + + /** + * Whether to use a bolded version based on the user specified fontWeightAdjustment. + */ + boolean useBoldedVersion() { + // "Bold text" fontWeightAdjustment is 300. + return getResources().getConfiguration().fontWeightAdjustment > 100; + } + void refreshTime() { mTime.setTimeInMillis(System.currentTimeMillis()); setText(DateFormat.format(mFormat, mTime)); @@ -162,7 +183,7 @@ public class AnimatableClockView extends TextView { } setTextStyle( - mDozingWeight, + getDozingWeight(), -1 /* text size, no update */, mLockScreenColor, false /* animate */, @@ -171,7 +192,7 @@ public class AnimatableClockView extends TextView { null /* onAnimationEnd */); setTextStyle( - mLockScreenWeight, + getLockScreenWeight(), -1 /* text size, no update */, mLockScreenColor, true, /* animate */ @@ -180,35 +201,22 @@ public class AnimatableClockView extends TextView { null /* onAnimationEnd */); } - void animateDisappear() { - if (mTextAnimator == null) { - return; - } - - setTextStyle( - 0 /* weight */, - -1 /* text size, no update */, - null /* color, no update */, - true /* animate */, - KeyguardBypassController.BYPASS_FADE_DURATION /* duration */, - 0 /* delay */, - null /* onAnimationEnd */); - } - void animateCharge(DozeStateGetter dozeStateGetter) { if (mTextAnimator == null || mTextAnimator.isRunning()) { // Skip charge animation if dozing animation is already playing. return; } Runnable startAnimPhase2 = () -> setTextStyle( - dozeStateGetter.isDozing() ? mDozingWeight : mLockScreenWeight/* weight */, + dozeStateGetter.isDozing() ? getDozingWeight() : getLockScreenWeight() /* weight */, -1, null, true /* animate */, CHARGE_ANIM_DURATION_PHASE_1, 0 /* delay */, null /* onAnimationEnd */); - setTextStyle(dozeStateGetter.isDozing() ? mLockScreenWeight : mDozingWeight/* weight */, + setTextStyle(dozeStateGetter.isDozing() + ? getLockScreenWeight() + : getDozingWeight()/* weight */, -1, null, true /* animate */, @@ -218,7 +226,7 @@ public class AnimatableClockView extends TextView { } void animateDoze(boolean isDozing, boolean animate) { - setTextStyle(isDozing ? mDozingWeight : mLockScreenWeight /* weight */, + setTextStyle(isDozing ? getDozingWeight() : getLockScreenWeight() /* weight */, -1, isDozing ? mDozingColor : mLockScreenColor, animate, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 1931c0a1645c..905495d369a0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -167,7 +167,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mBroadcastDispatcher, mBatteryController, mKeyguardUpdateMonitor, - mBypassController, mResources); mClockViewController.init(); @@ -178,7 +177,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mBroadcastDispatcher, mBatteryController, mKeyguardUpdateMonitor, - mBypassController, mResources); mLargeClockViewController.init(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 85bc8f7c70a2..d27bc675ecb8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -2044,17 +2044,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** - * @return true if there's at least one udfps enrolled + * @return true if there's at least one udfps enrolled for the current user. */ public boolean isUdfpsEnrolled() { return mIsUdfpsEnrolled; } /** - * @return if udfps is available on this device. will return true even if the user hasn't - * enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered. + * @return true if udfps HW is supported on this device. Can return true even if the user has + * not enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered. */ - public boolean isUdfpsAvailable() { + public boolean isUdfpsSupported() { return mAuthController.getUdfpsProps() != null && !mAuthController.getUdfpsProps().isEmpty(); } @@ -2102,7 +2102,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } updateUdfpsEnrolled(getCurrentUser()); - final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsEnrolled()); + final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported()); final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING; if (runningOrRestarting && !shouldListenForFingerprint) { @@ -2407,7 +2407,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } else { mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal, mFingerprintAuthenticationCallback, null /* handler */, - FingerprintManager.SENSOR_ID_ANY, userId); + FingerprintManager.SENSOR_ID_ANY, userId, 0 /* flags */); } setFingerprintRunningState(BIOMETRIC_STATE_RUNNING); } @@ -2990,7 +2990,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * Register to receive notifications about general keyguard information - * (see {@link InfoCallback}. + * (see {@link KeyguardUpdateMonitorCallback}. * * @param callback The callback to register */ @@ -3388,11 +3388,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab + " expected=" + (shouldListenForFingerprint(isUdfpsEnrolled()) ? 1 : 0)); pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); - pw.println(" udfpsEnrolled=" + isUdfpsEnrolled()); pw.println(" mFingerprintLockedOut=" + mFingerprintLockedOut); pw.println(" mFingerprintLockedOutPermanent=" + mFingerprintLockedOutPermanent); pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId)); - if (isUdfpsEnrolled()) { + if (isUdfpsSupported()) { + pw.println(" udfpsEnrolled=" + isUdfpsEnrolled()); pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true)); pw.println(" bouncerVisible=" + mBouncer); pw.println(" mStatusBarState=" diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index e115c342c4fe..b77db8f97ccb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -121,6 +121,13 @@ public class KeyguardVisibilityHelper { .setStartDelay(delay); } animator.start(); + } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) { + mKeyguardViewVisibilityAnimating = true; + + // Ask the screen off animation controller to animate the keyguard visibility for us + // since it may need to be cancelled due to keyguard lifecycle events. + mUnlockedScreenOffAnimationController.animateInKeyguard( + mView, mAnimateKeyguardStatusViewVisibleEndRunnable); } else if (mLastOccludedState && !isOccluded) { // An activity was displayed over the lock screen, and has now gone away mView.setVisibility(View.VISIBLE); @@ -132,13 +139,6 @@ public class KeyguardVisibilityHelper { .alpha(1f) .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable) .start(); - } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) { - mKeyguardViewVisibilityAnimating = true; - - // Ask the screen off animation controller to animate the keyguard visibility for us - // since it may need to be cancelled due to keyguard lifecycle events. - mUnlockedScreenOffAnimationController.animateInKeyguard( - mView, mAnimateKeyguardStatusViewVisibleEndRunnable); } else { mView.setVisibility(View.VISIBLE); mView.setAlpha(1f); diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 8a0b5b8704e6..c7be3ce01c54 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -435,7 +435,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme boolean wasUdfpsSupported = mUdfpsSupported; boolean wasUdfpsEnrolled = mUdfpsEnrolled; - mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + mUdfpsSupported = mKeyguardUpdateMonitor.isUdfpsSupported(); mView.setUseBackground(mUdfpsSupported); mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java index 59d9aff2ef46..d2703f5e73a2 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java @@ -22,6 +22,7 @@ import static android.util.MathUtils.sq; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; import static java.util.Objects.requireNonNull; @@ -659,6 +660,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, PixelFormat.TRANSLUCENT); params.receiveInsetsIgnoringZOrder = true; + params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; params.windowAnimations = android.R.style.Animation_Translucent; params.gravity = Gravity.START | Gravity.TOP; params.x = (mAlignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index ec17d4e5cd03..90a1e5e64daf 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -16,6 +16,8 @@ package com.android.systemui.biometrics +import android.animation.Animator +import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context import android.graphics.PointF @@ -29,7 +31,9 @@ import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.animation.Interpolators import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.CircleReveal +import com.android.systemui.statusbar.LiftReveal import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.Command @@ -41,13 +45,10 @@ import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarS import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.ViewController +import com.android.systemui.util.leak.RotationUtils import java.io.PrintWriter import javax.inject.Inject import javax.inject.Provider -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.util.leak.RotationUtils - -private const val WAKE_AND_UNLOCK_FADE_DURATION = 180L /*** * Controls the ripple effect that shows when authentication is successful. @@ -141,11 +142,12 @@ class AuthRippleController @Inject constructor( private fun showUnlockedRipple() { notificationShadeWindowController.setForcePluginOpen(true, this) - val useCircleReveal = circleReveal != null && biometricUnlockController.isWakeAndUnlock val lightRevealScrim = statusBar.lightRevealScrim - if (useCircleReveal) { - lightRevealScrim?.revealEffect = circleReveal!! - startLightRevealScrimOnKeyguardFadingAway = true + if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) { + circleReveal?.let { + lightRevealScrim?.revealEffect = it + startLightRevealScrimOnKeyguardFadingAway = true + } } mView.startUnlockedRipple( @@ -160,19 +162,29 @@ class AuthRippleController @Inject constructor( if (keyguardStateController.isKeyguardFadingAway) { val lightRevealScrim = statusBar.lightRevealScrim if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) { - val revealAnimator = ValueAnimator.ofFloat(.1f, 1f).apply { + ValueAnimator.ofFloat(.1f, 1f).apply { interpolator = Interpolators.LINEAR_OUT_SLOW_IN duration = RIPPLE_ANIMATION_DURATION startDelay = keyguardStateController.keyguardFadingAwayDelay addUpdateListener { animator -> if (lightRevealScrim.revealEffect != circleReveal) { - // if the something else took over the reveal, let's do nothing. + // if something else took over the reveal, let's do nothing. return@addUpdateListener } lightRevealScrim.revealAmount = animator.animatedValue as Float } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + // Reset light reveal scrim to the default, so the StatusBar + // can handle any subsequent light reveal changes + // (ie: from dozing changes) + if (lightRevealScrim.revealEffect == circleReveal) { + lightRevealScrim.revealEffect = LiftReveal + } + } + }) + start() } - revealAnimator.start() startLightRevealScrimOnKeyguardFadingAway = false } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java index d64f9b3b9fb6..34f441510a7e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java @@ -17,22 +17,11 @@ package com.android.systemui.flags; import android.content.Context; -import android.content.res.Resources; import android.util.FeatureFlagUtils; import android.util.Log; -import android.util.SparseArray; import android.widget.Toast; -import androidx.annotation.BoolRes; - -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import javax.inject.Inject; @@ -43,31 +32,13 @@ import javax.inject.Inject; */ @SysUISingleton public class FeatureFlags { - private final Resources mResources; private final FlagReader mFlagReader; private final Context mContext; - private final Map<Integer, Flag<?>> mFlagMap = new HashMap<>(); - private final Map<Integer, List<Listener>> mListeners = new HashMap<>(); - private final SparseArray<Boolean> mCachedFlags = new SparseArray<>(); @Inject - public FeatureFlags(@Main Resources resources, FlagReader flagReader, Context context) { - mResources = resources; + public FeatureFlags(FlagReader flagReader, Context context) { mFlagReader = flagReader; mContext = context; - - flagReader.addListener(mListener); - } - - private final FlagReader.Listener mListener = id -> { - if (mListeners.containsKey(id) && mFlagMap.containsKey(id)) { - mListeners.get(id).forEach(listener -> listener.onFlagChanged(mFlagMap.get(id))); - } - }; - - @VisibleForTesting - void addFlag(Flag<?> flag) { - mFlagMap.put(flag.getId(), flag); } /** @@ -75,32 +46,7 @@ public class FeatureFlags { * @return The value of the flag. */ public boolean isEnabled(BooleanFlag flag) { - boolean def = flag.getDefault(); - if (flag.hasResourceOverride()) { - try { - def = isEnabledInOverlay(flag.getResourceOverride()); - } catch (Resources.NotFoundException e) { - // no-op - } - } - return mFlagReader.isEnabled(flag.getId(), def); - } - - /** - * @param flag The {@link IntFlag} of interest. - - /** Add a listener for a specific flag. */ - public void addFlagListener(Flag<?> flag, Listener listener) { - mListeners.putIfAbsent(flag.getId(), new ArrayList<>()); - mListeners.get(flag.getId()).add(listener); - mFlagMap.putIfAbsent(flag.getId(), flag); - } - - /** Remove a listener for a specific flag. */ - public void removeFlagListener(Flag<?> flag, Listener listener) { - if (mListeners.containsKey(flag.getId())) { - mListeners.get(flag.getId()).remove(listener); - } + return mFlagReader.isEnabled(flag); } public void assertLegacyPipelineEnabled() { @@ -205,20 +151,4 @@ public class FeatureFlags { public static boolean isProviderModelSettingEnabled(Context context) { return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); } - - private boolean isEnabledInOverlay(@BoolRes int resId) { - synchronized (mCachedFlags) { - if (!mCachedFlags.contains(resId)) { - mCachedFlags.put(resId, mResources.getBoolean(resId)); - } - - return mCachedFlags.get(resId); - } - } - - /** Simple interface for beinga alerted when a specific flag changes value. */ - public interface Listener { - /** */ - void onFlagChanged(Flag<?> flag); - } } 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 42dd8862576b..19812697719c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -79,6 +79,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { private final DialogLaunchAnimator mDialogLaunchAnimator; private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>(); private final boolean mAboveStatusbar; + private final boolean mVolumeAdjustmentForRemoteGroupSessions; private final NotificationEntryManager mNotificationEntryManager; @VisibleForTesting final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); @@ -111,6 +112,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName); mUiEventLogger = uiEventLogger; mDialogLaunchAnimator = dialogLaunchAnimator; + mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); } void start(@NonNull Callback cb) { @@ -477,7 +480,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { } boolean isVolumeControlEnabled(@NonNull MediaDevice device) { - return !isActiveRemoteDevice(device); + // TODO(b/202500642): Also enable volume control for remote non-group sessions. + return !isActiveRemoteDevice(device) + || mVolumeAdjustmentForRemoteGroupSessions; } private final MediaController.Callback mCb = new MediaController.Callback() { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 6a1eae75f9a9..bc023cc892a6 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -24,9 +24,6 @@ import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; -import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; -import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE; -import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.containsType; @@ -131,10 +128,10 @@ import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; -import com.android.systemui.shared.rotation.RotationButton; -import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.rotation.RotationButton; +import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.AutoHideUiElement; @@ -614,8 +611,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mDeviceProvisionedController.addCallback(mUserSetupListener); mNotificationShadeDepthController.addListener(mDepthListener); - updateAccessibilityButtonModeIfNeeded(); - return barView; } @@ -1406,34 +1401,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, updateSystemUiStateFlags(a11yFlags); } - private void updateAccessibilityButtonModeIfNeeded() { - final int mode = Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, - ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT); - - // ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU is compatible under gestural or non-gestural - // mode, so we don't need to update it. - if (mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { - return; - } - - // ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR is incompatible under gestural mode. Need to - // force update to ACCESSIBILITY_BUTTON_MODE_GESTURE. - if (QuickStepContract.isGesturalMode(mNavBarMode) - && mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) { - Settings.Secure.putIntForUser(mContentResolver, - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE, - UserHandle.USER_CURRENT); - // ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to - // force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR. - } else if (!QuickStepContract.isGesturalMode(mNavBarMode) - && mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) { - Settings.Secure.putIntForUser(mContentResolver, - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, - ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT); - } - } - public void updateSystemUiStateFlags(int a11yFlags) { if (a11yFlags < 0) { a11yFlags = mNavigationBarA11yHelper.getA11yButtonState(); @@ -1551,6 +1518,9 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Override public void onNavigationModeChanged(int mode) { mNavBarMode = mode; + // update assistant entry points on system navigation radio button click + updateAssistantEntrypoints(); + if (!QuickStepContract.isGesturalMode(mode)) { // Reset the override alpha if (getBarTransitions() != null) { @@ -1558,7 +1528,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } } updateScreenPinningGestures(); - updateAccessibilityButtonModeIfNeeded(); if (!canShowSecondaryHandle()) { resetSecondaryHandle(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 4959c7d6808d..3dc79c43d894 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -16,10 +16,14 @@ package com.android.systemui.navigationbar; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.shared.recents.utilities.Utilities.isTablet; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -27,6 +31,8 @@ import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import android.util.SparseArray; import android.view.Display; @@ -46,6 +52,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.phone.AutoHideController; @@ -142,6 +149,8 @@ public class NavigationBarController implements } final int oldMode = mNavMode; mNavMode = mode; + updateAccessibilityButtonModeIfNeeded(); + mHandler.post(() -> { // create/destroy nav bar based on nav mode only in unfolded state if (oldMode != mNavMode) { @@ -157,6 +166,35 @@ public class NavigationBarController implements }); } + private void updateAccessibilityButtonModeIfNeeded() { + ContentResolver contentResolver = mContext.getContentResolver(); + final int mode = Settings.Secure.getIntForUser(contentResolver, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT); + + // ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU is compatible under gestural or non-gestural + // mode, so we don't need to update it. + if (mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { + return; + } + + // ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR is incompatible under gestural mode. Need to + // force update to ACCESSIBILITY_BUTTON_MODE_GESTURE. + if (QuickStepContract.isGesturalMode(mNavMode) + && mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) { + Settings.Secure.putIntForUser(contentResolver, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE, + UserHandle.USER_CURRENT); + // ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to + // force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR. + } else if (!QuickStepContract.isGesturalMode(mNavMode) + && mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) { + Settings.Secure.putIntForUser(contentResolver, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT); + } + } + /** @see #initializeTaskbarIfNecessary() */ private boolean updateNavbarForTaskbar() { boolean taskbarShown = initializeTaskbarIfNecessary(); @@ -222,6 +260,8 @@ public class NavigationBarController implements */ public void createNavigationBars(final boolean includeDefaultDisplay, RegisterStatusBarResult result) { + updateAccessibilityButtonModeIfNeeded(); + // Don't need to create nav bar on the default display if we initialize TaskBar. final boolean shouldCreateDefaultNavbar = includeDefaultDisplay && !initializeTaskbarIfNecessary(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 74ebfe5ad5e4..1c0088709f14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -818,7 +818,7 @@ public class KeyguardIndicationController { } private void showTryFingerprintMsg(int msgId, String a11yString) { - if (mKeyguardUpdateMonitor.isUdfpsAvailable()) { + if (mKeyguardUpdateMonitor.isUdfpsSupported()) { // if udfps available, there will always be a tappable affordance to unlock // For example, the lock icon if (mKeyguardBypassController.getUserHasDeviceEntryIntent()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index cbb3aba5cc64..da2b85ee0b61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -173,7 +173,7 @@ public class StatusBarStateControllerImpl implements } // Record the to-be mState and mLastState - recordHistoricalState(state, mState); + recordHistoricalState(state /* newState */, mState /* lastState */, false); // b/139259891 if (mState == StatusBarState.SHADE && state == StatusBarState.SHADE_LOCKED) { @@ -206,6 +206,7 @@ public class StatusBarStateControllerImpl implements @Override public void setUpcomingState(int nextState) { mUpcomingState = nextState; + recordHistoricalState(mUpcomingState /* newState */, mState /* lastState */, true); } @Override @@ -505,31 +506,36 @@ public class StatusBarStateControllerImpl implements } } - private void recordHistoricalState(int currentState, int lastState) { + private void recordHistoricalState(int newState, int lastState, boolean upcoming) { mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; HistoricalState state = mHistoricalRecords[mHistoryIndex]; - state.mState = currentState; + state.mNewState = newState; state.mLastState = lastState; state.mTimestamp = System.currentTimeMillis(); + state.mUpcoming = upcoming; } /** * For keeping track of our previous state to help with debugging */ private static class HistoricalState { - int mState; + int mNewState; int mLastState; long mTimestamp; + boolean mUpcoming; @Override public String toString() { if (mTimestamp != 0) { StringBuilder sb = new StringBuilder(); - sb.append("state=").append(mState) - .append(" (").append(describe(mState)).append(")"); - sb.append("lastState=").append(mLastState).append(" (").append(describe(mLastState)) + if (mUpcoming) { + sb.append("upcoming-"); + } + sb.append("newState=").append(mNewState) + .append("(").append(describe(mNewState)).append(")"); + sb.append(" lastState=").append(mLastState).append("(").append(describe(mLastState)) .append(")"); - sb.append("timestamp=") + sb.append(" timestamp=") .append(DateFormat.format("MM-dd HH:mm:ss", mTimestamp)); return sb.toString(); 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 e273727761d5..32659e416535 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -2206,7 +2206,7 @@ public class NotificationPanelViewController extends PanelViewController { mQs.setExpanded(mQsExpanded); } - private void setQsExpansion(float height) { + void setQsExpansion(float height) { height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0; if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling @@ -2250,7 +2250,13 @@ public class NotificationPanelViewController extends PanelViewController { int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction); mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY); setQSClippingBounds(); - mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction); + + // Only need to notify the notification stack when we're not in split screen mode. If we + // do, then the notification panel starts scrolling along with the QS. + if (!mShouldUseSplitNotificationShade) { + mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction); + } + mDepthController.setQsPanelExpansion(qsExpansionFraction); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 91df1c51aa04..cbaa4683c364 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -230,7 +230,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.window.StatusBarWindowController; -import com.android.systemui.statusbar.window.StatusBarWindowView; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.util.WallpaperController; @@ -2386,6 +2385,8 @@ public class StatusBar extends SystemUI implements if (mLightRevealScrim != null) { pw.println( + "mLightRevealScrim.getRevealEffect(): " + mLightRevealScrim.getRevealEffect()); + pw.println( "mLightRevealScrim.getRevealAmount(): " + mLightRevealScrim.getRevealAmount()); } @@ -3371,17 +3372,24 @@ public class StatusBar extends SystemUI implements return; } - if (wakingUp && mWakefulnessLifecycle.getLastWakeReason() - == PowerManager.WAKE_REASON_POWER_BUTTON - || !wakingUp && mWakefulnessLifecycle.getLastSleepReason() - == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON) { + final boolean wakingUpFromPowerButton = wakingUp + && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal) + && mWakefulnessLifecycle.getLastWakeReason() + == PowerManager.WAKE_REASON_POWER_BUTTON; + final boolean sleepingFromPowerButton = !wakingUp + && mWakefulnessLifecycle.getLastSleepReason() + == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON; + + if (wakingUpFromPowerButton || sleepingFromPowerButton) { mLightRevealScrim.setRevealEffect(mPowerButtonReveal); + mLightRevealScrim.setRevealAmount(1f - mStatusBarStateController.getDozeAmount()); } else if (!wakingUp || !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { // If we're going to sleep, but it's not from the power button, use the default reveal. // If we're waking up, only use the default reveal if the biometric controller didn't // already set it to the circular reveal because we're waking up from a fingerprint/face // auth. mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE); + mLightRevealScrim.setRevealAmount(1f - mStatusBarStateController.getDozeAmount()); } } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index cd5865f58c85..426bc91a606d 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -259,8 +259,13 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); reevaluateSystemTheme(true /* forceReload */); } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) { - mAcceptColorEvents = true; - Log.i(TAG, "Allowing color events again"); + if (intent.getBooleanExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, false)) { + mAcceptColorEvents = true; + Log.i(TAG, "Wallpaper changed, allowing color events again"); + } else { + Log.i(TAG, "Wallpaper changed from background app, " + + "keep deferring color events. Accepting: " + mAcceptColorEvents); + } } } }; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index e57059894786..cd6a77836304 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -33,7 +33,11 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.IAudioService; import android.media.IVolumeController; +import android.media.MediaRoute2Info; +import android.media.MediaRouter2Manager; +import android.media.RoutingSessionInfo; import android.media.VolumePolicy; +import android.media.session.MediaController; import android.media.session.MediaController.PlaybackInfo; import android.media.session.MediaSession.Token; import android.net.Uri; @@ -71,6 +75,7 @@ import com.android.systemui.util.concurrency.ThreadFactory; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -118,6 +123,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private final Context mContext; private final Looper mWorkerLooper; private final PackageManager mPackageManager; + private final MediaRouter2Manager mRouter2Manager; private final WakefulnessLifecycle mWakefulnessLifecycle; private AudioManager mAudio; private IAudioService mAudioService; @@ -179,6 +185,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mWorkerLooper = theadFactory.buildLooperOnNewThread( VolumeDialogControllerImpl.class.getSimpleName()); mWorker = new W(mWorkerLooper); + mRouter2Manager = MediaRouter2Manager.getInstance(mContext); mMediaSessionsCallbacksW = new MediaSessionsCallbacks(mContext); mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW); mAudio = audioManager; @@ -1149,16 +1156,16 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>(); private int mNextStream = DYNAMIC_STREAM_START_INDEX; - private final boolean mShowRemoteSessions; + private final boolean mVolumeAdjustmentForRemoteGroupSessions; public MediaSessionsCallbacks(Context context) { - mShowRemoteSessions = context.getResources().getBoolean( - com.android.internal.R.bool.config_volumeShowRemoteSessions); + mVolumeAdjustmentForRemoteGroupSessions = context.getResources().getBoolean( + com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); } @Override public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) { - if (mShowRemoteSessions) { + if (showForSession(token)) { addStream(token, "onRemoteUpdate"); int stream = 0; @@ -1190,7 +1197,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa @Override public void onRemoteVolumeChanged(Token token, int flags) { - if (mShowRemoteSessions) { + if (showForSession(token)) { addStream(token, "onRemoteVolumeChanged"); int stream = 0; synchronized (mRemoteStreams) { @@ -1214,7 +1221,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa @Override public void onRemoteRemoved(Token token) { - if (mShowRemoteSessions) { + if (showForSession(token)) { int stream = 0; synchronized (mRemoteStreams) { if (!mRemoteStreams.containsKey(token)) { @@ -1233,14 +1240,41 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } public void setStreamVolume(int stream, int level) { - if (mShowRemoteSessions) { - final Token t = findToken(stream); - if (t == null) { - Log.w(TAG, "setStreamVolume: No token found for stream: " + stream); - return; + final Token token = findToken(stream); + if (token == null) { + Log.w(TAG, "setStreamVolume: No token found for stream: " + stream); + return; + } + if (showForSession(token)) { + mMediaSessions.setVolume(token, level); + } + } + + private boolean showForSession(Token token) { + if (mVolumeAdjustmentForRemoteGroupSessions) { + return true; + } + MediaController ctr = new MediaController(mContext, token); + String packageName = ctr.getPackageName(); + List<RoutingSessionInfo> sessions = + mRouter2Manager.getRoutingSessions(packageName); + boolean foundNonSystemSession = false; + boolean isGroup = false; + for (RoutingSessionInfo session : sessions) { + if (!session.isSystemSession()) { + foundNonSystemSession = true; + int selectedRouteCount = session.getSelectedRoutes().size(); + if (selectedRouteCount > 1) { + isGroup = true; + break; + } } - mMediaSessions.setVolume(t, level); } + if (!foundNonSystemSession) { + Log.d(TAG, "No routing session for " + packageName); + return false; + } + return !isGroup; } private Token findToken(int stream) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 1ee6f70ec7fc..ff5960bc33ce 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -472,7 +472,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); mTestableLooper.processAllMessages(); - verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt()); + verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(), + anyInt()); verify(mFingerprintManager, never()).detectFingerprint(any(), any(), anyInt()); } 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 209df6b54f8f..d4c3840356d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -86,7 +86,14 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { assertFalse(dialog.isShowing) assertFalse(dialog.onStopCalled) - runOnMainThreadAndWaitForIdleSync { dialog.dismiss() } + runOnMainThreadAndWaitForIdleSync { + // TODO(b/204561691): Remove this call to disableAllCurrentDialogsExitAnimations() and + // make sure that the test still pass on git_master/cf_x86_64_phone-userdebug in + // Forrest. + dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() + + dialog.dismiss() + } assertFalse(hostDialog.isShowing) assertFalse(dialog.isShowing) assertTrue(hostDialog.wasDismissed) diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java index 2fa32ba1fe75..634763866d02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java @@ -53,8 +53,6 @@ import java.io.StringWriter; public class FeatureFlagManagerTest extends SysuiTestCase { FeatureFlagManager mFeatureFlagManager; - @Mock private FlagManager mFlagManager; - @Mock private SecureSettings mSecureSettings; @Mock private Context mContext; @Mock private DumpManager mDumpManager; @@ -62,14 +60,11 @@ public class FeatureFlagManagerTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); - mFeatureFlagManager = new FeatureFlagManager(mSecureSettings, mContext, mDumpManager); + mFeatureFlagManager = new FeatureFlagManager(mDumpManager); } @After public void onFinished() { - // SecureSettings and Context are provided for constructor consistency with the - // debug version of the FeatureFlagManager, but should never be used. - verifyZeroInteractions(mSecureSettings, mContext); // The dump manager should be registered with even for the release version, but that's it. verify(mDumpManager).registerDumpable(anyString(), any()); verifyNoMoreInteractions(mDumpManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java deleted file mode 100644 index 30e9b51ea47e..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java +++ /dev/null @@ -1,138 +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. - */ - -package com.android.systemui.flags; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.res.Resources; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.stubbing.Answer; - -@SmallTest -public class FeatureFlagsTest extends SysuiTestCase { - - @Mock Resources mResources; - @Mock FlagReader mFeatureFlagReader; - - private FeatureFlags mFeatureFlags; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - - when(mFeatureFlagReader.isEnabled(anyInt(), anyBoolean())).thenAnswer( - (Answer<Boolean>) invocation -> invocation.getArgument(1)); - - mFeatureFlags = new FeatureFlags(mResources, mFeatureFlagReader, getContext()); - } - - @Test - public void testAddListener() { - Flag<?> flag = new BooleanFlag(1); - mFeatureFlags.addFlag(flag); - - // Assert and capture that a plugin listener was added. - ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor = - ArgumentCaptor.forClass(FlagReader.Listener.class); - verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture()); - FlagReader.Listener pluginListener = pluginListenerCaptor.getValue(); - - // Signal a change. No listeners, so no real effect. - pluginListener.onFlagChanged(flag.getId()); - - // Add a listener for the flag - final Flag<?>[] changedFlag = {null}; - FeatureFlags.Listener listener = f -> changedFlag[0] = f; - mFeatureFlags.addFlagListener(flag, listener); - - // No changes seen yet. - assertThat(changedFlag[0]).isNull(); - - // Signal a change. - pluginListener.onFlagChanged(flag.getId()); - - // Assert that the change was for the correct flag. - assertThat(changedFlag[0]).isEqualTo(flag); - } - - @Test - public void testRemoveListener() { - Flag<?> flag = new BooleanFlag(1); - mFeatureFlags.addFlag(flag); - - // Assert and capture that a plugin listener was added. - ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor = - ArgumentCaptor.forClass(FlagReader.Listener.class); - verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture()); - FlagReader.Listener pluginListener = pluginListenerCaptor.getValue(); - - // Add a listener for the flag - final Flag<?>[] changedFlag = {null}; - FeatureFlags.Listener listener = f -> changedFlag[0] = f; - mFeatureFlags.addFlagListener(flag, listener); - - // Signal a change. - pluginListener.onFlagChanged(flag.getId()); - - // Assert that the change was for the correct flag. - assertThat(changedFlag[0]).isEqualTo(flag); - - changedFlag[0] = null; - - // Now remove the listener. - mFeatureFlags.removeFlagListener(flag, listener); - // Signal a change. - pluginListener.onFlagChanged(flag.getId()); - // Assert that the change was not triggered - assertThat(changedFlag[0]).isNull(); - } - - @Test - public void testBooleanDefault() { - BooleanFlag flag = new BooleanFlag(1, true); - - mFeatureFlags.addFlag(flag); - - assertThat(mFeatureFlags.isEnabled(flag)).isTrue(); - } - - @Test - public void testBooleanResourceOverlay() { - int resourceId = 12; - BooleanFlag flag = new BooleanFlag(1, false, resourceId); - when(mResources.getBoolean(resourceId)).thenReturn(true); - when(mResources.getResourceEntryName(resourceId)).thenReturn("flag"); - - mFeatureFlags.addFlag(flag); - - assertThat(mFeatureFlags.isEnabled(flag)).isTrue(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java index df112840ed87..5a4bb86dba45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java @@ -40,7 +40,6 @@ import com.android.settingslib.Utils; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.BatteryController; import org.junit.After; @@ -69,8 +68,6 @@ public class AnimatableClockControllerTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock - private KeyguardBypassController mBypassController; - @Mock private Resources mResources; private MockitoSession mStaticMockSession; @@ -99,7 +96,6 @@ public class AnimatableClockControllerTest extends SysuiTestCase { mBroadcastDispatcher, mBatteryController, mKeyguardUpdateMonitor, - mBypassController, mResources ); mAnimatableClockController.init(); 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 5e73dbcbc95d..d64319b278b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -212,6 +212,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { @Test public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() { // GIVEN fp sensor location is not available pre-init + when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); when(mAuthController.getUdfpsProps()).thenReturn(null); mLockIconViewController.init(); @@ -232,7 +233,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { } @Test - public void testLockIconViewBackgroundEnabledWhenUdfpsIsAvailable() { + public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() { // GIVEN Udpfs sensor location is available setupUdfps(); @@ -247,9 +248,9 @@ public class LockIconViewControllerTest extends SysuiTestCase { } @Test - public void testLockIconViewBackgroundDisabledWhenUdfpsIsUnavailable() { - // GIVEN Udfps sensor location is not available - when(mAuthController.getUdfpsSensorLocation()).thenReturn(null); + public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() { + // GIVEN Udfps sensor location is not supported + when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); mLockIconViewController.init(); captureAttachListener(); @@ -365,6 +366,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { } private Pair<Integer, PointF> setupUdfps() { + when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true); final PointF udfpsLocation = new PointF(50, 75); final int radius = 33; final FingerprintSensorPropertiesInternal fpProps = diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java index bc86ef98c6fe..8cd7d94d8952 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java @@ -22,6 +22,7 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; +import com.android.internal.graphics.cam.Cam; import com.android.systemui.SysuiTestCase; import org.junit.Assert; @@ -90,4 +91,13 @@ public class ColorSchemeTest extends SysuiTestCase { List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors); Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a, 0xffbe0000, 0xffcc040f)); } + + @Test + public void testTertiaryHueWrapsProperly() { + int colorInt = 0xffB3588A; // H350 C50 T50 + ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */); + int tertiaryMid = colorScheme.getAccent3().get(colorScheme.getAccent3().size() / 2); + Cam cam = Cam.fromInt(tertiaryMid); + Assert.assertEquals(cam.getHue(), 50.0, 10.0); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 01f7fae05f76..cb0d87a20f89 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -701,7 +701,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // GIVEN fingerprint is also running (not udfps) when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - when(mKeyguardUpdateMonitor.isUdfpsAvailable()).thenReturn(false); + when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); mController.setVisible(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index f89bbe898e35..766471b9a695 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -152,7 +152,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test - public void onWallpaperColorsChanged_setsTheme() { + public void onWallpaperColorsChanged_setsTheme_whenForeground() { // Should ask for a new theme when wallpaper colors change WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), Color.valueOf(Color.BLUE), null); @@ -180,13 +180,43 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { // But should change theme after changing wallpapers clearInvocations(mThemeOverlayApplier); - mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED)); + Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED); + intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, true); + mBroadcastReceiver.getValue().onReceive(null, intent); mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), null, null), WallpaperManager.FLAG_SYSTEM); verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); } @Test + public void onWallpaperColorsChanged_setsTheme_skipWhenBackground() { + // Should ask for a new theme when wallpaper colors change + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); + ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays = + ArgumentCaptor.forClass(Map.class); + + verify(mThemeOverlayApplier) + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); + + // Assert that we received the colors that we were expecting + assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) + .isEqualTo(new OverlayIdentifier("ffff0000")); + assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR)) + .isEqualTo(new OverlayIdentifier("ffff0000")); + + // Should not change theme after changing wallpapers, if intent doesn't have + // WallpaperManager.EXTRA_FROM_FOREGROUND_APP set to true. + clearInvocations(mThemeOverlayApplier); + mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED)); + mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), + null, null), WallpaperManager.FLAG_SYSTEM); + verify(mThemeOverlayApplier, never()) + .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + } + + @Test public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() { // Should ask for a new theme when wallpaper colors change WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), @@ -455,7 +485,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { // Regression test: null events should not reset the internal state and allow colors to be // applied again. clearInvocations(mThemeOverlayApplier); - mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED)); + Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED); + intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, true); + mBroadcastReceiver.getValue().onReceive(null, intent); mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM); verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(), any()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index 5c0efd36fcd1..c9462d651bc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -101,6 +101,11 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { // Initial non-set value when(mRingerModeLiveData.getValue()).thenReturn(-1); when(mRingerModeInternalLiveData.getValue()).thenReturn(-1); + // Enable group volume adjustments + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions, + true); + mCallback = mock(VolumeDialogControllerImpl.C.class); mThreadFactory.setLooper(TestableLooper.get(this).getLooper()); mVolumeController = new TestableVolumeDialogControllerImpl(mContext, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index cd743f9524e6..b5f3389c50e0 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -31,7 +31,7 @@ import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManager.PROCESS_STATE_TOP; -import static android.app.ActivityManager.StopBgUsersOnSwitch; +import static android.app.ActivityManager.StopUserOnSwitch; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.AppOpsManager.OP_NONE; @@ -15099,8 +15099,8 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) { - mUserController.setStopBackgroundUsersOnSwitch(value); + public void setStopUserOnSwitch(@StopUserOnSwitch int value) { + mUserController.setStopUserOnSwitch(value); } @Override @@ -16402,8 +16402,8 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void setStopBackgroundUsersOnSwitch(int value) { - ActivityManagerService.this.setStopBackgroundUsersOnSwitch(value); + public void setStopUserOnSwitch(int value) { + ActivityManagerService.this.setStopUserOnSwitch(value); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 60b2149b1f14..31e48fb0837f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -331,7 +331,7 @@ final class ActivityManagerShellCommand extends ShellCommand { case "get-isolated-pids": return runGetIsolatedProcesses(pw); case "set-stop-user-on-switch": - return runSetStopBackgroundUsersOnSwitch(pw); + return runSetStopUserOnSwitch(pw); default: return handleDefaultCommands(cmd); } @@ -3166,25 +3166,24 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } - private int runSetStopBackgroundUsersOnSwitch(PrintWriter pw) throws RemoteException { + private int runSetStopUserOnSwitch(PrintWriter pw) throws RemoteException { mInternal.enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "setStopBackgroundUsersOnSwitch()"); + "setStopUserOnSwitch()"); String arg = getNextArg(); if (arg == null) { - Slogf.i(TAG, "runSetStopBackgroundUsersOnSwitch(): resetting to default value"); - mInternal.setStopBackgroundUsersOnSwitch( - ActivityManager.STOP_BG_USERS_ON_SWITCH_DEFAULT); + Slogf.i(TAG, "setStopUserOnSwitch(): resetting to default value"); + mInternal.setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_DEFAULT); pw.println("Reset to default value"); return 0; } boolean stop = Boolean.parseBoolean(arg); int value = stop - ? ActivityManager.STOP_BG_USERS_ON_SWITCH_TRUE - : ActivityManager.STOP_BG_USERS_ON_SWITCH_FALSE; + ? ActivityManager.STOP_USER_ON_SWITCH_TRUE + : ActivityManager.STOP_USER_ON_SWITCH_FALSE; - Slogf.i(TAG, "runSetStopBackgroundUsersOnSwitch(): setting to %d (%b)", value, stop); - mInternal.setStopBackgroundUsersOnSwitch(value); + Slogf.i(TAG, "runSetStopUserOnSwitch(): setting to %d (%b)", value, stop); + mInternal.setStopUserOnSwitch(value); pw.println("Set to " + stop); return 0; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index b8be6c5f83dd..319fa94058dd 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -19,9 +19,9 @@ package com.android.server.am; import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; -import static android.app.ActivityManager.STOP_BG_USERS_ON_SWITCH_DEFAULT; -import static android.app.ActivityManager.STOP_BG_USERS_ON_SWITCH_TRUE; -import static android.app.ActivityManager.StopBgUsersOnSwitch; +import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT; +import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE; +import static android.app.ActivityManager.StopUserOnSwitch; import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM; import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; import static android.app.ActivityManager.USER_OP_IS_CURRENT; @@ -376,7 +376,7 @@ class UserController implements Handler.Callback { * user is switched. */ @GuardedBy("mLock") - private @StopBgUsersOnSwitch int mStopBgUsersOnSwitch = STOP_BG_USERS_ON_SWITCH_DEFAULT; + private @StopUserOnSwitch int mStopUserOnSwitch = STOP_USER_ON_SWITCH_DEFAULT; UserController(ActivityManagerService service) { this(new Injector(service)); @@ -418,29 +418,27 @@ class UserController implements Handler.Callback { } } - void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) { + void setStopUserOnSwitch(@StopUserOnSwitch int value) { if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS) == PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) == PackageManager.PERMISSION_DENIED) { throw new SecurityException( "You either need MANAGE_USERS or INTERACT_ACROSS_USERS_FULL permission to " - + "call setStopBackgroundUsersOnSwitch()"); + + "call setStopUserOnSwitch()"); } synchronized (mLock) { - Slogf.i(TAG, "setStopBackgroundUsersOnSwitch(): %d -> %d", - mStopBgUsersOnSwitch, value); - mStopBgUsersOnSwitch = value; + Slogf.i(TAG, "setStopUserOnSwitch(): %d -> %d", mStopUserOnSwitch, value); + mStopUserOnSwitch = value; } } - private boolean shouldStopBackgroundUsersOnSwitch() { + private boolean shouldStopUserOnSwitch() { synchronized (mLock) { - if (mStopBgUsersOnSwitch != STOP_BG_USERS_ON_SWITCH_DEFAULT) { - final boolean value = mStopBgUsersOnSwitch == STOP_BG_USERS_ON_SWITCH_TRUE; - Slogf.i(TAG, "isStopBackgroundUsersOnSwitch(): returning overridden value (%b)", - value); + if (mStopUserOnSwitch != STOP_USER_ON_SWITCH_DEFAULT) { + final boolean value = mStopUserOnSwitch == STOP_USER_ON_SWITCH_TRUE; + Slogf.i(TAG, "shouldStopUserOnSwitch(): returning overridden value (%b)", value); return value; } } @@ -1834,7 +1832,7 @@ class UserController implements Handler.Callback { mUserSwitchObservers.finishBroadcast(); } - private void stopBackgroundUsersOnSwitchIfEnforced(@UserIdInt int oldUserId) { + private void stopUserOnSwitchIfEnforced(@UserIdInt int oldUserId) { // Never stop system user if (oldUserId == UserHandle.USER_SYSTEM) { return; @@ -1842,18 +1840,17 @@ class UserController implements Handler.Callback { boolean hasRestriction = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId); synchronized (mLock) { - // If running in background is disabled or mStopBackgroundUsersOnSwitch mode, - // stop the user. - boolean disallowRunInBg = hasRestriction || shouldStopBackgroundUsersOnSwitch(); + // If running in background is disabled or mStopUserOnSwitch mode, stop the user. + boolean disallowRunInBg = hasRestriction || shouldStopUserOnSwitch(); if (!disallowRunInBg) { if (DEBUG_MU) { - Slogf.i(TAG, "stopBackgroundUsersIfEnforced() NOT stopping %d and related " - + "users", oldUserId); + Slogf.i(TAG, "stopUserOnSwitchIfEnforced() NOT stopping %d and related users", + oldUserId); } return; } if (DEBUG_MU) { - Slogf.i(TAG, "stopBackgroundUsersIfEnforced() stopping %d and related users", + Slogf.i(TAG, "stopUserOnSwitchIfEnforced() stopping %d and related users", oldUserId); } stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true, @@ -1956,7 +1953,7 @@ class UserController implements Handler.Callback { mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0)); stopGuestOrEphemeralUserIfBackground(oldUserId); - stopBackgroundUsersOnSwitchIfEnforced(oldUserId); + stopUserOnSwitchIfEnforced(oldUserId); } private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) { @@ -2646,9 +2643,8 @@ class UserController implements Handler.Callback { pw.println(" mTargetUserId:" + mTargetUserId); pw.println(" mLastActiveUsers:" + mLastActiveUsers); pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking); - pw.println(" shouldStopBackgroundUsersOnSwitch():" - + shouldStopBackgroundUsersOnSwitch()); - pw.println(" mStopBgUsersOnSwitch:" + mStopBgUsersOnSwitch); + pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch()); + pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch); pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled); pw.println(" mInitialized:" + mInitialized); diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index e0775d48b42f..f42870b4b734 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1381,7 +1381,8 @@ public class BiometricService extends SystemService { Slog.d(TAG, "handleAuthenticate: modality(" + preAuthStatus.first + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo - + " requestId: " + requestId); + + " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: " + + promptInfo.isIgnoreEnrollmentState()); if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) { // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index cd0ff10168bb..a5a3542f49c7 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -83,6 +83,7 @@ class PreAuthInfo { final List<Pair<BiometricSensor, Integer>> ineligibleSensors; final boolean credentialAvailable; final boolean confirmationRequested; + final boolean ignoreEnrollmentState; static PreAuthInfo create(ITrustManager trustManager, DevicePolicyManager devicePolicyManager, @@ -114,7 +115,8 @@ class PreAuthInfo { @AuthenticatorStatus int status = getStatusForBiometricAuthenticator( devicePolicyManager, settingObserver, sensor, userId, opPackageName, checkDevicePolicyManager, requestedStrength, - promptInfo.getAllowedSensorIds()); + promptInfo.getAllowedSensorIds(), + promptInfo.isIgnoreEnrollmentState()); Slog.d(TAG, "Package: " + opPackageName + " Sensor ID: " + sensor.id @@ -130,7 +132,8 @@ class PreAuthInfo { } return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, - eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested); + eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, + promptInfo.isIgnoreEnrollmentState()); } /** @@ -145,7 +148,8 @@ class PreAuthInfo { BiometricService.SettingObserver settingObserver, BiometricSensor sensor, int userId, String opPackageName, boolean checkDevicePolicyManager, int requestedStrength, - @NonNull List<Integer> requestedSensorIds) { + @NonNull List<Integer> requestedSensorIds, + boolean ignoreEnrollmentState) { if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) { return BIOMETRIC_NO_HARDWARE; @@ -167,7 +171,8 @@ class PreAuthInfo { return BIOMETRIC_HARDWARE_NOT_DETECTED; } - if (!sensor.impl.hasEnrolledTemplates(userId, opPackageName)) { + if (!sensor.impl.hasEnrolledTemplates(userId, opPackageName) + && !ignoreEnrollmentState) { return BIOMETRIC_NOT_ENROLLED; } @@ -238,7 +243,7 @@ class PreAuthInfo { private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List<BiometricSensor> eligibleSensors, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, - boolean confirmationRequested) { + boolean confirmationRequested, boolean ignoreEnrollmentState) { mBiometricRequested = biometricRequested; mBiometricStrengthRequested = biometricStrengthRequested; this.credentialRequested = credentialRequested; @@ -247,6 +252,7 @@ class PreAuthInfo { this.ineligibleSensors = ineligibleSensors; this.credentialAvailable = credentialAvailable; this.confirmationRequested = confirmationRequested; + this.ignoreEnrollmentState = ignoreEnrollmentState; } private Pair<BiometricSensor, Integer> calculateErrorByPriority() { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index f35bb7ffd26b..c5d33ed7400b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -281,7 +281,7 @@ public class FingerprintService extends SystemService { @Override // Binder call public long authenticate(final IBinder token, final long operationId, final int sensorId, final int userId, final IFingerprintServiceReceiver receiver, - final String opPackageName) { + final String opPackageName, boolean ignoreEnrollmentState) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); @@ -333,7 +333,8 @@ public class FingerprintService extends SystemService { && sensorProps != null && sensorProps.isAnyUdfpsType()) { identity = Binder.clearCallingIdentity(); try { - return authenticateWithPrompt(operationId, sensorProps, userId, receiver); + return authenticateWithPrompt(operationId, sensorProps, userId, receiver, + ignoreEnrollmentState); } finally { Binder.restoreCallingIdentity(identity); } @@ -347,7 +348,8 @@ public class FingerprintService extends SystemService { final long operationId, @NonNull final FingerprintSensorPropertiesInternal props, final int userId, - final IFingerprintServiceReceiver receiver) { + final IFingerprintServiceReceiver receiver, + boolean ignoreEnrollmentState) { final Context context = getUiContext(); final Executor executor = context.getMainExecutor(); @@ -368,6 +370,7 @@ public class FingerprintService extends SystemService { }) .setAllowedSensorIds(new ArrayList<>( Collections.singletonList(props.sensorId))) + .setIgnoreEnrollmentState(ignoreEnrollmentState) .build(); final BiometricPrompt.AuthenticationCallback promptCallback = diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 82b34c35cfd2..73baf79ea4b1 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -223,7 +223,7 @@ public class InputMethodMenuController { public Context getSettingsContext(int displayId) { if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) { final Context systemUiContext = ActivityThread.currentActivityThread() - .getSystemUiContext(displayId); + .createSystemUiContext(displayId); final Context windowContext = systemUiContext.createWindowContext( WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */); mSettingsContext = new ContextThemeWrapper( diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index 607218e20ea8..b424c2083bd4 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -146,6 +146,12 @@ public class MediaSession2Record implements MediaSessionRecordImpl { } @Override + public boolean canHandleVolumeKey() { + // TODO: Implement when MediaSession2 starts to get key events. + return false; + } + + @Override public int getSessionPolicies() { synchronized (mLock) { return mPolicies; diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 1525cd4da669..e4ed0e5d4186 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -26,7 +26,9 @@ import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioSystem; import android.media.MediaMetadata; +import android.media.MediaRouter2Manager; import android.media.Rating; +import android.media.RoutingSessionInfo; import android.media.VolumeProvider; import android.media.session.ISession; import android.media.session.ISessionCallback; @@ -50,6 +52,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; +import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; @@ -121,6 +124,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR private final SessionCb mSessionCb; private final MediaSessionService mService; private final Context mContext; + private final boolean mVolumeAdjustmentForRemoteGroupSessions; private final Object mLock = new Object(); private final CopyOnWriteArrayList<ISessionControllerCallbackHolder> @@ -180,6 +184,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mAudioAttrs = DEFAULT_ATTRIBUTES; mPolicies = policies; + mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); // May throw RemoteException if the session app is killed. mSessionCb.mCb.asBinder().linkToDeath(this, 0); @@ -449,6 +455,33 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } @Override + public boolean canHandleVolumeKey() { + if (isPlaybackTypeLocal() || mVolumeAdjustmentForRemoteGroupSessions) { + return true; + } + MediaRouter2Manager mRouter2Manager = MediaRouter2Manager.getInstance(mContext); + List<RoutingSessionInfo> sessions = + mRouter2Manager.getRoutingSessions(mPackageName); + boolean foundNonSystemSession = false; + boolean isGroup = false; + for (RoutingSessionInfo session : sessions) { + if (!session.isSystemSession()) { + foundNonSystemSession = true; + int selectedRouteCount = session.getSelectedRoutes().size(); + if (selectedRouteCount > 1) { + isGroup = true; + break; + } + } + } + if (!foundNonSystemSession) { + Log.d(TAG, "No routing session for " + mPackageName); + return false; + } + return !isGroup; + } + + @Override public int getSessionPolicies() { synchronized (mLock) { return mPolicies; diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java index 3c50597b8cfc..8f01f02f2ab1 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java @@ -131,6 +131,13 @@ public interface MediaSessionRecordImpl extends AutoCloseable { KeyEvent ke, int sequenceId, ResultReceiver cb); /** + * Returns whether the media session can handle volume key events. + * + * @return True if this media session can handle volume key events, false otherwise. + */ + boolean canHandleVolumeKey(); + + /** * Get session policies from custom policy provider set when MediaSessionRecord is instantiated. * If custom policy does not exist, will return null. */ diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java index c4c21df746b3..b75ba75e028b 100644 --- a/services/core/java/com/android/server/media/MediaSessionStack.java +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -325,8 +325,7 @@ class MediaSessionStack { int size = records.size(); for (int i = 0; i < size; i++) { MediaSessionRecord record = records.get(i); - // Do not send the volume key events to remote sessions. - if (record.checkPlaybackActiveState(true) && record.isPlaybackTypeLocal()) { + if (record.checkPlaybackActiveState(true) && record.canHandleVolumeKey()) { mCachedVolumeDefault = record; return record; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2f550c6e9338..47d8022a5acc 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -7699,7 +7699,10 @@ public class NotificationManagerService extends SystemService { final int waitMs = mAudioManager.getFocusRampTimeMs( AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, record.getAudioAttributes()); - if (DBG) Slog.v(TAG, "Delaying vibration by " + waitMs + "ms"); + if (DBG) { + Slog.v(TAG, "Delaying vibration for notification " + + record.getKey() + " by " + waitMs + "ms"); + } try { Thread.sleep(waitMs); } catch (InterruptedException e) { } @@ -7707,9 +7710,17 @@ public class NotificationManagerService extends SystemService { // so need to check the notification still valide for vibrate. synchronized (mNotificationLock) { if (mNotificationsByKey.get(record.getKey()) != null) { - vibrate(record, effect, true); + if (record.getKey().equals(mVibrateNotificationKey)) { + vibrate(record, effect, true); + } else { + if (DBG) { + Slog.v(TAG, "No vibration for notification " + + record.getKey() + ": a new notification is " + + "vibrating, or effects were cleared while waiting"); + } + } } else { - Slog.e(TAG, "No vibration for canceled notification : " + Slog.w(TAG, "No vibration for canceled notification " + record.getKey()); } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index a51ed09790a4..2f353d19b5df 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -17,6 +17,7 @@ package com.android.server.wallpaper; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.WallpaperManager.COMMAND_REAPPLY; import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; @@ -791,6 +792,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private final Context mContext; private final WindowManagerInternal mWindowManagerInternal; private final IPackageManager mIPackageManager; + private final ActivityManager mActivityManager; private final MyPackageMonitor mMonitor; private final AppOpsManager mAppOpsManager; @@ -939,6 +941,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub */ WallpaperColors primaryColors; + /** + * If the wallpaper was set from a foreground app (instead of from a background service). + */ + public boolean fromForegroundApp; + WallpaperConnection connection; long lastDiedTime; boolean wallpaperUpdating; @@ -1688,6 +1695,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mDisplayManager = mContext.getSystemService(DisplayManager.class); mDisplayManager.registerDisplayListener(mDisplayListener, null /* handler */); + mActivityManager = mContext.getSystemService(ActivityManager.class); mMonitor = new MyPackageMonitor(); mColorsChangedListeners = new SparseArray<>(); @@ -2648,6 +2656,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + final boolean fromForegroundApp = Binder.withCleanCallingIdentity(() -> + mActivityManager.getPackageImportance(callingPackage) == IMPORTANCE_FOREGROUND); + synchronized (mLock) { if (DEBUG) Slog.v(TAG, "setWallpaper which=0x" + Integer.toHexString(which)); WallpaperData wallpaper; @@ -2670,6 +2681,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.imageWallpaperPending = true; wallpaper.whichPending = which; wallpaper.setComplete = completion; + wallpaper.fromForegroundApp = fromForegroundApp; wallpaper.cropHint.set(cropHint); wallpaper.allowBackup = allowBackup; } @@ -3052,6 +3064,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.callbacks.finishBroadcast(); final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED); + intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, wallpaper.fromForegroundApp); mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId)); } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index cf9783fb9241..38a48570ba16 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -20,11 +20,11 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATIO import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK; import static android.os.Build.IS_USER; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY; import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER; @@ -1009,6 +1009,8 @@ final class AccessibilityController { final int windowType = windowState.mAttrs.type; if (isExcludedWindowType(windowType) || ((windowState.mAttrs.privateFlags + & PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION) != 0) + || ((windowState.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) { continue; } @@ -1073,7 +1075,6 @@ final class AccessibilityController { } } } - visibleWindows.clear(); mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset, @@ -1110,9 +1111,6 @@ final class AccessibilityController { private boolean isExcludedWindowType(int windowType) { return windowType == TYPE_MAGNIFICATION_OVERLAY - // Omit the touch region to avoid the cut out of the magnification - // bounds because nav bar panel is unmagnifiable. - || windowType == TYPE_NAVIGATION_BAR_PANEL // Omit the touch region of window magnification to avoid the cut out of the // magnification and the magnified center of window magnification could be // in the bounds diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index d1374362505f..f87856255b3e 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -770,10 +770,6 @@ class ActivityMetricsLogger { if (compatStateInfo.mLastLoggedActivity == r) { compatStateInfo.mLastLoggedActivity = null; } - if (compatStateInfo.mVisibleActivities.isEmpty()) { - // No need to keep the entry if there are no visible activities. - mPackageUidToCompatStateInfo.remove(packageUid); - } } /** @@ -1269,13 +1265,14 @@ class ActivityMetricsLogger { * activity. * <li>If the current state is NOT_VISIBLE, there is a previously logged state for the * package UID and there are no other visible activities with the same package UID. - * <li>The last logged activity with the same package UID is either {@code activity} or the - * last logged state is NOT_VISIBLE or NOT_LETTERBOXED. + * <li>The last logged activity with the same package UID is either {@code activity} (or an + * activity that has been removed) or the last logged state is NOT_VISIBLE or NOT_LETTERBOXED. * </ul> * * <p>If the current state is NOT_VISIBLE and the previous state which was logged by {@code - * activity} wasn't, looks for the first visible activity with the same package UID that has - * a letterboxed state, or a non-letterboxed state if there isn't one, and logs that state. + * activity} (or an activity that has been removed) wasn't, looks for the first visible activity + * with the same package UID that has a letterboxed state, or a non-letterboxed state if + * there isn't one, and logs that state. * * <p>This method assumes that the caller is wrapping the call with a synchronized block so * that there won't be a race condition between two activities with the same package. @@ -1311,14 +1308,14 @@ class ActivityMetricsLogger { if (!isVisible && !visibleActivities.isEmpty()) { // There is another visible activity for this package UID. - if (activity == lastLoggedActivity) { + if (lastLoggedActivity == null || activity == lastLoggedActivity) { // Make sure a new visible state is logged if needed. findAppCompatStateToLog(compatStateInfo, packageUid); } return; } - if (activity != lastLoggedActivity + if (lastLoggedActivity != null && activity != lastLoggedActivity && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED) { // Another visible activity for this package UID has logged a letterboxed state. @@ -1332,15 +1329,25 @@ class ActivityMetricsLogger { * Looks for the first visible activity in {@code compatStateInfo} that has a letterboxed * state, or a non-letterboxed state if there isn't one, and logs that state for the given * {@code packageUid}. + * + * <p>If there is a visible activity in {@code compatStateInfo} with the same state as the + * last logged state for the given {@code packageUid}, changes the last logged activity to + * reference the first such activity without actually logging the same state twice. */ private void findAppCompatStateToLog(PackageCompatStateInfo compatStateInfo, int packageUid) { final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities; + final int lastLoggedState = compatStateInfo.mLastLoggedState; ActivityRecord activityToLog = null; int stateToLog = APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; for (int i = 0; i < visibleActivities.size(); i++) { ActivityRecord activity = visibleActivities.get(i); int state = activity.getAppCompatState(); + if (state == lastLoggedState) { + // Change last logged activity without logging the same state twice. + compatStateInfo.mLastLoggedActivity = activity; + return; + } if (state == APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) { // This shouldn't happen. Slog.w(TAG, "Visible activity with NOT_VISIBLE App Compat state for package UID: " diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 535a061ee4ab..f94777339fae 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -934,6 +934,10 @@ public class AppTransitionController { voiceInteraction); applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp, voiceInteraction); + final RecentsAnimationController rac = mService.getRecentsAnimationController(); + if (rac != null) { + rac.sendTasksAppeared(); + } for (int i = 0; i < openingApps.size(); ++i) { openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 9ef3ef2c0705..29b1a80b7b58 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -34,7 +34,6 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.os.Build.VERSION_CODES.N; -import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.util.DisplayMetrics.DENSITY_DEFAULT; import static android.util.RotationUtils.deltaRotation; @@ -63,7 +62,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; -import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; @@ -4998,12 +4996,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp reconfigureDisplayLocked(); onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); mWmService.mDisplayNotificationController.dispatchDisplayAdded(this); - // Attach the SystemUiContext to this DisplayContent the get latest configuration. - // Note that the SystemUiContext will be removed automatically if this DisplayContent - // is detached. - mWmService.mWindowContextListenerController.registerWindowContainerListener( - getDisplayUiContext().getWindowContextToken(), this, SYSTEM_UID, - INVALID_WINDOW_TYPE, null /* options */); } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 1d3c56efaf24..296bd7882ae6 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -112,6 +112,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; import android.app.ActivityManager; +import android.app.ActivityThread; +import android.app.LoadedApk; +import android.app.ResourcesManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -448,7 +451,7 @@ public class DisplayPolicy { : service.mContext.createDisplayContext(displayContent.getDisplay()); mUiContext = displayContent.isDefaultDisplay ? service.mAtmService.mUiContext : service.mAtmService.mSystemThread - .getSystemUiContext(displayContent.getDisplayId()); + .createSystemUiContext(displayContent.getDisplayId()); mDisplayContent = displayContent; mLock = service.getWindowManagerLock(); @@ -2254,8 +2257,19 @@ public class DisplayPolicy { // For non-system users, ensure that the resources are loaded from the current // user's package info (see ContextImpl.createDisplayContext) - mCurrentUserResources = uiContext.createContextAsUser(UserHandle.of(userId), 0 /* flags*/) - .getResources(); + final LoadedApk pi = ActivityThread.currentActivityThread().getPackageInfo( + uiContext.getPackageName(), null, 0, userId); + mCurrentUserResources = ResourcesManager.getInstance().getResources(null, + pi.getResDir(), + null /* splitResDirs */, + pi.getOverlayDirs(), + pi.getOverlayPaths(), + pi.getApplicationInfo().sharedLibraryFiles, + mDisplayContent.getDisplayId(), + null /* overrideConfig */, + uiContext.getResources().getCompatibilityInfo(), + null /* classLoader */, + null /* loaders */); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java index 081a53e7bd05..fe21e5f0011e 100644 --- a/services/core/java/com/android/server/wm/PackageConfigPersister.java +++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java @@ -16,10 +16,8 @@ package com.android.server.wm; -import static android.app.UiModeManager.MODE_NIGHT_AUTO; -import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; - import android.annotation.NonNull; +import android.content.res.Configuration; import android.os.Environment; import android.os.LocaleList; import android.util.AtomicFile; @@ -303,7 +301,7 @@ public class PackageConfigPersister { } boolean isResetNightMode() { - return mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM; + return mNightMode == Configuration.UI_MODE_NIGHT_UNDEFINED; } @Override diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java index b4963c5b9f1c..b54208d11974 100644 --- a/services/core/java/com/android/server/wm/PinnedTaskController.java +++ b/services/core/java/com/android/server/wm/PinnedTaskController.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -173,10 +172,8 @@ class PinnedTaskController { * to avoid flickering when running PiP animation across different orientations. */ void deferOrientationChangeForEnteringPipFromFullScreenIfNeeded() { - final Task topFullscreenTask = mDisplayContent.getDefaultTaskDisplayArea() - .getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN); - final ActivityRecord topFullscreen = topFullscreenTask != null - ? topFullscreenTask.topRunningActivity() : null; + final ActivityRecord topFullscreen = mDisplayContent.getActivity( + a -> a.fillsParent() && !a.getTask().inMultiWindowMode()); if (topFullscreen == null || topFullscreen.hasFixedRotationTransform()) { return; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 2057b1cdf24b..fd4b63e26403 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; @@ -163,6 +164,8 @@ public class RecentsAnimationController implements DeathRecipient { private boolean mNavigationBarAttachedToApp; private ActivityRecord mNavBarAttachedApp; + private final ArrayList<RemoteAnimationTarget> mPendingTaskAppears = new ArrayList<>(); + /** * An app transition listener to cancel the recents animation only after the app transition * starts or is canceled. @@ -732,11 +735,19 @@ public class RecentsAnimationController implements DeathRecipient { return; } ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target); - try { - mRunner.onTaskAppeared(target); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to report task appeared", e); - } + mPendingTaskAppears.add(target); + } + } + + void sendTasksAppeared() { + if (mPendingTaskAppears.isEmpty() || mRunner == null) return; + try { + final RemoteAnimationTarget[] targets = mPendingTaskAppears.toArray( + new RemoteAnimationTarget[0]); + mRunner.onTasksAppeared(targets); + mPendingTaskAppears.clear(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to report task appeared", e); } } @@ -744,10 +755,15 @@ public class RecentsAnimationController implements DeathRecipient { OnAnimationFinishedCallback finishedCallback) { final SparseBooleanArray recentTaskIds = mService.mAtmService.getRecentTasks().getRecentTaskIds(); + // The target must be built off the root task (the leaf task surface would be cropped + // within the root surface). However, recents only tracks leaf task ids, so we'll replace + // the task-id with the leaf id. + final Task leafTask = task.getTopLeafTask(); + int taskId = leafTask.mTaskId; TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task, - !recentTaskIds.get(task.mTaskId), true /* hidden */, finishedCallback); - mPendingNewTaskTargets.add(task.mTaskId); - return adapter.createRemoteAnimationTarget(); + !recentTaskIds.get(taskId), true /* hidden */, finishedCallback); + mPendingNewTaskTargets.add(taskId); + return adapter.createRemoteAnimationTarget(taskId); } void logRecentsAnimationStartTime(int durationMs) { @@ -782,7 +798,8 @@ public class RecentsAnimationController implements DeathRecipient { final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i); - final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationTarget(); + final RemoteAnimationTarget target = + taskAdapter.createRemoteAnimationTarget(INVALID_TASK_ID); if (target != null) { targets.add(target); } else { @@ -995,6 +1012,8 @@ public class RecentsAnimationController implements DeathRecipient { removeAnimation(taskAdapter); taskAdapter.onCleanup(); } + // Should already be empty, but clean-up pending task-appears in-case they weren't sent. + mPendingTaskAppears.clear(); for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) { final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i); @@ -1224,7 +1243,14 @@ public class RecentsAnimationController implements DeathRecipient { mLocalBounds.offsetTo(tmpPos.x, tmpPos.y); } - RemoteAnimationTarget createRemoteAnimationTarget() { + /** + * @param overrideTaskId overrides the target's taskId. It may differ from mTaskId and thus + * can differ from taskInfo. This mismatch is needed, however, in + * some cases where we are animating root tasks but need need leaf + * ids for identification. If this is INVALID (-1), then mTaskId + * will be used. + */ + RemoteAnimationTarget createRemoteAnimationTarget(int overrideTaskId) { final ActivityRecord topApp = mTask.getTopVisibleActivity(); final WindowState mainWindow = topApp != null ? topApp.findMainWindow() @@ -1238,7 +1264,10 @@ public class RecentsAnimationController implements DeathRecipient { final int mode = topApp.getActivityType() == mTargetActivityType ? MODE_OPENING : MODE_CLOSING; - mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash, + if (overrideTaskId < 0) { + overrideTaskId = mTask.mTaskId; + } + mTarget = new RemoteAnimationTarget(overrideTaskId, mode, mCapturedLeash, !topApp.fillsParent(), new Rect(), insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top), mLocalBounds, mBounds, mTask.getWindowConfiguration(), diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 3ffa62dbbe7e..b1eca9d5d4e4 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2585,7 +2585,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // starts. Instead, we expect home activities to be launched when the system is ready // (ActivityManagerService#systemReady). if (mService.isBooted() || mService.isBooting()) { - startSystemDecorations(display); + startSystemDecorations(display.mDisplayContent); } // Drop any cached DisplayInfos associated with this display id - the values are now // out of date given this display added event. diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a799e7c18703..1b7a012094f6 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1126,7 +1126,11 @@ class Task extends TaskFragment { if (inMultiWindowMode() || !hasChild()) return false; if (intent != null) { final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME; - return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags; + final Task task = getDisplayArea() != null ? getDisplayArea().getRootHomeTask() : null; + final boolean isLockTaskModeViolation = task != null + && mAtmService.getLockTaskController().isLockTaskModeViolation(task); + return (intent.getFlags() & returnHomeFlags) == returnHomeFlags + && !isLockTaskModeViolation; } final Task bottomTask = getBottomMostTask(); return bottomTask != this && bottomTask.returnsToHomeRootTask(); @@ -2377,6 +2381,16 @@ class Task extends TaskFragment { return true; } + /** Return the top-most leaf-task under this one, or this task if it is a leaf. */ + public Task getTopLeafTask() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final Task child = mChildren.get(i).asTask(); + if (child == null) continue; + return child.getTopLeafTask(); + } + return this; + } + int getDescendantTaskCount() { final int[] currentCount = {0}; final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; }, diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java index 86e356a876b5..bc530416c8cd 100644 --- a/services/core/java/com/android/server/wm/WindowContextListenerController.java +++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java @@ -45,7 +45,7 @@ import java.util.Objects; * * <ul> * <li>When a {@link WindowContext} is created, it registers the listener via - * {@link WindowManagerService#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)} + * {@link WindowManagerService#registerWindowContextListener(IBinder, int, int, Bundle)} * automatically.</li> * <li>When the {@link WindowContext} adds the first window to the screen via * {@link android.view.WindowManager#addView(View, android.view.ViewGroup.LayoutParams)}, @@ -53,7 +53,7 @@ import java.util.Objects; * to corresponding {@link WindowToken} via this controller.</li> * <li>When the {@link WindowContext} is GCed, it unregisters the previously * registered listener via - * {@link WindowManagerService#detachWindowContextFromWindowContainer(IBinder)}. + * {@link WindowManagerService#unregisterWindowContextListener(IBinder)}. * {@link WindowManagerService} is also responsible for removing the * {@link WindowContext} created {@link WindowToken}.</li> * </ul> @@ -68,7 +68,7 @@ class WindowContextListenerController { /** * Registers the listener to a {@code container} which is associated with - * a {@code clientToken}, which is a {@link android.window.WindowContext} representation. If the + * a {@code clientToken}, which is a {@link android.app.WindowContext} representation. If the * listener associated with {@code clientToken} hasn't been initialized yet, create one * {@link WindowContextListenerImpl}. Otherwise, the listener associated with * {@code clientToken} switches to listen to the {@code container}. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 3edcd5cdf013..9bede979880a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2741,9 +2741,6 @@ public class WindowManagerService extends IWindowManager.Stub @Override public Configuration attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId, Bundle options) { - if (clientToken == null) { - throw new IllegalArgumentException("clientToken must not be null!"); - } final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS, "attachWindowContextToDisplayArea", false /* printLog */); final int callingUid = Binder.getCallingUid(); @@ -2834,39 +2831,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - @Override - public Configuration attachToDisplayContent(IBinder clientToken, int displayId) { - if (clientToken == null) { - throw new IllegalArgumentException("clientToken must not be null!"); - } - final int callingUid = Binder.getCallingUid(); - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (mGlobalLock) { - // We use "getDisplayContent" instead of "getDisplayContentOrCreate" because - // this method may be called in DisplayPolicy's constructor and may cause - // infinite loop. In this scenario, we early return here and switch to do the - // registration in DisplayContent#onParentChanged at DisplayContent initialization. - final DisplayContent dc = mRoot.getDisplayContent(displayId); - if (dc == null) { - if (Binder.getCallingPid() != myPid()) { - throw new WindowManager.InvalidDisplayException("attachToDisplayContent: " - + "trying to attach to a non-existing display:" + displayId); - } - // Early return if this method is invoked from system process. - // See above comments for more detail. - return null; - } - - mWindowContextListenerController.registerWindowContainerListener(clientToken, dc, - callingUid, INVALID_WINDOW_TYPE, null /* options */); - return dc.getConfiguration(); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - /** Returns {@code true} if this binder is a registered window token. */ @Override public boolean isWindowToken(IBinder binder) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 5dd01a5b46ec..bd8d1164ebef 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -581,27 +581,130 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub task.getDisplayArea().setLaunchAdjacentFlagRootTask(clearRoot ? null : task); break; } - case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: + case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: { effects |= setAdjacentRootsHierarchyOp(hop); break; - } - // The following operations may change task order so they are skipped while in lock task - // mode. The above operations are still allowed because they don't move tasks. And it may - // be necessary such as clearing launch root after entering lock task mode. - if (isInLockTaskMode) { - Slog.w(TAG, "Skip applying hierarchy operation " + hop + " while in lock task mode"); - return effects; + } + case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: { + final TaskFragmentCreationParams taskFragmentCreationOptions = + hop.getTaskFragmentCreationOptions(); + createTaskFragment(taskFragmentCreationOptions, errorCallbackToken); + break; + } + case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: { + final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); + if (wc == null || !wc.isAttached()) { + Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc); + break; + } + final TaskFragment taskFragment = wc.asTaskFragment(); + if (taskFragment == null || taskFragment.asTask() != null) { + throw new IllegalArgumentException( + "Can only delete organized TaskFragment, but not Task."); + } + if (isInLockTaskMode) { + final ActivityRecord bottomActivity = taskFragment.getActivity( + a -> !a.finishing, false /* traverseTopToBottom */); + if (bottomActivity != null + && mService.getLockTaskController().activityBlockedFromFinish( + bottomActivity)) { + Slog.w(TAG, "Skip removing TaskFragment due in lock task mode."); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, + new IllegalStateException( + "Not allow to delete task fragment in lock task mode.")); + break; + } + } + effects |= deleteTaskFragment(taskFragment, errorCallbackToken); + break; + } + case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: { + final IBinder fragmentToken = hop.getContainer(); + if (!mLaunchTaskFragments.containsKey(fragmentToken)) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to operate with invalid fragment token"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } + final Intent activityIntent = hop.getActivityIntent(); + final Bundle activityOptions = hop.getLaunchOptions(); + final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken); + final int result = mService.getActivityStartController() + .startActivityInTaskFragment(tf, activityIntent, activityOptions, + hop.getCallingActivity()); + if (!isStartResultSuccessful(result)) { + sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(), + errorCallbackToken, + convertStartFailureToThrowable(result, activityIntent)); + } + break; + } + case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { + final IBinder fragmentToken = hop.getNewParent(); + final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer()); + if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to operate with invalid fragment token or activity."); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } + activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + break; + } + case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS: { + final IBinder fragmentToken = hop.getContainer(); + final IBinder adjacentFragmentToken = hop.getAdjacentRoot(); + final TaskFragment tf1 = mLaunchTaskFragments.get(fragmentToken); + final TaskFragment tf2 = adjacentFragmentToken != null + ? mLaunchTaskFragments.get(adjacentFragmentToken) + : null; + if (tf1 == null || (adjacentFragmentToken != null && tf2 == null)) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to set adjacent on invalid fragment tokens"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } + tf1.setAdjacentTaskFragment(tf2); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + + final Bundle bundle = hop.getLaunchOptions(); + final WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = + bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentParams( + bundle) : null; + if (adjacentParams == null) { + break; + } + + tf1.setDelayLastActivityRemoval( + adjacentParams.shouldDelayPrimaryLastActivityRemoval()); + if (tf2 != null) { + tf2.setDelayLastActivityRemoval( + adjacentParams.shouldDelaySecondaryLastActivityRemoval()); + } + break; + } + default: { + // The other operations may change task order so they are skipped while in lock + // task mode. The above operations are still allowed because they don't move + // tasks. And it may be necessary such as clearing launch root after entering + // lock task mode. + if (isInLockTaskMode) { + Slog.w(TAG, "Skip applying hierarchy operation " + hop + + " while in lock task mode"); + return effects; + } + } } - final WindowContainer wc; - final IBinder fragmentToken; switch (type) { - case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: + case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: { effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId); break; + } case HIERARCHY_OP_TYPE_REORDER: - case HIERARCHY_OP_TYPE_REPARENT: - wc = WindowContainer.fromBinder(hop.getContainer()); + case HIERARCHY_OP_TYPE_REPARENT: { + final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); if (wc == null || !wc.isAttached()) { Slog.e(TAG, "Attempt to operate on detached container: " + wc); break; @@ -630,7 +733,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } effects |= sanitizeAndApplyHierarchyOp(wc, hop); break; - case HIERARCHY_OP_TYPE_LAUNCH_TASK: + } + case HIERARCHY_OP_TYPE_LAUNCH_TASK: { mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS, "launchTask HierarchyOp"); final Bundle launchOpts = hop.getLaunchOptions(); @@ -639,7 +743,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID); final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid); - final Integer[] starterResult = { null }; + final Integer[] starterResult = {null}; // startActivityFromRecents should not be called in lock. mService.mH.post(() -> { try { @@ -660,10 +764,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } break; - case HIERARCHY_OP_TYPE_PENDING_INTENT: + } + case HIERARCHY_OP_TYPE_PENDING_INTENT: { String resolvedType = hop.getActivityIntent() != null ? hop.getActivityIntent().resolveTypeIfNeeded( - mService.mContext.getContentResolver()) + mService.mContext.getContentResolver()) : null; Bundle options = null; @@ -683,57 +788,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub hop.getActivityIntent(), resolvedType, null /* finishReceiver */, null /* requiredPermission */, options); break; - case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: - final TaskFragmentCreationParams taskFragmentCreationOptions = - hop.getTaskFragmentCreationOptions(); - createTaskFragment(taskFragmentCreationOptions, errorCallbackToken); - break; - case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: - wc = WindowContainer.fromBinder(hop.getContainer()); - if (wc == null || !wc.isAttached()) { - Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc); - break; - } - final TaskFragment taskFragment = wc.asTaskFragment(); - if (taskFragment == null || taskFragment.asTask() != null) { - throw new IllegalArgumentException( - "Can only delete organized TaskFragment, but not Task."); - } - effects |= deleteTaskFragment(taskFragment, errorCallbackToken); - break; - case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: - fragmentToken = hop.getContainer(); - if (!mLaunchTaskFragments.containsKey(fragmentToken)) { - final Throwable exception = new IllegalArgumentException( - "Not allowed to operate with invalid fragment token"); - sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); - break; - } - final Intent activityIntent = hop.getActivityIntent(); - final Bundle activityOptions = hop.getLaunchOptions(); - final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken); - final int result = mService.getActivityStartController() - .startActivityInTaskFragment(tf, activityIntent, activityOptions, - hop.getCallingActivity()); - if (!isStartResultSuccessful(result)) { - sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(), - errorCallbackToken, - convertStartFailureToThrowable(result, activityIntent)); - } - break; - case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: - fragmentToken = hop.getNewParent(); - final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer()); - if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) { - final Throwable exception = new IllegalArgumentException( - "Not allowed to operate with invalid fragment token or activity."); - sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); - break; - } - activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP); - effects |= TRANSACT_EFFECTS_LIFECYCLE; - break; - case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: + } + case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: { final WindowContainer oldParent = WindowContainer.fromBinder(hop.getContainer()); final WindowContainer newParent = hop.getNewParent() != null ? WindowContainer.fromBinder(hop.getNewParent()) @@ -746,37 +802,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub reparentTaskFragment(oldParent, newParent, errorCallbackToken); effects |= TRANSACT_EFFECTS_LIFECYCLE; break; - case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS: - fragmentToken = hop.getContainer(); - final IBinder adjacentFragmentToken = hop.getAdjacentRoot(); - final TaskFragment tf1 = mLaunchTaskFragments.get(fragmentToken); - final TaskFragment tf2 = adjacentFragmentToken != null - ? mLaunchTaskFragments.get(adjacentFragmentToken) - : null; - if (tf1 == null || (adjacentFragmentToken != null && tf2 == null)) { - final Throwable exception = new IllegalArgumentException( - "Not allowed to set adjacent on invalid fragment tokens"); - sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); - break; - } - tf1.setAdjacentTaskFragment(tf2); - effects |= TRANSACT_EFFECTS_LIFECYCLE; - - final Bundle bundle = hop.getLaunchOptions(); - final WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = - bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentParams( - bundle) : null; - if (adjacentParams == null) { - break; - } - - tf1.setDelayLastActivityRemoval( - adjacentParams.shouldDelayPrimaryLastActivityRemoval()); - if (tf2 != null) { - tf2.setDelayLastActivityRemoval( - adjacentParams.shouldDelaySecondaryLastActivityRemoval()); - } - break; + } } return effects; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 8042841f45ca..db13ae275965 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -12752,6 +12752,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent( + @NonNull UserHandle userHandle) { + return DevicePolicyManagerService.this.getProfileOwnerOrDeviceOwnerSupervisionComponent( + userHandle); + } + + @Override public boolean isActiveDeviceOwner(int uid) { return isDeviceOwner(new CallerIdentity(uid, null, null)); } diff --git a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java index 24c58f49bed6..7358551d1bc5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java @@ -72,7 +72,7 @@ public class UserUsageStatsServiceTest { HashMap<String, Long> installedPkgs = new HashMap<>(); installedPkgs.put(TEST_PACKAGE_NAME, System.currentTimeMillis()); - mService.init(System.currentTimeMillis(), installedPkgs); + mService.init(System.currentTimeMillis(), installedPkgs, true); } @After diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 2a5bb18ae428..df975cda54a5 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -40,7 +40,6 @@ import android.graphics.drawable.Icon; import android.os.IBinder; import android.os.UserHandle; import android.provider.Settings; -import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -54,13 +53,16 @@ import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** * APCT tests for {@link AccessibilityManagerService}. */ -public class AccessibilityManagerServiceTest extends AndroidTestCase { +public class AccessibilityManagerServiceTest { private static final String TAG = "A11Y_MANAGER_SERVICE_TEST"; private static final int ACTION_ID = 20; private static final String LABEL = "label"; @@ -104,8 +106,8 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { private AccessibilityServiceConnection mAccessibilityServiceConnection; private AccessibilityManagerService mA11yms; - @Override - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); @@ -167,44 +169,48 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { } @SmallTest + @Test public void testRegisterSystemActionWithoutPermission() throws Exception { doThrow(SecurityException.class).when(mMockSecurityPolicy) .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); try { mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID); - fail(); + Assert.fail(); } catch (SecurityException expected) { } verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION); } @SmallTest + @Test public void testRegisterSystemAction() throws Exception { mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID); verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION); } - @SmallTest + @Test public void testUnregisterSystemActionWithoutPermission() throws Exception { doThrow(SecurityException.class).when(mMockSecurityPolicy) .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); try { mA11yms.unregisterSystemAction(ACTION_ID); - fail(); + Assert.fail(); } catch (SecurityException expected) { } verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID); } @SmallTest + @Test public void testUnregisterSystemAction() throws Exception { mA11yms.unregisterSystemAction(ACTION_ID); verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID); } @SmallTest + @Test public void testOnSystemActionsChanged() throws Exception { setupAccessibilityServiceConnection(); mA11yms.notifySystemActionsChangedLocked(mUserState); @@ -213,6 +219,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { } @SmallTest + @Test public void testOnMagnificationTransitionFailed_capabilitiesIsAll_fallBackToPreviousMode() { final AccessibilityUserState userState = mA11yms.mUserStates.get( mA11yms.getCurrentUserIdLocked()); @@ -223,7 +230,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { mA11yms.onMagnificationTransitionEndedLocked(false); - assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, + Assert.assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, userState.getMagnificationModeLocked()); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index ea46eab6e8f9..d593e8000048 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -32,7 +32,6 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -40,6 +39,7 @@ import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.after; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -48,6 +48,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; @@ -73,7 +74,6 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; -import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; @@ -102,6 +102,7 @@ import java.util.Objects; @SmallTest @RunWith(AndroidJUnit4.class) +@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. public class BuzzBeepBlinkTest extends UiServiceTestCase { @Mock AudioManager mAudioManager; @@ -156,6 +157,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); + when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50); when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); when(mVibrator.hasFrequencyControl()).thenReturn(false); when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false); @@ -444,6 +446,11 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { timeout(MAX_VIBRATION_DELAY).times(1)); } + private void verifyDelayedNeverVibrate() { + verify(mVibrator, after(MAX_VIBRATION_DELAY).never()).vibrate(anyInt(), anyString(), any(), + anyString(), any(AudioAttributes.class)); + } + private void verifyVibrate(ArgumentMatcher<VibrationEffect> effectMatcher, VerificationMode verification) { ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class); @@ -1588,8 +1595,51 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // beep wasn't reset verifyNeverBeep(); verifyNeverVibrate(); - verify(mRingtonePlayer, never()).stopAsync(); - verify(mVibrator, never()).cancel(); + verifyNeverStopAudio(); + verifyNeverStopVibrate(); + } + + @Test + public void testRingtoneInsistentBeep_clearEffectsStopsSoundAndVibration() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"), + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification)); + mService.buzzBeepBlinkLocked(ringtoneNotification); + verifyBeepLooped(); + verifyDelayedVibrateLooped(); + + mService.clearSoundLocked(); + mService.clearVibrateLocked(); + + verifyStopAudio(); + verifyStopVibrate(); + } + + @Test + public void testRingtoneInsistentBeep_neverVibratesWhenEffectsClearedBeforeDelay() + throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"), + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification)); + mService.buzzBeepBlinkLocked(ringtoneNotification); + verifyBeepLooped(); + verifyNeverVibrate(); + + mService.clearSoundLocked(); + mService.clearVibrateLocked(); + + verifyStopAudio(); + verifyDelayedNeverVibrate(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java index d7daa57cc9da..a8ede13e5de6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java @@ -29,9 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import android.app.ActivityThread; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; @@ -46,7 +44,6 @@ import android.view.WindowManagerGlobal; import com.android.server.inputmethod.InputMethodManagerService; import com.android.server.inputmethod.InputMethodMenuController; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -65,9 +62,6 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { private InputMethodMenuController mController; private DualDisplayAreaGroupPolicyTest.DualDisplayContent mSecondaryDisplay; - private IWindowManager mIWindowManager; - private DisplayManagerGlobal mDisplayManagerGlobal; - @Before public void setUp() throws Exception { // Let the Display to be created with the DualDisplay policy. @@ -76,12 +70,10 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); mController = new InputMethodMenuController(mock(InputMethodManagerService.class)); - mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent - .Builder(mAtm, 1000, 1000).build(); // Mock addWindowTokenWithOptions to create a test window token. - mIWindowManager = WindowManagerGlobal.getWindowManagerService(); - spyOn(mIWindowManager); + IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); + spyOn(wms); doAnswer(invocation -> { Object[] args = invocation.getArguments(); IBinder clientToken = (IBinder) args[0]; @@ -91,24 +83,19 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG, null /* options */); return dc.getImeContainer().getConfiguration(); - }).when(mIWindowManager).attachWindowContextToDisplayArea(any(), - eq(TYPE_INPUT_METHOD_DIALOG), anyInt(), any()); - mDisplayManagerGlobal = DisplayManagerGlobal.getInstance(); - spyOn(mDisplayManagerGlobal); + }).when(wms).attachWindowContextToDisplayArea(any(), eq(TYPE_INPUT_METHOD_DIALOG), + anyInt(), any()); + + mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent + .Builder(mAtm, 1000, 1000).build(); + + // Mock DisplayManagerGlobal to return test display when obtaining Display instance. final int displayId = mSecondaryDisplay.getDisplayId(); final Display display = mSecondaryDisplay.getDisplay(); - doReturn(display).when(mDisplayManagerGlobal).getCompatibleDisplay(eq(displayId), + DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance(); + spyOn(displayManagerGlobal); + doReturn(display).when(displayManagerGlobal).getCompatibleDisplay(eq(displayId), (Resources) any()); - Context systemUiContext = ActivityThread.currentActivityThread() - .getSystemUiContext(displayId); - spyOn(systemUiContext); - doReturn(display).when(systemUiContext).getDisplay(); - } - - @After - public void tearDown() { - reset(mIWindowManager); - reset(mDisplayManagerGlobal); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 9c04560bc65f..9639aa78fd5b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1084,6 +1084,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioScreenOrientationNotSetThenChangedToPortrait() { // In this test, the activity's orientation isn't fixed to portrait, therefore the override @@ -1115,6 +1116,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioScreenOrientationLandscapeThenChangedToPortrait() { // In this test, the activity's orientation isn't fixed to portrait, therefore the override @@ -1147,6 +1149,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioScreenOrientationPortraitThenChangedToUnspecified() { setUpDisplaySizeWithApp(1000, 1200); @@ -1175,6 +1178,52 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) + @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY}) + public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationNotSet() { + setUpDisplaySizeWithApp(1000, 1200); + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + // The per-package override forces the activity into a 3:2 aspect ratio + assertEquals(1200, activity.getBounds().height()); + assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, + activity.getBounds().width(), 0.5); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) + @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY}) + public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationLandscape() { + // In this test, the activity's orientation isn't fixed to portrait, therefore the override + // isn't applied. + + setUpDisplaySizeWithApp(1000, 1200); + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + // The per-package override forces the activity into a 3:2 aspect ratio + assertEquals(1000 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, + activity.getBounds().height(), 0.5); + assertEquals(1000, activity.getBounds().width()); + } + + @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioWithoutGlobalOverride() { // In this test, only OVERRIDE_MIN_ASPECT_RATIO_1_5 is set, which has no effect without diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 1b8492722c10..ac1fcce20dc0 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -380,6 +380,7 @@ public class UsageStatsService extends SystemService implements if (userId == UserHandle.USER_SYSTEM) { UsageStatsIdleService.scheduleUpdateMappingsJob(getContext()); } + final boolean deleteObsoleteData = shouldDeleteObsoleteData(UserHandle.of(userId)); synchronized (mLock) { // This should be safe to add this early. Other than reportEventOrAddToQueue, every // other user grabs the lock before accessing @@ -402,7 +403,7 @@ public class UsageStatsService extends SystemService implements boolean needToFlush = !pendingEvents.isEmpty(); initializeUserUsageStatsServiceLocked(userId, System.currentTimeMillis(), - installedPackages); + installedPackages, deleteObsoleteData); final UserUsageStatsService userService = getUserUsageStatsServiceLocked(userId); if (userService == null) { Slog.i(TAG, "Attempted to unlock stopped or removed user " + userId); @@ -596,13 +597,13 @@ public class UsageStatsService extends SystemService implements * when the user is initially unlocked. */ private void initializeUserUsageStatsServiceLocked(int userId, long currentTimeMillis, - HashMap<String, Long> installedPackages) { + HashMap<String, Long> installedPackages, boolean deleteObsoleteData) { final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId), "usagestats"); final UserUsageStatsService service = new UserUsageStatsService(getContext(), userId, usageStatsDir, this); try { - service.init(currentTimeMillis, installedPackages); + service.init(currentTimeMillis, installedPackages, deleteObsoleteData); mUserState.put(userId, service); } catch (Exception e) { if (mUserManager.isUserUnlocked(userId)) { @@ -1165,6 +1166,10 @@ public class UsageStatsService extends SystemService implements * Called by the Binder stub. */ private boolean updatePackageMappingsData() { + // don't update the mappings if a profile user is defined + if (!shouldDeleteObsoleteData(UserHandle.SYSTEM)) { + return true; // return true so job scheduler doesn't reschedule the job + } // fetch the installed packages outside the lock so it doesn't block package manager. final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM); synchronized (mLock) { @@ -1309,6 +1314,13 @@ public class UsageStatsService extends SystemService implements } } + private boolean shouldDeleteObsoleteData(UserHandle userHandle) { + final DevicePolicyManagerInternal dpmInternal = getDpmInternal(); + // If a profile owner is not defined for the given user, obsolete data should be deleted + return dpmInternal == null + || dpmInternal.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle) == null; + } + private String buildFullToken(String packageName, String token) { final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1); sb.append(packageName); @@ -2532,8 +2544,12 @@ public class UsageStatsService extends SystemService implements private class MyPackageMonitor extends PackageMonitor { @Override public void onPackageRemoved(String packageName, int uid) { - mHandler.obtainMessage(MSG_PACKAGE_REMOVED, getChangingUserId(), 0, packageName) - .sendToTarget(); + final int changingUserId = getChangingUserId(); + // Only remove the package's data if a profile owner is not defined for the user + if (shouldDeleteObsoleteData(UserHandle.of(changingUserId))) { + mHandler.obtainMessage(MSG_PACKAGE_REMOVED, changingUserId, 0, packageName) + .sendToTarget(); + } super.onPackageRemoved(packageName, uid); } } diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 36d8c857ca21..fee4a47fd6ff 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -115,8 +115,9 @@ class UserUsageStatsService { mSystemTimeSnapshot = System.currentTimeMillis(); } - void init(final long currentTimeMillis, HashMap<String, Long> installedPackages) { - readPackageMappingsLocked(installedPackages); + void init(final long currentTimeMillis, HashMap<String, Long> installedPackages, + boolean deleteObsoleteData) { + readPackageMappingsLocked(installedPackages, deleteObsoleteData); mDatabase.init(currentTimeMillis); if (mDatabase.wasUpgradePerformed()) { mDatabase.prunePackagesDataOnUpgrade(installedPackages); @@ -180,12 +181,13 @@ class UserUsageStatsService { return mDatabase.onPackageRemoved(packageName, timeRemoved); } - private void readPackageMappingsLocked(HashMap<String, Long> installedPackages) { + private void readPackageMappingsLocked(HashMap<String, Long> installedPackages, + boolean deleteObsoleteData) { mDatabase.readMappingsLocked(); // Package mappings for the system user are updated after 24 hours via a job scheduled by // UsageStatsIdleService to ensure restored data is not lost on first boot. Additionally, // this makes user service initialization a little quicker on subsequent boots. - if (mUserId != UserHandle.USER_SYSTEM) { + if (mUserId != UserHandle.USER_SYSTEM && deleteObsoleteData) { updatePackageMappingsLocked(installedPackages); } } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 54d3af520d3c..4d2e00785d49 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3893,6 +3893,30 @@ public class CarrierConfigManager { public static final String KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL = "enabled_4g_opportunistic_network_scan_bool"; + /** + * Only relevant when the device supports opportunistic networks but does not support + * simultaneuous 5G+5G. Controls how long, in milliseconds, to wait before opportunistic network + * goes out of service before switching the 5G capability back to primary stack. The idea of + * waiting a few seconds is to minimize the calling of the expensive capability switching + * operation in the case where CBRS goes back into service shortly after going out of it. + * + * @hide + */ + public static final String KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG = + "time_to_switch_back_to_primary_if_opportunistic_oos_long"; + + /** + * Only relevant when the device supports opportunistic networks but does not support + * simultaneuous 5G+5G. Controls how long, in milliseconds, after 5G capability has switched back + * to primary stack due to opportunistic network being OOS. The idea is to minimizing the + * 'ping-ponging' effect where device is constantly witching capability back and forth between + * primary and opportunistic stack. + * + * @hide + */ + public static final String KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG + = "opportunistic_time_to_scan_after_capability_switch_to_primary_long"; + /** * Indicates zero or more emergency number prefix(es), because some carrier requires * if users dial an emergency number address with a specific prefix, the combination of the @@ -5766,6 +5790,10 @@ public class CarrierConfigManager { /* Default value is 2 seconds. */ sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_5G_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG, 2000); sDefaults.putBoolean(KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL, true); + sDefaults.putInt(KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG, 60000); + sDefaults.putInt( + KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG, + 120000); sDefaults.putAll(Gps.getDefaults()); sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY, new int[] { |