diff options
249 files changed, 4535 insertions, 3148 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 7acc0d7d3a23..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 { 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 431755e092e3..81f0b4483806 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -29,6 +29,7 @@ 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; @@ -314,7 +315,7 @@ public final class ActivityThread extends ClientTransactionHandler @UnsupportedAppUsage private ContextImpl mSystemContext; - private ContextImpl mSystemUiContext; + private final SparseArray<ContextImpl> mDisplaySystemUiContexts = new SparseArray<>(); @UnsupportedAppUsage static volatile IPackageManager sPackageManager; @@ -2611,22 +2612,26 @@ public final class ActivityThread extends ClientTransactionHandler } @Override + @NonNull public ContextImpl getSystemUiContext() { - synchronized (this) { - if (mSystemUiContext == null) { - mSystemUiContext = ContextImpl.createSystemUiContext(getSystemContext()); - } - return mSystemUiContext; - } + return getSystemUiContext(DEFAULT_DISPLAY); } /** - * Create the context instance base on system resources & display information which used for UI. + * Gets 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) */ - public ContextImpl createSystemUiContext(int displayId) { - return ContextImpl.createSystemUiContext(getSystemUiContext(), displayId); + @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 void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { @@ -3745,7 +3750,7 @@ public final class ActivityThread extends ClientTransactionHandler if (pkgName != null && !pkgName.isEmpty() && r.packageInfo.mPackageName.contains(pkgName)) { for (int id : dm.getDisplayIds()) { - if (id != Display.DEFAULT_DISPLAY) { + if (id != 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 b5ed1717496e..ed496c61bddb 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2618,7 +2618,10 @@ class ContextImpl extends Context { overrideConfig, display.getDisplayAdjustments().getCompatibilityInfo(), mResources.getLoaders())); context.mDisplay = display; - context.mContextType = CONTEXT_TYPE_DISPLAY_CONTEXT; + // 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; // 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). @@ -2671,7 +2674,8 @@ 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); + final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, + display.getDisplayId()); // 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 @@ -2696,9 +2700,7 @@ class ContextImpl extends Context { if (display == null) { throw new IllegalArgumentException("Display must not be null"); } - final ContextImpl tokenContext = createWindowContextBase(token, display); - tokenContext.setResources(createWindowContextResources(tokenContext)); - return tokenContext; + return createWindowContextBase(token, display.getDisplayId()); } /** @@ -2706,13 +2708,13 @@ class ContextImpl extends Context { * window. * * @param token The token to associate with {@link Resources} - * @param display The {@link Display} to associate with. + * @param displayId The ID of {@link Display} to associate with. * * @see #createWindowContext(Display, int, Bundle) * @see #createTokenContext(IBinder, Display) */ @UiContext - ContextImpl createWindowContextBase(@NonNull IBinder token, @NonNull Display display) { + ContextImpl createWindowContextBase(@NonNull IBinder token, int displayId) { ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams, mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), @@ -2726,8 +2728,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(display.getDisplayId(), windowContextResources); + baseContext.mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId, + windowContextResources); return baseContext; } @@ -2963,6 +2965,16 @@ 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); @@ -2983,22 +2995,13 @@ class ContextImpl extends Context { * @param displayId The ID of the display where the UI is shown. */ static ContextImpl createSystemUiContext(ContextImpl systemContext, int 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); + final WindowTokenClient token = new WindowTokenClient(); + ContextImpl context = systemContext.createWindowContextBase(token, displayId); + token.attachContext(context); + token.attachToDisplayContent(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); + return context; } @UnsupportedAppUsage @@ -3206,7 +3209,13 @@ class ContextImpl extends Context { @Override public IBinder getWindowContextToken() { - return mContextType == CONTEXT_TYPE_WINDOW_CONTEXT ? mToken : null; + switch (mContextType) { + case CONTEXT_TYPE_WINDOW_CONTEXT: + case CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI: + return mToken; + default: + return 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/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/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 7c2b1b72fbf1..8be2b4873c67 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -97,7 +97,8 @@ public class AppWidgetHostView extends FrameLayout { AppWidgetProviderInfo mInfo; View mView; int mViewMode = VIEW_MODE_NOINIT; - int mLayoutId = -1; + // If true, we should not try to re-apply the RemoteViews on the next inflation. + boolean mColorMappingChanged = false; private InteractionHandler mInteractionHandler; private boolean mOnLightBackground; private SizeF mCurrentSize = null; @@ -540,7 +541,6 @@ public class AppWidgetHostView extends FrameLayout { return; } content = getDefaultView(); - mLayoutId = -1; mViewMode = VIEW_MODE_DEFAULT; } else { // Select the remote view we are actually going to apply. @@ -557,8 +557,7 @@ public class AppWidgetHostView extends FrameLayout { // inflate any requested LayoutParams. mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath(); - int layoutId = rvToApply.getLayoutId(); - if (rvToApply.canRecycleView(mView)) { + if (!mColorMappingChanged && rvToApply.canRecycleView(mView)) { try { rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize, mColorResources); @@ -583,7 +582,6 @@ public class AppWidgetHostView extends FrameLayout { } } - mLayoutId = layoutId; mViewMode = VIEW_MODE_CONTENT; } @@ -591,6 +589,7 @@ public class AppWidgetHostView extends FrameLayout { } private void applyContent(View content, boolean recycled, Exception exception) { + mColorMappingChanged = false; if (content == null) { if (mViewMode == VIEW_MODE_ERROR) { // We've already done this -- nothing to do. @@ -626,7 +625,7 @@ public class AppWidgetHostView extends FrameLayout { // If our stale view has been prepared to match active, and the new // layout matches, try recycling it - if (remoteViews.canRecycleView(mView)) { + if (!mColorMappingChanged && remoteViews.canRecycleView(mView)) { try { mLastExecutionSignal = remoteViews.reapplyAsync(mContext, mView, @@ -666,7 +665,6 @@ public class AppWidgetHostView extends FrameLayout { @Override public void onViewApplied(View v) { - AppWidgetHostView.this.mLayoutId = mLayoutId; mViewMode = VIEW_MODE_CONTENT; applyContent(v, mIsReapply, null); @@ -907,7 +905,7 @@ public class AppWidgetHostView extends FrameLayout { } mColorMapping = colorMapping.clone(); mColorResources = RemoteViews.ColorResources.create(mContext, mColorMapping); - mLayoutId = -1; + mColorMappingChanged = true; mViewMode = VIEW_MODE_NOINIT; reapplyLastRemoteViews(); } @@ -937,7 +935,7 @@ public class AppWidgetHostView extends FrameLayout { if (mColorResources != null) { mColorResources = null; mColorMapping = null; - mLayoutId = -1; + mColorMappingChanged = true; mViewMode = VIEW_MODE_NOINIT; reapplyLastRemoteViews(); } diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index b99ad5125149..32c491753af4 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -108,7 +108,7 @@ public final class CompanionDeviceManager { } private final ICompanionDeviceManager mService; - private final Context mContext; + private Context mContext; /** @hide */ public CompanionDeviceManager( @@ -541,6 +541,7 @@ public final class CompanionDeviceManager { mCallback = null; mHandler = null; mRequest = null; + mContext = null; } } diff --git a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java index 603b06ddabaa..065ae64a92ad 100644 --- a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java +++ b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java @@ -38,13 +38,16 @@ public interface BiometricOverlayConstants { int REASON_AUTH_KEYGUARD = 4; /** Non-specific usage (from FingerprintManager). */ int REASON_AUTH_OTHER = 5; + /** Usage from Settings. */ + int REASON_AUTH_SETTINGS = 6; @IntDef({REASON_UNKNOWN, REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING, REASON_AUTH_BP, REASON_AUTH_KEYGUARD, - REASON_AUTH_OTHER}) + REASON_AUTH_OTHER, + REASON_AUTH_SETTINGS}) @Retention(RetentionPolicy.SOURCE) @interface ShowReason {} } 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/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 9e41e4d2906c..ae32a481691a 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -866,6 +866,23 @@ 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/SurfaceView.java b/core/java/android/view/SurfaceView.java index 856dfe53dfac..e8725f00ffdf 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -162,8 +162,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) boolean mIsCreating = false; - private volatile boolean mRtHandlingPositionUpdates = false; - private volatile boolean mRtReleaseSurfaces = false; private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener = this::updateSurface; @@ -909,13 +907,14 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mBlastBufferQueue = null; } - if (mRtHandlingPositionUpdates) { - mRtReleaseSurfaces = true; - return; + ViewRootImpl viewRoot = getViewRootImpl(); + Transaction transaction = new Transaction(); + releaseSurfaces(transaction); + if (viewRoot != null) { + viewRoot.applyTransactionOnDraw(transaction); + } else { + transaction.apply(); } - - releaseSurfaces(mTmpTransaction); - mTmpTransaction.apply(); } } @@ -1468,15 +1467,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (mSurfaceControl == null) { return; } - // TODO: This is teensy bit racey in that a brand new SurfaceView moving on - // its 2nd frame if RenderThread is running slowly could potentially see - // this as false, enter the branch, get pre-empted, then this comes along - // and reports a new position, then the UI thread resumes and reports - // its position. This could therefore be de-sync'd in that interval, but - // the synchronization would violate the rule that RT must never block - // on the UI thread which would open up potential deadlocks. The risk of - // a single-frame desync is therefore preferable for now. - mRtHandlingPositionUpdates = true; } if (mRTLastReportedPosition.left == left && mRTLastReportedPosition.top == top @@ -1552,12 +1542,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall return; } mRtTransaction.hide(mSurfaceControl); - if (mRtReleaseSurfaces) { - mRtReleaseSurfaces = false; - releaseSurfaces(mRtTransaction); - } applyOrMergeTransaction(mRtTransaction, frameNumber); - mRtHandlingPositionUpdates = false; } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 65d0e497a5b1..b0b5bb8870c6 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -208,6 +208,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.function.Consumer; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -757,6 +758,8 @@ public final class ViewRootImpl implements ViewParent, */ private boolean mNextDrawUseBlastSync = false; + private Consumer<SurfaceControl.Transaction> mBLASTDrawConsumer; + /** * Wait for the blast sync transaction complete callback before drawing and queuing up more * frames. This will prevent out of order buffers submissions when WM has requested to @@ -3351,6 +3354,9 @@ public final class ViewRootImpl implements ViewParent, } boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; + if (mBLASTDrawConsumer != null) { + mNextDrawUseBlastSync = true; + } if (!cancelDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { @@ -4049,6 +4055,8 @@ public final class ViewRootImpl implements ViewParent, */ private HardwareRenderer.FrameCompleteCallback createFrameCompleteCallback(Handler handler, boolean reportNextDraw, ArrayList<Runnable> commitCallbacks) { + final Consumer<SurfaceControl.Transaction> blastSyncConsumer = mBLASTDrawConsumer; + mBLASTDrawConsumer = null; return frameNr -> { if (DEBUG_BLAST) { Log.d(mTag, "Received frameCompleteCallback frameNum=" + frameNr); @@ -4061,6 +4069,9 @@ public final class ViewRootImpl implements ViewParent, // is only true when the UI thread is paused. Therefore, no one should be // modifying this object until the next vsync. mSurfaceChangedTransaction.merge(mRtBLASTSyncTransaction); + if (blastSyncConsumer != null) { + blastSyncConsumer.accept(mSurfaceChangedTransaction); + } } if (reportNextDraw) { @@ -10507,9 +10518,11 @@ public final class ViewRootImpl implements ViewParent, @Override public boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t) { - registerRtFrameCallback(frame -> { - mergeWithNextTransaction(t, frame); - }); + if (mRemoved) { + t.apply(); + } else { + registerRtFrameCallback(frame -> mergeWithNextTransaction(t, frame)); + } return true; } @@ -10548,4 +10561,35 @@ public final class ViewRootImpl implements ViewParent, listener.onBufferTransformHintChanged(hint); } } + + /** + * Redirect the next draw of this ViewRoot (from the UI thread perspective) + * to the passed in consumer. This can be used to create P2P synchronization + * between ViewRoot's however it comes with many caveats. + * + * 1. You MUST consume the transaction, by either applying it immediately or + * merging it in to another transaction. The threading model doesn't + * allow you to hold in the passed transaction. + * 2. If you merge it in to another transaction, this ViewRootImpl will be + * paused until you finally apply that transaction and it receives + * the callback from SF. If you lose track of the transaction you will + * ANR the app. + * 3. Only one person can consume the transaction at a time, if you already + * have a pending consumer for this frame, the function will return false + * 4. Someone else may have requested to consume the next frame, in which case + * this function will return false and you will not receive a callback. + * 5. This function does not trigger drawing so even if it returns true you + * may not receive a callback unless there is some other UI thread work + * to trigger drawing. If it returns true, and a draw occurs, the callback + * will be called (Though again watch out for the null transaction case!) + * 6. This function must be called on the UI thread. The consumer will likewise + * be called on the UI thread. + */ + public boolean consumeNextDraw(Consumer<SurfaceControl.Transaction> consume) { + if (mBLASTDrawConsumer != null) { + return false; + } + mBLASTDrawConsumer = consume; + return true; + } } 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 57b7d618917d..2357d13c8d41 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -5746,9 +5746,11 @@ public class RemoteViews implements Parcelable, Filter { // 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 (!rvToApply.canRecycleView(v)) { - throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" - + " that does not share the same root layout id."); + 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."); + } } rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources); @@ -5792,9 +5794,11 @@ public class RemoteViews implements Parcelable, Filter { // 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 (!rvToApply.canRecycleView(v)) { - throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" - + " that does not share the same root layout id."); + 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."); + } } return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java index 165dcdf3a836..a118f9a8188f 100644 --- a/core/java/android/window/TaskFragmentInfo.java +++ b/core/java/android/window/TaskFragmentInfo.java @@ -213,6 +213,7 @@ public final class TaskFragmentInfo implements Parcelable { + " isEmpty=" + mIsEmpty + " runningActivityCount=" + mRunningActivityCount + " isVisible=" + mIsVisible + + " activities=" + mActivities + " positionInParent=" + mPositionInParent + " isTaskClearedForReuse=" + mIsTaskClearedForReuse + "}"; diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index 5aa623388574..17b675f93f86 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -19,13 +19,10 @@ 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; @@ -38,7 +35,6 @@ 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 @@ -56,14 +52,7 @@ 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; } /** @@ -80,19 +69,7 @@ public class WindowContextController { throw new IllegalStateException("A Window Context can be only attached to " + "a DisplayArea once."); } - 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(); - } + mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options); } /** @@ -120,22 +97,14 @@ public class WindowContextController { throw new IllegalStateException("The Window Context should have been attached" + " to a DisplayArea."); } - try { - mWms.attachWindowContextToWindowToken(mToken, windowToken); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mToken.attachToWindowToken(windowToken); } /** Detaches the window context from the node it's currently associated with. */ public void detachIfNeeded() { if (mAttachedToDisplayArea) { - try { - mWms.detachWindowContextFromWindowContainer(mToken); - mAttachedToDisplayArea = false; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mToken.detachFromWindowContainerIfNeeded(); + mAttachedToDisplayArea = false; } } } diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index f3e3859b4256..b331a9e81e27 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -21,6 +21,7 @@ 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; @@ -31,7 +32,11 @@ 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; @@ -59,10 +64,14 @@ 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}. @@ -84,6 +93,88 @@ 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 a6e351d9cee7..52cb9f318dd0 100644 --- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java @@ -24,16 +24,13 @@ 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; @@ -59,17 +56,14 @@ 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, mMockWms); + mController = new WindowContextController(mMockToken); doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean()); - doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(), - anyInt(), anyInt(), any()); + doReturn(true).when(mMockToken).attachToDisplayArea(anyInt(), anyInt(), any()); } @Test(expected = IllegalStateException.class) @@ -81,10 +75,10 @@ public class WindowContextControllerTest { } @Test - public void testDetachIfNeeded_NotAttachedYet_DoNothing() throws Exception { + public void testDetachIfNeeded_NotAttachedYet_DoNothing() { mController.detachIfNeeded(); - verify(mMockWms, never()).detachWindowContextFromWindowContainer(any()); + verify(mMockToken, never()).detachFromWindowContainerIfNeeded(); } @Test @@ -93,8 +87,6 @@ public class WindowContextControllerTest { null /* options */); assertThat(mController.mAttachedToDisplayArea).isTrue(); - verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY), - eq(false) /* shouldReportConfigChange */); mController.detachIfNeeded(); diff --git a/data/etc/car/com.android.car.shell.xml b/data/etc/car/com.android.car.shell.xml index c058cb91cf9d..992840db1a27 100644 --- a/data/etc/car/com.android.car.shell.xml +++ b/data/etc/car/com.android.car.shell.xml @@ -19,6 +19,7 @@ is ok. --> <privapp-permissions package="com.android.shell"> <permission name="android.permission.INSTALL_PACKAGES" /> + <permission name="android.permission.MODIFY_AUDIO_ROUTING"/> <permission name="android.permission.MEDIA_CONTENT_CONTROL"/> <permission name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"/> <permission name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/> 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 06e7d1457417..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,14 +73,61 @@ 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{" + + " primaryContainer=" + mPrimaryContainer + + " secondaryContainer=" + mSecondaryContainer + + " splitRule=" + mSplitRule + + "}"; + } } 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 20515e71a91b..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; } @@ -497,7 +508,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } List<SplitInfo> currentSplitStates = getActiveSplitStates(); - if (mLastReportedSplitStates.equals(currentSplitStates)) { + if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) { return; } mLastReportedSplitStates.clear(); @@ -506,15 +517,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** - * Returns a list of descriptors for currently active split states. + * @return a list of descriptors for currently active split states. If the value returned is + * null, that indicates that the active split states are in an intermediate state and should + * not be reported. */ + @Nullable private List<SplitInfo> getActiveSplitStates() { List<SplitInfo> splitStates = new ArrayList<>(); for (SplitContainer container : mSplitContainers) { if (container.getPrimaryContainer().isEmpty() || container.getSecondaryContainer().isEmpty()) { - // Skipping containers that do not have any activities to report. - continue; + // We are in an intermediate state because either the split container is about to be + // removed or the primary or secondary container are about to receive an activity. + return null; } ActivityStack primaryContainer = container.getPrimaryContainer().toActivityStack(); ActivityStack secondaryContainer = container.getSecondaryContainer().toActivityStack(); @@ -639,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 80d9c2c1719c..a1a53bc93781 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -27,6 +27,7 @@ import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** @@ -227,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); } @@ -234,6 +238,9 @@ class TaskFragmentContainer { // Finish associated activities for (Activity activity : mActivitiesToFinishOnExit) { + if (controller.shouldRetainAssociatedActivity(this, activity)) { + continue; + } activity.finish(); } mActivitiesToFinishOnExit.clear(); @@ -267,4 +274,42 @@ class TaskFragmentContainer { mLastRequestedBounds.set(bounds); } } + + @Override + public String toString() { + return toString(true /* includeContainersToFinishOnExit */); + } + + /** + * @return string for this TaskFragmentContainer and includes containers to finish on exit + * based on {@code includeContainersToFinishOnExit}. If containers to finish on exit are always + * included in the string, then calling {@link #toString()} on a container that mutually + * finishes with another container would cause a stack overflow. + */ + private String toString(boolean includeContainersToFinishOnExit) { + return "TaskFragmentContainer{" + + " token=" + mToken + + " info=" + mInfo + + " topNonFinishingActivity=" + getTopNonFinishingActivity() + + " pendingAppearedActivities=" + mPendingAppearedActivities + + (includeContainersToFinishOnExit ? " containersToFinishOnExit=" + + containersToFinishOnExitToString() : "") + + " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit + + " isFinished=" + mIsFinished + + " lastRequestedBounds=" + mLastRequestedBounds + + "}"; + } + + private String containersToFinishOnExitToString() { + StringBuilder sb = new StringBuilder("["); + Iterator<TaskFragmentContainer> containerIterator = mContainersToFinishOnExit.iterator(); + while (containerIterator.hasNext()) { + sb.append(containerIterator.next().toString( + false /* includeContainersToFinishOnExit */)); + if (containerIterator.hasNext()) { + sb.append(", "); + } + } + return sb.append("]").toString(); + } } 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 7dc2f31e9871..9fe024748610 100644 --- a/libs/WindowManager/Shell/res/layout/pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/pip_menu.xml @@ -65,28 +65,25 @@ <LinearLayout android:id="@+id/top_end_container" android:layout_gravity="top|end" - android:layout_width="match_parent" + android:layout_width="wrap_content" 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/enter_split" + android:id="@+id/dismiss" android:layout_width="@dimen/pip_action_size" android:layout_height="@dimen/pip_action_size" - android:layout_gravity="top|start" + android:contentDescription="@string/pip_phone_close" android:gravity="center" - android:contentDescription="@string/pip_phone_enter_split" - android:src="@drawable/pip_expand" + android:src="@drawable/pip_ic_close_white" android:background="?android:selectableItemBackgroundBorderless" /> </LinearLayout> @@ -100,14 +97,4 @@ 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 c88fc16e218e..764854af3b3f 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -24,9 +24,6 @@ <!-- 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 711a0ac76702..b80dcd063589 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,7 +43,6 @@ 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; @@ -161,14 +160,13 @@ 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, newSplitScreenOptional, - displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); + pipTransitionController, splitScreenOptional, 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 ec701470354c..944dfed57cb6 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,7 +55,6 @@ 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; @@ -216,15 +215,14 @@ 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, newSplitScreenOptional, - displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); + pipTransitionController, splitScreenOptional, 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 6cc5f09827af..b6e5804a64dd 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,7 +77,6 @@ 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; @@ -127,8 +126,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private final int mExitAnimationDuration; private final int mCrossFadeAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; - private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional; - private final Optional<SplitScreenController> mSplitScreenOptional; + private final Optional<LegacySplitScreenController> mSplitScreenOptional; protected final ShellTaskOrganizer mTaskOrganizer; protected final ShellExecutor mMainExecutor; @@ -254,8 +252,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @NonNull PipAnimationController pipAnimationController, @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper, @NonNull PipTransitionController pipTransitionController, - Optional<LegacySplitScreenController> legacySplitScreenOptional, - Optional<SplitScreenController> splitScreenOptional, + Optional<LegacySplitScreenController> splitScreenOptional, @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, @NonNull ShellTaskOrganizer shellTaskOrganizer, @@ -277,7 +274,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipAnimationController = pipAnimationController; mPipUiEventLoggerLogger = pipUiEventLogger; mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; - mLegacySplitScreenOptional = legacySplitScreenOptional; mSplitScreenOptional = splitScreenOptional; mTaskOrganizer = shellTaskOrganizer; mMainExecutor = mainExecutor; @@ -377,11 +373,8 @@ 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, boolean requestEnterSplit) { + public void exitPip(int animationDurationMs) { if (!mPipTransitionState.isInPip() || mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP || mToken == null) { @@ -394,7 +387,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, requestEnterSplit) + final int direction = syncWithSplitScreenBounds(destinationBounds) ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN : TRANSITION_DIRECTION_LEAVE_PIP; final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); @@ -403,7 +396,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 && !requestEnterSplit + direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_FULLSCREEN); wct.setBounds(mToken, destinationBounds); @@ -442,7 +435,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); - mLegacySplitScreenOptional.ifPresent(splitScreen -> { + mSplitScreenOptional.ifPresent(splitScreen -> { if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { wct.reparent(mToken, splitScreen.getSecondaryRoot(), true /* onTop */); } @@ -1172,7 +1165,6 @@ 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(); @@ -1218,10 +1210,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, null /* callback */, false /* withStartDelay */); }); } else { - applyFinishBoundsResize(wct, direction, isPipTopLeft); + applyFinishBoundsResize(wct, direction); } } else { - applyFinishBoundsResize(wct, direction, isPipTopLeft); + applyFinishBoundsResize(wct, direction); } finishResizeForMenu(destinationBounds); @@ -1249,11 +1241,7 @@ 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. - if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { - taskBounds = destinationBounds; - } else { - taskBounds = null; - } + taskBounds = null; applyWindowingModeChangeOnExit(wct, direction); } else { // Just a resize in PIP @@ -1273,20 +1261,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, * applying it. */ public void applyFinishBoundsResize(@NonNull WindowContainerTransaction 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()); + @PipAnimationController.TransitionDirection int direction) { + mTaskOrganizer.applyTransaction(wct); } /** @@ -1371,27 +1347,18 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** - * Sync with {@link LegacySplitScreenController} or {@link SplitScreenController} on destination - * bounds if PiP is going to split screen. + * Sync with {@link LegacySplitScreenController} 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, 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()) { + private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut) { + if (!mSplitScreenOptional.isPresent()) { return false; } - LegacySplitScreenController legacySplitScreen = mLegacySplitScreenOptional.get(); + LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.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 5687f4d62444..ae8c1b6f8c1a 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,11 +95,6 @@ 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(); @@ -463,10 +458,6 @@ 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 69ae45d12795..47a8c67a22e6 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(false /* skipAnimation */); + mMotionHelper.expandLeavePip(); 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 10bc7e250cc8..8c431f08a385 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,8 +482,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb false /* fromShelfAdjustment */, wct /* windowContainerTransaction */); if (wct != null) { - mPipTaskOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_SAME, - false /* wasPipTopLeft */); + mPipTaskOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_SAME); } }; 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 06446573840c..3eeba6eb5366 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,6 +18,8 @@ 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; @@ -32,7 +34,6 @@ public class PipMenuIconsAlgorithm { protected ViewGroup mViewRoot; protected ViewGroup mTopEndContainer; protected View mDragHandle; - protected View mEnterSplitButton; protected View mSettingsButton; protected View mDismissButton; @@ -43,13 +44,14 @@ public class PipMenuIconsAlgorithm { * Bind the necessary views. */ public void bindViews(ViewGroup viewRoot, ViewGroup topEndContainer, View dragHandle, - View enterSplitButton, View settingsButton, View dismissButton) { + View settingsButton, View dismissButton) { mViewRoot = viewRoot; mTopEndContainer = topEndContainer; mDragHandle = dragHandle; - mEnterSplitButton = enterSplitButton; mSettingsButton = settingsButton; mDismissButton = dismissButton; + + bindInitialViewState(); } /** @@ -70,4 +72,22 @@ 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 7bbebe5bf287..8ef2b6b12030 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_ENTER_SPLIT = false; + private static final boolean ENABLE_RESIZE_HANDLE = 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 mEnterSplitButton; + protected View mResizeHandle; protected View mTopEndContainer; protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm; @@ -177,23 +177,14 @@ public class PipMenuView extends FrameLayout { } }); - mEnterSplitButton = findViewById(R.id.enter_split); - mEnterSplitButton.setAlpha(0); - mEnterSplitButton.setOnClickListener(v -> { - if (mMenuContainer.getAlpha() != 0) { - enterSplit(); - } - }); - - findViewById(R.id.resize_handle).setAlpha(0); - + mResizeHandle = findViewById(R.id.resize_handle); + mResizeHandle.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, - findViewById(R.id.resize_handle), mEnterSplitButton, mSettingsButton, - mDismissButton); + mResizeHandle, mSettingsButton, mDismissButton); mDismissFadeOutDurationMs = context.getResources() .getInteger(R.integer.config_pipExitAnimationDuration); @@ -277,13 +268,14 @@ public class PipMenuView extends FrameLayout { mSettingsButton.getAlpha(), 1f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 1f); - ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, - mEnterSplitButton.getAlpha(), ENABLE_ENTER_SPLIT ? 1f : 0f); + ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA, + mResizeHandle.getAlpha(), + ENABLE_RESIZE_HANDLE && showResizeHandle ? 1f : 0f); if (menuState == MENU_STATE_FULL) { mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, - enterSplitAnim); + resizeAnim); } else { - mMenuContainerAnimator.playTogether(enterSplitAnim); + mMenuContainerAnimator.playTogether(resizeAnim); } mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS); @@ -336,7 +328,7 @@ public class PipMenuView extends FrameLayout { mMenuContainer.setAlpha(0f); mSettingsButton.setAlpha(0f); mDismissButton.setAlpha(0f); - mEnterSplitButton.setAlpha(0f); + mResizeHandle.setAlpha(0f); } void pokeMenu() { @@ -376,10 +368,9 @@ public class PipMenuView extends FrameLayout { mSettingsButton.getAlpha(), 0f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 0f); - ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, - mEnterSplitButton.getAlpha(), 0f); - mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, - enterSplitAnim); + ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA, + mResizeHandle.getAlpha(), 0f); + mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim); mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT); mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType)); mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { @@ -531,14 +522,6 @@ 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 c634b7f220b0..dbd09fd7b265 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,29 +338,22 @@ 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(boolean skipAnimation) { - expandLeavePip(skipAnimation, false /* enterSplit */); - } - - /** - * Resizes the pinned task to split-screen mode. - */ - void expandIntoSplit() { - expandLeavePip(false, true /* enterSplit */); + void expandLeavePip() { + expandLeavePip(false /* skipAnimation */); } /** * Resizes the pinned stack back to unknown windowing mode, which could be freeform or * fullscreen depending on the display area's windowing mode. */ - private void expandLeavePip(boolean skipAnimation, boolean enterSplit) { + void expandLeavePip(boolean skipAnimation) { 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, enterSplit); + mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION); } /** 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 570fd5eab9f6..9f2f6a575aca 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,12 +139,7 @@ public class PipTouchHandler { @Override public void onPipExpand() { - mMotionHelper.expandLeavePip(false /* skipAnimation */); - } - - @Override - public void onEnterSplit() { - mMotionHelper.expandIntoSplit(); + mMotionHelper.expandLeavePip(); } @Override @@ -904,7 +899,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(false /* skipAnimation */); + mMotionHelper.expandLeavePip(); } } 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 00083d986dbe..a2e9b64046fd 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, false /* requestEnterSplit */); + mPipTaskOrganizer.exitPip(mResizeAnimationDuration); 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 7457be2d0871..04058ed6388c 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,25 +202,11 @@ 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); } @@ -238,11 +224,6 @@ 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 3c35e6a69bf5..3589f7c14cd3 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,11 +281,6 @@ 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); @@ -696,11 +692,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 0172cf324eea..0270093da938 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,7 +50,6 @@ 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; @@ -76,8 +75,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Mock private PipTransitionController mMockPipTransitionController; @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper; @Mock private PipUiEventLogger mMockPipUiEventLogger; - @Mock private Optional<LegacySplitScreenController> mMockOptionalLegacySplitScreen; - @Mock private Optional<SplitScreenController> mMockOptionalSplitScreen; + @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen; @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; private TestShellExecutor mMainExecutor; private PipBoundsState mPipBoundsState; @@ -101,9 +99,8 @@ public class PipTaskOrganizerTest extends ShellTestCase { mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState, mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController, mMockPipSurfaceTransactionHelper, - mMockPipTransitionController, mMockOptionalLegacySplitScreen, - mMockOptionalSplitScreen, mMockDisplayController, mMockPipUiEventLogger, - mMockShellTaskOrganizer, mMainExecutor)); + mMockPipTransitionController, 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/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index edbfd2a8f03e..a5168ccd977c 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -78,6 +78,7 @@ public class CompanionDeviceActivity extends Activity { getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); sInstance = this; + getService().mActivity = this; String deviceProfile = getRequest().getDeviceProfile(); String profilePrivacyDisclaimer = emptyIfNull(getRequest() @@ -141,8 +142,6 @@ public class CompanionDeviceActivity extends Activity { profileSummary.setVisibility(View.GONE); } - getService().mActivity = this; - mCancelButton = findViewById(R.id.button_cancel); mCancelButton.setOnClickListener(v -> cancel()); } @@ -194,6 +193,7 @@ public class CompanionDeviceActivity extends Activity { @Override protected void onDestroy() { super.onDestroy(); + getService().mActivity = null; if (sInstance == this) { sInstance = null; } diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index 5df8e3c83a7a..2a72c501d7d0 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -258,12 +258,8 @@ public class CompanionDeviceDiscoveryService extends Service { if (!mIsScanning) return; mIsScanning = false; - CompanionDeviceActivity activity = mActivity; - if (activity != null) { - if (activity.mDeviceListView != null) { - activity.mDeviceListView.removeFooterView(activity.mLoadingIndicator); - } - mActivity = null; + if (mActivity != null && mActivity.mDeviceListView != null) { + mActivity.mDeviceListView.removeFooterView(mActivity.mLoadingIndicator); } mBluetoothAdapter.cancelDiscovery(); @@ -337,6 +333,7 @@ public class CompanionDeviceDiscoveryService extends Service { void onCancel() { if (DEBUG) Log.i(LOG_TAG, "onCancel()"); + mActivity = null; mServiceCallback.cancel(true); } diff --git a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java index 794b0eb66519..280e40726c03 100644 --- a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java +++ b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java @@ -15,13 +15,8 @@ */ package com.android.settingslib; -import com.android.settingslib.mobile.TelephonyIcons; - -import java.text.SimpleDateFormat; -import java.util.Objects; - /** - * Icons and states for SysUI and Settings. + * Icons for SysUI and Settings. */ public class SignalIcon { @@ -71,92 +66,6 @@ public class SignalIcon { } /** - * Holds states for SysUI. - */ - public static class State { - // No locale as it's only used for logging purposes - private static SimpleDateFormat sSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); - public boolean connected; - public boolean enabled; - public boolean activityIn; - public boolean activityOut; - public int level; - public IconGroup iconGroup; - public int inetCondition; - public int rssi; // Only for logging. - - // Not used for comparison, just used for logging. - public long time; - - /** - * Generates a copy of the source state. - */ - public void copyFrom(State state) { - connected = state.connected; - enabled = state.enabled; - level = state.level; - iconGroup = state.iconGroup; - inetCondition = state.inetCondition; - activityIn = state.activityIn; - activityOut = state.activityOut; - rssi = state.rssi; - time = state.time; - } - - @Override - public String toString() { - if (time != 0) { - StringBuilder builder = new StringBuilder(); - toString(builder); - return builder.toString(); - } else { - return "Empty " + getClass().getSimpleName(); - } - } - - protected void toString(StringBuilder builder) { - builder.append("connected=").append(connected).append(',') - .append("enabled=").append(enabled).append(',') - .append("level=").append(level).append(',') - .append("inetCondition=").append(inetCondition).append(',') - .append("iconGroup=").append(iconGroup).append(',') - .append("activityIn=").append(activityIn).append(',') - .append("activityOut=").append(activityOut).append(',') - .append("rssi=").append(rssi).append(',') - .append("lastModified=").append(sSDF.format(time)); - } - - @Override - public boolean equals(Object o) { - if (!o.getClass().equals(getClass())) { - return false; - } - State other = (State) o; - return other.connected == connected - && other.enabled == enabled - && other.level == level - && other.inetCondition == inetCondition - && other.iconGroup == iconGroup - && other.activityIn == activityIn - && other.activityOut == activityOut - && other.rssi == rssi; - } - - @Override - public int hashCode() { - return Objects.hash( - connected, - enabled, - level, - inetCondition, - iconGroup, - activityIn, - activityOut, - rssi); - } - } - - /** * Holds icons for a given MobileState. */ public static class MobileIconGroup extends IconGroup { @@ -189,110 +98,4 @@ public class SignalIcon { this.dataType = dataType; } } - - /** - * Holds mobile states for SysUI. - */ - public static class MobileState extends State { - public String networkName; - public String networkNameData; - public boolean dataSim; - public boolean dataConnected; - public boolean isEmergency; - public boolean airplaneMode; - public boolean carrierNetworkChangeMode; - public boolean isDefault; - public boolean userSetup; - public boolean roaming; - public boolean defaultDataOff; // Tracks the on/off state of the defaultDataSubscription - - @Override - public void copyFrom(State s) { - super.copyFrom(s); - MobileState state = (MobileState) s; - dataSim = state.dataSim; - networkName = state.networkName; - networkNameData = state.networkNameData; - dataConnected = state.dataConnected; - isDefault = state.isDefault; - isEmergency = state.isEmergency; - airplaneMode = state.airplaneMode; - carrierNetworkChangeMode = state.carrierNetworkChangeMode; - userSetup = state.userSetup; - roaming = state.roaming; - defaultDataOff = state.defaultDataOff; - } - - /** @return true if this state is disabled or not default data */ - public boolean isDataDisabledOrNotDefault() { - return (iconGroup == TelephonyIcons.DATA_DISABLED - || (iconGroup == TelephonyIcons.NOT_DEFAULT_DATA)) && userSetup; - } - - /** @return if this state is considered to have inbound activity */ - public boolean hasActivityIn() { - return dataConnected && !carrierNetworkChangeMode && activityIn; - } - - /** @return if this state is considered to have outbound activity */ - public boolean hasActivityOut() { - return dataConnected && !carrierNetworkChangeMode && activityOut; - } - - /** @return true if this state should show a RAT icon in quick settings */ - public boolean showQuickSettingsRatIcon() { - return dataConnected || isDataDisabledOrNotDefault(); - } - - @Override - protected void toString(StringBuilder builder) { - super.toString(builder); - builder.append(','); - builder.append("dataSim=").append(dataSim).append(','); - builder.append("networkName=").append(networkName).append(','); - builder.append("networkNameData=").append(networkNameData).append(','); - builder.append("dataConnected=").append(dataConnected).append(','); - builder.append("roaming=").append(roaming).append(','); - builder.append("isDefault=").append(isDefault).append(','); - builder.append("isEmergency=").append(isEmergency).append(','); - builder.append("airplaneMode=").append(airplaneMode).append(','); - builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode) - .append(','); - builder.append("userSetup=").append(userSetup).append(','); - builder.append("defaultDataOff=").append(defaultDataOff).append(','); - builder.append("showQuickSettingsRatIcon=").append(showQuickSettingsRatIcon()); - } - - @Override - public boolean equals(Object o) { - return super.equals(o) - && Objects.equals(((MobileState) o).networkName, networkName) - && Objects.equals(((MobileState) o).networkNameData, networkNameData) - && ((MobileState) o).dataSim == dataSim - && ((MobileState) o).dataConnected == dataConnected - && ((MobileState) o).isEmergency == isEmergency - && ((MobileState) o).airplaneMode == airplaneMode - && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode - && ((MobileState) o).userSetup == userSetup - && ((MobileState) o).isDefault == isDefault - && ((MobileState) o).roaming == roaming - && ((MobileState) o).defaultDataOff == defaultDataOff; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), - networkName, - networkNameData, - dataSim, - dataConnected, - isEmergency, - airplaneMode, - carrierNetworkChangeMode, - userSetup, - isDefault, - roaming, - defaultDataOff); - } - } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index a1fba4a018e2..dc109cac37b2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -16,7 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_ALL; +import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO; +import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -29,6 +30,7 @@ import android.content.Context; import android.util.Log; import com.android.settingslib.R; +import com.android.settingslib.Utils; import java.util.ArrayList; import java.util.List; @@ -162,9 +164,12 @@ public class HearingAidProfile implements LocalBluetoothProfile { if (mBluetoothAdapter == null) { return false; } + int profiles = Utils.isAudioModeOngoingCall(mContext) + ? ACTIVE_DEVICE_PHONE_CALL + : ACTIVE_DEVICE_AUDIO; return device == null - ? mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_ALL) - : mBluetoothAdapter.setActiveDevice(device, ACTIVE_DEVICE_ALL); + ? mBluetoothAdapter.removeActiveDevice(profiles) + : mBluetoothAdapter.setActiveDevice(device, profiles); } public List<BluetoothDevice> getActiveDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java index 6cb60d1aaf0e..7390b6aaaaed 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java @@ -97,6 +97,9 @@ public class MetricsFeatureProvider { /** * Logs a simple action without page id or attribution + * + * @param category the target page + * @param taggedData the data for {@link EventLogWriter} */ public void action(Context context, int category, Pair<Integer, Object>... taggedData) { for (LogWriter writer : mLoggerWriters) { 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/Android.bp b/packages/SystemUI/Android.bp index eae6f27db738..bca5071380de 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -50,17 +50,6 @@ java_library { srcs: ["src/com/android/systemui/EventLogTags.logtags"], } -java_library { - name: "SystemUI-flags", - srcs: [ - "src/com/android/systemui/flags/Flags.java", - ], - libs: [ - "SystemUI-flag-types", - ], - static_kotlin_stdlib: false, -} - filegroup { name: "ReleaseJavaFiles", srcs: [ @@ -126,7 +115,6 @@ android_library { "iconloader_base", "SystemUI-tags", "SystemUI-proto", - "SystemUI-flags", "monet", "dagger2", "jsr330", diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 58e3d398553c..49a01df96530 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -356,10 +356,6 @@ android:exported="false" android:finishOnTaskLaunch="true" /> - <activity android:name=".screenrecord.ScreenRecordDialog" - android:theme="@style/ScreenRecord" - android:showForAllUsers="true" - android:excludeFromRecents="true" /> <service android:name=".screenrecord.RecordingService" /> <receiver android:name=".SysuiRestartReceiver" diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 865f96be1775..faa7554525c5 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -16,11 +16,16 @@ package com.android.systemui.animation +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator import android.app.Dialog import android.content.Context import android.graphics.Color +import android.graphics.Rect import android.os.Looper import android.util.Log +import android.util.MathUtils import android.view.GhostView import android.view.Gravity import android.view.View @@ -32,6 +37,7 @@ import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN import android.view.WindowManagerPolicyConstants import android.widget.FrameLayout +import kotlin.math.roundToInt private const val TAG = "DialogLaunchAnimator" @@ -52,7 +58,8 @@ class DialogLaunchAnimator( private val currentAnimations = hashSetOf<DialogLaunchAnimation>() /** - * Show [dialog] by expanding it from [view]. + * Show [dialog] by expanding it from [view]. If [animateBackgroundBoundsChange] is true, then + * the background of the dialog will be animated when the dialog bounds change. * * Caveats: When calling this function, the dialog content view will actually be stolen and * attached to a different dialog (and thus a different window) which means that the actual @@ -60,7 +67,12 @@ class DialogLaunchAnimator( * must call dismiss(), hide() and show() on the [Dialog] returned by this function to actually * dismiss, hide or show the dialog. */ - fun showFromView(dialog: Dialog, view: View): Dialog { + @JvmOverloads + fun showFromView( + dialog: Dialog, + view: View, + animateBackgroundBoundsChange: Boolean = false + ): Dialog { if (Looper.myLooper() != Looper.getMainLooper()) { throw IllegalStateException( "showFromView must be called from the main thread and dialog must be created in " + @@ -78,7 +90,8 @@ class DialogLaunchAnimator( val launchAnimation = DialogLaunchAnimation( context, launchAnimator, hostDialogProvider, view, - onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog) + onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog, + animateBackgroundBoundsChange) val hostDialog = launchAnimation.hostDialog currentAnimations.add(launchAnimation) @@ -208,7 +221,10 @@ private class DialogLaunchAnimation( private val onDialogDismissed: (DialogLaunchAnimation) -> Unit, /** The original dialog whose content will be shown and animate in/out in [hostDialog]. */ - private val originalDialog: Dialog + private val originalDialog: Dialog, + + /** Whether we should animate the dialog background when its bounds change. */ + private val animateBackgroundBoundsChange: Boolean ) { /** * The fullscreen dialog to which we will add the content view [originalDialogView] of @@ -221,10 +237,11 @@ private class DialogLaunchAnimation( private val hostDialogRoot = FrameLayout(context) /** - * The content view of [originalDialog], which will be stolen from that dialog and added to - * [hostDialogRoot]. + * The parent of the original dialog content view, that serves as a fake window that will have + * the same size as the original dialog window and to which we will set the original dialog + * window background. */ - private var originalDialogView: View? = null + private val dialogContentParent = FrameLayout(context) /** * The background color of [originalDialogView], taking into consideration the [originalDialog] @@ -246,6 +263,11 @@ private class DialogLaunchAnimation( private var isTouchSurfaceGhostDrawn = false private var isOriginalDialogViewLaidOut = false + private var backgroundLayoutListener = if (animateBackgroundBoundsChange) { + AnimatedBoundsLayoutListener() + } else { + null + } fun start() { // Show the host (fullscreen) dialog, to which we will add the stolen dialog view. @@ -374,9 +396,6 @@ private class DialogLaunchAnimation( } private fun showDialogFromView(dialogView: View) { - // Save the dialog view for later as we will need it for the close animation. - this.originalDialogView = dialogView - // Close the dialog when clicking outside of it. hostDialogRoot.setOnClickListener { hostDialog.dismiss() } dialogView.isClickable = true @@ -394,29 +413,46 @@ private class DialogLaunchAnimation( throw IllegalStateException("Dialogs with no backgrounds on window are not supported") } - dialogView.setBackgroundResource(backgroundRes) + // Add a parent view to the original dialog view to which we will set the original dialog + // window background. This View serves as a fake window with background, so that we are sure + // that we don't override the dialog view paddings with the window background that usually + // has insets. + dialogContentParent.setBackgroundResource(backgroundRes) + hostDialogRoot.addView( + dialogContentParent, + + // We give it the size of its original dialog window. + FrameLayout.LayoutParams( + originalDialog.window.attributes.width, + originalDialog.window.attributes.height, + Gravity.CENTER + ) + ) + + // Make the dialog view parent invisible for now, to make sure it's not drawn yet. + dialogContentParent.visibility = View.INVISIBLE + + val background = dialogContentParent.background!! originalDialogBackgroundColor = - GhostedViewLaunchAnimatorController.findGradientDrawable(dialogView.background!!) + GhostedViewLaunchAnimatorController.findGradientDrawable(background) ?.color ?.defaultColor ?: Color.BLACK - // Add the dialog view to the host (fullscreen) dialog and make it invisible to make sure - // it's not drawn yet. + // Add the dialog view to its parent (that has the original window background). (dialogView.parent as? ViewGroup)?.removeView(dialogView) - hostDialogRoot.addView( + dialogContentParent.addView( dialogView, - // We give it the size of its original dialog window. + // It should match its parent size, which is sized the same as the original dialog + // window. FrameLayout.LayoutParams( - originalDialog.window.attributes.width, - originalDialog.window.attributes.height, - Gravity.CENTER + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT ) ) - dialogView.visibility = View.INVISIBLE // Start the animation when the dialog is laid out in the center of the host dialog. - dialogView.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { + dialogContentParent.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { override fun onLayoutChange( view: View, left: Int, @@ -428,7 +464,7 @@ private class DialogLaunchAnimation( oldRight: Int, oldBottom: Int ) { - dialogView.removeOnLayoutChangeListener(this) + dialogContentParent.removeOnLayoutChangeListener(this) isOriginalDialogViewLaidOut = true maybeStartLaunchAnimation() @@ -479,6 +515,13 @@ private class DialogLaunchAnimation( if (dismissRequested) { hostDialog.dismiss() } + + // If necessary, we animate the dialog background when its bounds change. We do it + // at the end of the launch animation, because the lauch animation already correctly + // handles bounds changes. + if (backgroundLayoutListener != null) { + dialogContentParent.addOnLayoutChangeListener(backgroundLayoutListener) + } } ) } @@ -548,7 +591,11 @@ private class DialogLaunchAnimation( } touchSurface.visibility = View.VISIBLE - originalDialogView!!.visibility = View.INVISIBLE + dialogContentParent.visibility = View.INVISIBLE + + if (backgroundLayoutListener != null) { + dialogContentParent.removeOnLayoutChangeListener(backgroundLayoutListener) + } // The animated ghost was just removed. We create a temporary ghost that will be // removed only once we draw the touch surface, to avoid flickering that would @@ -578,12 +625,10 @@ private class DialogLaunchAnimation( onLaunchAnimationStart: () -> Unit = {}, onLaunchAnimationEnd: () -> Unit = {} ) { - val dialogView = this.originalDialogView!! - // Create 2 ghost controllers to animate both the dialog and the touch surface in the host // dialog. - val startView = if (isLaunching) touchSurface else dialogView - val endView = if (isLaunching) dialogView else touchSurface + val startView = if (isLaunching) touchSurface else dialogContentParent + val endView = if (isLaunching) dialogContentParent else touchSurface val startViewController = GhostedViewLaunchAnimatorController(startView) val endViewController = GhostedViewLaunchAnimatorController(endView) startViewController.launchContainer = hostDialogRoot @@ -662,4 +707,81 @@ private class DialogLaunchAnimation( return (touchSurface.parent as? View)?.isShown ?: true } + + /** A layout listener to animate the change of bounds of the dialog background. */ + class AnimatedBoundsLayoutListener : View.OnLayoutChangeListener { + companion object { + private const val ANIMATION_DURATION = 500L + } + + private var lastBounds: Rect? = null + private var currentAnimator: ValueAnimator? = null + + override fun onLayoutChange( + view: View, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int + ) { + // Don't animate if bounds didn't actually change. + if (left == oldLeft && top == oldTop && right == oldRight && bottom == oldBottom) { + // Make sure that we that the last bounds set by the animator were not overridden. + lastBounds?.let { bounds -> + view.left = bounds.left + view.top = bounds.top + view.right = bounds.right + view.bottom = bounds.bottom + } + return + } + + if (lastBounds == null) { + lastBounds = Rect(oldLeft, oldTop, oldRight, oldBottom) + } + + val bounds = lastBounds!! + val startLeft = bounds.left + val startTop = bounds.top + val startRight = bounds.right + val startBottom = bounds.bottom + + currentAnimator?.cancel() + currentAnimator = null + + val animator = ValueAnimator.ofFloat(0f, 1f).apply { + duration = ANIMATION_DURATION + interpolator = Interpolators.STANDARD + + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + currentAnimator = null + } + }) + + addUpdateListener { animatedValue -> + val progress = animatedValue.animatedFraction + + // Compute new bounds. + bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt() + bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt() + bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt() + bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt() + + // Set the new bounds. + view.left = bounds.left + view.top = bounds.top + view.right = bounds.right + view.bottom = bounds.bottom + } + } + + currentAnimator = animator + animator.start() + } + } } 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/drawable/stat_sys_managed_profile_status.xml b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml index c415ecd4f0a8..88914ded15c8 100644 --- a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml +++ b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml @@ -20,5 +20,5 @@ android:viewportHeight="24"> <path android:fillColor="@android:color/white" - android:pathData="M20,6h-4V4c0-1.1-0.9-2-2-2h-4C8.9,2,8,2.9,8,4v2H4C2.9,6,2,6.9,2,8l0,11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8 C22,6.9,21.1,6,20,6z M10,4h4v2h-4V4z M20,19H4V8h16V19z" /> + android:pathData="@*android:string/config_work_badge_path_24" /> </vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/global_screenshot_static.xml b/packages/SystemUI/res/layout/global_screenshot_static.xml index 21c5ab04eb9a..b319d44da500 100644 --- a/packages/SystemUI/res/layout/global_screenshot_static.xml +++ b/packages/SystemUI/res/layout/global_screenshot_static.xml @@ -94,7 +94,7 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:elevation="@dimen/screenshot_preview_elevation" - android:contentDescription="@string/screenshot_edit_label" + android:contentDescription="@string/screenshot_edit_description" android:scaleType="fitEnd" android:background="@drawable/screenshot_preview_background" android:adjustViewBounds="true" diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml index 543b7d77243b..9495ee6f3139 100644 --- a/packages/SystemUI/res/layout/qs_user_dialog_content.xml +++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml @@ -16,78 +16,74 @@ ~ limitations under the License. --> -<FrameLayout +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:sysui="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="wrap_content"> - <androidx.constraintlayout.widget.ConstraintLayout - android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="24dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" +> + <TextView + android:id="@+id/title" android:layout_height="wrap_content" - android:padding="24dp" - android:layout_marginStart="16dp" - android:layout_marginEnd="16dp" - > - <TextView - android:id="@+id/title" - android:layout_height="wrap_content" - android:layout_width="0dp" - android:textAlignment="center" - android:text="@string/qs_user_switch_dialog_title" - android:textAppearance="@style/TextAppearance.QSDialog.Title" - android:layout_marginBottom="32dp" - sysui:layout_constraintTop_toTopOf="parent" - sysui:layout_constraintStart_toStartOf="parent" - sysui:layout_constraintEnd_toEndOf="parent" - sysui:layout_constraintBottom_toTopOf="@id/grid" - /> - - <com.android.systemui.qs.PseudoGridView - android:id="@+id/grid" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="28dp" - sysui:verticalSpacing="4dp" - sysui:horizontalSpacing="4dp" - sysui:fixedChildWidth="80dp" - sysui:layout_constraintTop_toBottomOf="@id/title" - sysui:layout_constraintStart_toStartOf="parent" - sysui:layout_constraintEnd_toEndOf="parent" - sysui:layout_constraintBottom_toTopOf="@id/barrier" + android:layout_width="0dp" + android:textAlignment="center" + android:text="@string/qs_user_switch_dialog_title" + android:textAppearance="@style/TextAppearance.QSDialog.Title" + android:layout_marginBottom="32dp" + sysui:layout_constraintTop_toTopOf="parent" + sysui:layout_constraintStart_toStartOf="parent" + sysui:layout_constraintEnd_toEndOf="parent" + sysui:layout_constraintBottom_toTopOf="@id/grid" /> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/barrier" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - sysui:barrierDirection="top" - sysui:constraint_referenced_ids="settings,done" - /> + <com.android.systemui.qs.PseudoGridView + android:id="@+id/grid" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="28dp" + sysui:verticalSpacing="4dp" + sysui:horizontalSpacing="4dp" + sysui:fixedChildWidth="80dp" + sysui:layout_constraintTop_toBottomOf="@id/title" + sysui:layout_constraintStart_toStartOf="parent" + sysui:layout_constraintEnd_toEndOf="parent" + sysui:layout_constraintBottom_toTopOf="@id/barrier" + /> - <Button - android:id="@+id/settings" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:text="@string/quick_settings_more_user_settings" - sysui:layout_constraintTop_toBottomOf="@id/barrier" - sysui:layout_constraintBottom_toBottomOf="parent" - sysui:layout_constraintStart_toStartOf="parent" - sysui:layout_constraintEnd_toStartOf="@id/done" - sysui:layout_constraintHorizontal_chainStyle="spread_inside" - style="@style/Widget.QSDialog.Button.BorderButton" - /> + <androidx.constraintlayout.widget.Barrier + android:id="@+id/barrier" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + sysui:barrierDirection="top" + sysui:constraint_referenced_ids="settings,done" + /> - <Button - android:id="@+id/done" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:text="@string/quick_settings_done" - sysui:layout_constraintTop_toBottomOf="@id/barrier" - sysui:layout_constraintBottom_toBottomOf="parent" - sysui:layout_constraintStart_toEndOf="@id/settings" - sysui:layout_constraintEnd_toEndOf="parent" - style="@style/Widget.QSDialog.Button" - /> + <Button + android:id="@+id/settings" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:text="@string/quick_settings_more_user_settings" + sysui:layout_constraintTop_toBottomOf="@id/barrier" + sysui:layout_constraintBottom_toBottomOf="parent" + sysui:layout_constraintStart_toStartOf="parent" + sysui:layout_constraintEnd_toStartOf="@id/done" + sysui:layout_constraintHorizontal_chainStyle="spread_inside" + style="@style/Widget.QSDialog.Button.BorderButton" + /> + + <Button + android:id="@+id/done" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:text="@string/quick_settings_done" + sysui:layout_constraintTop_toBottomOf="@id/barrier" + sysui:layout_constraintBottom_toBottomOf="parent" + sysui:layout_constraintStart_toEndOf="@id/settings" + sysui:layout_constraintEnd_toEndOf="parent" + style="@style/Widget.QSDialog.Button" + /> - </androidx.constraintlayout.widget.ConstraintLayout> -</FrameLayout>
\ No newline at end of file +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml index c122829c01b6..e43a149a6cd9 100644 --- a/packages/SystemUI/res/layout/screen_record_dialog.xml +++ b/packages/SystemUI/res/layout/screen_record_dialog.xml @@ -17,8 +17,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" - android:background="@drawable/rounded_bg_full"> + android:orientation="vertical"> <!-- Scrollview is necessary to fit everything in landscape layout --> <ScrollView diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 986f82f98565..6b26d0744c98 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -241,6 +241,8 @@ <string name="screenshot_edit_label">Edit</string> <!-- Content description indicating that tapping the element will allow editing the screenshot [CHAR LIMIT=NONE] --> <string name="screenshot_edit_description">Edit screenshot</string> + <!-- Content description indicating that tapping the element will allow sharing the screenshot [CHAR LIMIT=NONE] --> + <string name="screenshot_share_description">Share screenshot</string> <!-- Label for UI element which allows the user to capture additional off-screen content in a screenshot. [CHAR LIMIT=30] --> <string name="screenshot_scroll_label">Capture more</string> <!-- Content description indicating that tapping a button will dismiss the screenshots UI [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 3f855c762273..4d2986f40a33 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -952,6 +952,7 @@ <item name="android:lineHeight">20sp</item> <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> <item name="android:stateListAnimator">@null</item> + <item name="android:layout_marginHorizontal">4dp</item> </style> <style name="Widget.QSDialog.Button.BorderButton"> diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml index c3510b61e68a..12e446f53634 100644 --- a/packages/SystemUI/res/xml/media_collapsed.xml +++ b/packages/SystemUI/res/xml/media_collapsed.xml @@ -58,7 +58,7 @@ <!-- Song name --> <Constraint android:id="@+id/header_title" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/qs_media_info_margin" android:layout_marginEnd="@dimen/qs_center_guideline_padding" @@ -71,7 +71,7 @@ <!-- Artist name --> <Constraint android:id="@+id/header_artist" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constrainedWidth="true" android:layout_marginTop="@dimen/qs_media_info_spacing" diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 25db478e57b7..d172006d986d 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -45,9 +45,6 @@ android_library { ":wm_shell-aidls", ":wm_shell_util-sources", ], - libs: [ - "SystemUI-flags", - ], static_libs: [ "PluginCoreLib", "androidx.dynamicanimation_dynamicanimation", @@ -62,6 +59,7 @@ java_library { srcs: [ "src/com/android/systemui/flags/Flag.kt", ], + include_srcs: true, static_kotlin_stdlib: false, java_version: "1.8", min_sdk_version: "current", @@ -78,7 +76,6 @@ java_library { ], static_libs: [ "SystemUI-flag-types", - "SystemUI-flags", ], java_version: "1.8", min_sdk_version: "current", diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt index 68834bc2aa23..9574101b466c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt @@ -16,37 +16,168 @@ package com.android.systemui.flags -interface Flag<T> { +import android.os.Parcel +import android.os.Parcelable + +interface Flag<T> : Parcelable { val id: Int val default: T + val resourceOverride: Int + + override fun describeContents() = 0 + + fun hasResourceOverride(): Boolean { + return resourceOverride != -1 + } } +// Consider using the "parcelize" kotlin library. + data class BooleanFlag @JvmOverloads constructor( override val id: Int, - override val default: Boolean = false -) : Flag<Boolean> + override val default: Boolean = false, + override val resourceOverride: Int = -1 +) : Flag<Boolean> { + + companion object { + @JvmField + val CREATOR = object : Parcelable.Creator<BooleanFlag> { + override fun createFromParcel(parcel: Parcel) = BooleanFlag(parcel) + override fun newArray(size: Int) = arrayOfNulls<BooleanFlag>(size) + } + } + + private constructor(parcel: Parcel) : this( + id = parcel.readInt(), + default = parcel.readBoolean() + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(id) + parcel.writeBoolean(default) + } +} data class StringFlag @JvmOverloads constructor( override val id: Int, - override val default: String = "" -) : Flag<String> + override val default: String = "", + override val resourceOverride: Int = -1 +) : Flag<String> { + companion object { + @JvmField + val CREATOR = object : Parcelable.Creator<StringFlag> { + override fun createFromParcel(parcel: Parcel) = StringFlag(parcel) + override fun newArray(size: Int) = arrayOfNulls<StringFlag>(size) + } + } + + private constructor(parcel: Parcel) : this( + id = parcel.readInt(), + default = parcel.readString() ?: "" + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(id) + parcel.writeString(default) + } +} data class IntFlag @JvmOverloads constructor( override val id: Int, - override val default: Int = 0 -) : Flag<Int> + override val default: Int = 0, + override val resourceOverride: Int = -1 +) : Flag<Int> { + + companion object { + @JvmField + val CREATOR = object : Parcelable.Creator<IntFlag> { + override fun createFromParcel(parcel: Parcel) = IntFlag(parcel) + override fun newArray(size: Int) = arrayOfNulls<IntFlag>(size) + } + } + + private constructor(parcel: Parcel) : this( + id = parcel.readInt(), + default = parcel.readInt() + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(id) + parcel.writeInt(default) + } +} data class LongFlag @JvmOverloads constructor( override val id: Int, - override val default: Long = 0 -) : Flag<Long> + override val default: Long = 0, + override val resourceOverride: Int = -1 +) : Flag<Long> { + + companion object { + @JvmField + val CREATOR = object : Parcelable.Creator<LongFlag> { + override fun createFromParcel(parcel: Parcel) = LongFlag(parcel) + override fun newArray(size: Int) = arrayOfNulls<LongFlag>(size) + } + } + + private constructor(parcel: Parcel) : this( + id = parcel.readInt(), + default = parcel.readLong() + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(id) + parcel.writeLong(default) + } +} data class FloatFlag @JvmOverloads constructor( override val id: Int, - override val default: Float = 0f -) : Flag<Float> + override val default: Float = 0f, + override val resourceOverride: Int = -1 +) : Flag<Float> { + + companion object { + @JvmField + val CREATOR = object : Parcelable.Creator<FloatFlag> { + override fun createFromParcel(parcel: Parcel) = FloatFlag(parcel) + override fun newArray(size: Int) = arrayOfNulls<FloatFlag>(size) + } + } + + private constructor(parcel: Parcel) : this( + id = parcel.readInt(), + default = parcel.readFloat() + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(id) + parcel.writeFloat(default) + } +} data class DoubleFlag @JvmOverloads constructor( override val id: Int, - override val default: Double = 0.0 -) : Flag<Double>
\ No newline at end of file + override val default: Double = 0.0, + override val resourceOverride: Int = -1 +) : Flag<Double> { + + companion object { + @JvmField + val CREATOR = object : Parcelable.Creator<DoubleFlag> { + override fun createFromParcel(parcel: Parcel) = DoubleFlag(parcel) + override fun newArray(size: Int) = arrayOfNulls<DoubleFlag>(size) + } + } + + private constructor(parcel: Parcel) : this( + id = parcel.readInt(), + default = parcel.readDouble() + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(id) + parcel.writeDouble(default) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt index cbb942bc3015..1dc555e300b4 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt @@ -16,10 +16,13 @@ package com.android.systemui.flags +import android.app.Activity +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.database.ContentObserver import android.net.Uri +import android.os.Bundle import android.os.Handler import android.provider.Settings import androidx.concurrent.futures.CallbackToFutureAdapter @@ -34,10 +37,12 @@ class FlagManager constructor( companion object { const val RECEIVING_PACKAGE = "com.android.systemui" const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG" + const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS" const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS" const val FIELD_ID = "id" const val FIELD_VALUE = "value" const val FIELD_TYPE = "type" + const val FIELD_FLAGS = "flags" const val TYPE_BOOLEAN = "boolean" private const val SETTINGS_PREFIX = "systemui/flags" } @@ -46,14 +51,26 @@ class FlagManager constructor( private val settingsObserver: ContentObserver = SettingsObserver() fun getFlagsFuture(): ListenableFuture<Collection<Flag<*>>> { - val knownFlagMap = Flags.collectFlags() - // Possible todo in the future: query systemui async to actually get the known flag ids. - return CallbackToFutureAdapter.getFuture( - CallbackToFutureAdapter.Resolver { - completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> -> - completer.set(knownFlagMap.values as Collection<Flag<*>>) - "Retrieving Flags" - }) + val intent = Intent(ACTION_GET_FLAGS) + intent.setPackage(RECEIVING_PACKAGE) + + return CallbackToFutureAdapter.getFuture { + completer: CallbackToFutureAdapter.Completer<Any?> -> + context.sendOrderedBroadcast(intent, null, + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val extras: Bundle? = getResultExtras(false) + val listOfFlags: java.util.ArrayList<Flag<*>>? = + extras?.getParcelableArrayList(FIELD_FLAGS) + if (listOfFlags != null) { + completer.set(listOfFlags) + } else { + completer.setException(NoFlagResultsException()) + } + } + }, null, Activity.RESULT_OK, "extra data", null) + "QueryingFlags" + } as ListenableFuture<Collection<Flag<*>>> } fun setFlagValue(id: Int, enabled: Boolean) { @@ -149,4 +166,7 @@ class FlagManager constructor( } } -class InvalidFlagStorageException : Exception("Data found but is invalid")
\ No newline at end of file +class InvalidFlagStorageException : Exception("Data found but is invalid") + +class NoFlagResultsException : Exception( + "SystemUI failed to communicate its flags back successfully")
\ No newline at end of file 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/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 9311966f82f2..acfa3c84a4ba 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java @@ -16,22 +16,26 @@ package com.android.systemui.flags; +import static com.android.systemui.flags.FlagManager.ACTION_GET_FLAGS; import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG; +import static com.android.systemui.flags.FlagManager.FIELD_FLAGS; import static com.android.systemui.flags.FlagManager.FIELD_ID; import static com.android.systemui.flags.FlagManager.FIELD_VALUE; -import static com.android.systemui.flags.FlagManager.FLAGS_PERMISSION; 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; @@ -61,30 +65,52 @@ 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; - IntentFilter filter = new IntentFilter(ACTION_SET_FLAG); - context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null); + mResources = resources; + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_SET_FLAG); + filter.addAction(ACTION_GET_FLAGS); + context.registerReceiver(mReceiver, filter, null, null); 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 { @@ -95,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) { @@ -151,9 +181,15 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { if (action == null) { return; } - if (ACTION_SET_FLAG.equals(action)) { handleSetFlag(intent.getExtras()); + } else if (ACTION_GET_FLAGS.equals(action)) { + Map<Integer, Flag<?>> knownFlagMap = Flags.collectFlags(); + ArrayList<Flag<?>> flags = new ArrayList<>(knownFlagMap.values()); + Bundle extras = getResultExtras(true); + if (extras != null) { + extras.putParcelableArrayList(FIELD_FLAGS, flags); + } } } 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/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java index cfef6cb399ed..907943a9203d 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java @@ -261,8 +261,10 @@ public class CarrierTextManager { mCarrierTextCallback = callback; if (mNetworkSupported.get()) { // Keyguard update monitor expects callbacks from main thread - mMainExecutor.execute(() -> mKeyguardUpdateMonitor.registerCallback(mCallback)); - mWakefulnessLifecycle.addObserver(mWakefulnessObserver); + mMainExecutor.execute(() -> { + mKeyguardUpdateMonitor.registerCallback(mCallback); + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); + }); mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); } else { // Don't listen and clear out the text when the device isn't a phone. @@ -272,8 +274,10 @@ public class CarrierTextManager { } } else { mCarrierTextCallback = null; - mMainExecutor.execute(() -> mKeyguardUpdateMonitor.removeCallback(mCallback)); - mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); + mMainExecutor.execute(() -> { + mKeyguardUpdateMonitor.removeCallback(mCallback); + mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); + }); mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index a41a49799c2d..a3801546fa98 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -102,7 +102,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -323,7 +322,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mLockIconPressed; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private final Executor mBackgroundExecutor; - private int mLockScreenMode; /** * Short delay before restarting fingerprint authentication after a successful try. This should @@ -1736,11 +1734,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab DumpManager dumpManager, RingerModeTracker ringerModeTracker, @Background Executor backgroundExecutor, + @Main Executor mainExecutor, StatusBarStateController statusBarStateController, LockPatternUtils lockPatternUtils, AuthController authController, TelephonyListenerManager telephonyListenerManager, - FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, LatencyTracker latencyTracker) { mContext = context; @@ -1966,6 +1964,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mBiometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback); } + // in case authenticators aren't registered yet at this point: + mAuthController.addCallback(new AuthController.Callback() { + @Override + public void onAllAuthenticatorsRegistered() { + } + + @Override + public void onEnrollmentsChanged() { + mainExecutor.execute(() -> updateBiometricListeningState()); + } + }); updateBiometricListeningState(); if (mFpm != null) { mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback); @@ -2043,7 +2052,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * @return if udfps is available on this device. will return true even if the user hasn't - * enrolled udfps. + * enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered. */ public boolean isUdfpsAvailable() { return mAuthController.getUdfpsProps() != null @@ -2092,7 +2101,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return; } - // TODO: Add support for multiple fingerprint sensors, b/173730729 updateUdfpsEnrolled(getCurrentUser()); final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsEnrolled()); final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING @@ -2399,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); } @@ -2982,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 */ diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 3c80a186a4a7..8a0b5b8704e6 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -695,11 +695,20 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { @Override public void onAllAuthenticatorsRegistered() { - // must be called from the main thread since it may update the views - mExecutor.execute(() -> { - updateIsUdfpsEnrolled(); - updateConfiguration(); - }); + updateUdfpsConfig(); + } + + @Override + public void onEnrollmentsChanged() { + updateUdfpsConfig(); } }; + + private void updateUdfpsConfig() { + // must be called from the main thread since it may update the views + mExecutor.execute(() -> { + updateIsUdfpsEnrolled(); + updateConfiguration(); + }); + } } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 80c3616f693e..e566ccb49e3b 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -534,6 +534,9 @@ public class ScreenDecorations extends SystemUI implements Tunable { lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; + // FLAG_SLIPPERY can only be set by trusted overlays + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; + if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) { lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; } 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/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index f4b446b50c9e..1226dca1f306 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -142,6 +142,10 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, mUdfpsEnrolledForUser.put(userId, hasEnrollments); } } + + for (Callback cb : mCallbacks) { + cb.onEnrollmentsChanged(); + } } }; @@ -844,5 +848,11 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, * registered before this call, this callback will never be triggered. */ void onAllAuthenticatorsRegistered(); + + /** + * Called when UDFPS enrollments have changed. This is called after boot and on changes to + * enrollment. + */ + void onEnrollmentsChanged(); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 8b04bf59658a..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 } } @@ -292,10 +304,16 @@ class AuthRippleController @Inject constructor( } } - private val authControllerCallback = AuthController.Callback { - updateSensorLocation() - updateUdfpsDependentParams() - } + private val authControllerCallback = + object : AuthController.Callback { + override fun onAllAuthenticatorsRegistered() { + updateSensorLocation() + updateUdfpsDependentParams() + } + + override fun onEnrollmentsChanged() { + } + } private fun updateUdfpsDependentParams() { authController.udfpsProps?.let { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt index dacc169f85ee..7bb4708443e9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics import android.animation.Animator import android.animation.AnimatorListenerAdapter +import android.app.ActivityTaskManager import android.content.Context import android.graphics.PixelFormat import android.graphics.PorterDuff @@ -24,6 +25,7 @@ import android.graphics.PorterDuffColorFilter import android.graphics.Rect import android.hardware.biometrics.BiometricOverlayConstants import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS import android.hardware.display.DisplayManager import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintSensorPropertiesInternal @@ -61,6 +63,7 @@ class SidefpsController @Inject constructor( private val layoutInflater: LayoutInflater, fingerprintManager: FingerprintManager?, private val windowManager: WindowManager, + private val activityTaskManager: ActivityTaskManager, overviewProxyService: OverviewProxyService, displayManager: DisplayManager, @Main mainExecutor: DelayableExecutor, @@ -130,7 +133,7 @@ class SidefpsController @Inject constructor( override fun show( sensorId: Int, @BiometricOverlayConstants.ShowReason reason: Int - ) = if (reason.isReasonToShow()) doShow() else hide(sensorId) + ) = if (reason.isReasonToShow(activityTaskManager)) doShow() else hide(sensorId) private fun doShow() = mainExecutor.execute { if (overlayView == null) { @@ -228,11 +231,19 @@ class SidefpsController @Inject constructor( } @BiometricOverlayConstants.ShowReason -private fun Int.isReasonToShow(): Boolean = when (this) { +private fun Int.isReasonToShow(activityTaskManager: ActivityTaskManager): Boolean = when (this) { REASON_AUTH_KEYGUARD -> false + REASON_AUTH_SETTINGS -> when (activityTaskManager.topClass()) { + // TODO(b/186176653): exclude fingerprint overlays from this list view + "com.android.settings.biometrics.fingerprint.FingerprintSettings" -> false + else -> true + } else -> true } +private fun ActivityTaskManager.topClass(): String = + getTasks(1).firstOrNull()?.topActivity?.className ?: "" + @RawRes private fun Display.asSideFpsAnimation(): Int = when (rotation) { Surface.ROTATION_0 -> R.raw.sfps_pulse diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java index 70bc56bd1425..94743407f03d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java @@ -63,13 +63,13 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView> @Override protected void onViewAttached() { - mPanelExpansionStateManager.addListener(mPanelExpansionListener); + mPanelExpansionStateManager.addExpansionListener(mPanelExpansionListener); mDumpManger.registerDumpable(getDumpTag(), this); } @Override protected void onViewDetached() { - mPanelExpansionStateManager.removeListener(mPanelExpansionListener); + mPanelExpansionStateManager.removeExpansionListener(mPanelExpansionListener); mDumpManger.unregisterDumpable(getDumpTag()); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 81a4d590aec6..eb36915f9753 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -760,10 +760,12 @@ public class UdfpsController implements DozeReceiver { mOnFingerDown = false; mView.setSensorProperties(mSensorProps); mView.setHbmProvider(mHbmProvider); - UdfpsAnimationViewController animation = inflateUdfpsAnimation(reason); + UdfpsAnimationViewController<?> animation = inflateUdfpsAnimation(reason); mAttemptedToDismissKeyguard = false; - animation.init(); - mView.setAnimationViewController(animation); + if (animation != null) { + animation.init(); + mView.setAnimationViewController(animation); + } mOrientationListener.enable(); // This view overlaps the sensor area, so prevent it from being selectable @@ -786,7 +788,8 @@ public class UdfpsController implements DozeReceiver { } } - private UdfpsAnimationViewController inflateUdfpsAnimation(int reason) { + @Nullable + private UdfpsAnimationViewController<?> inflateUdfpsAnimation(int reason) { switch (reason) { case BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR: case BiometricOverlayConstants.REASON_ENROLL_ENROLLING: @@ -830,6 +833,7 @@ public class UdfpsController implements DozeReceiver { mDumpManager ); case BiometricOverlayConstants.REASON_AUTH_OTHER: + case BiometricOverlayConstants.REASON_AUTH_SETTINGS: UdfpsFpmOtherView authOtherView = (UdfpsFpmOtherView) mInflater.inflate(R.layout.udfps_fpm_other_view, null); mView.addView(authOtherView); @@ -840,7 +844,7 @@ public class UdfpsController implements DozeReceiver { mDumpManager ); default: - Log.d(TAG, "Animation for reason " + reason + " not supported yet"); + Log.e(TAG, "Animation for reason " + reason + " not supported yet"); return null; } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index 2034ff35be70..1f01fc5a4b3d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java @@ -115,7 +115,8 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mBlueFill.setColor(context.getColor(R.color.udfps_moving_target_fill)); mBlueFill.setStyle(Paint.Style.FILL); - mMovingTargetFpIcon = context.getResources().getDrawable(R.drawable.ic_fingerprint, null); + mMovingTargetFpIcon = context.getResources() + .getDrawable(R.drawable.ic_kg_fingerprint, null); mMovingTargetFpIcon.setTint(Color.WHITE); mMovingTargetFpIcon.mutate(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 495366c8f69f..d1ea45cac081 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -126,7 +126,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN; mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing(); mConfigurationController.addCallback(mConfigurationListener); - mPanelExpansionStateManager.addListener(mPanelExpansionListener); + mPanelExpansionStateManager.addExpansionListener(mPanelExpansionListener); updateAlpha(); updatePauseAuth(); @@ -145,7 +145,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor); mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); mConfigurationController.removeCallback(mConfigurationListener); - mPanelExpansionStateManager.removeListener(mPanelExpansionListener); + mPanelExpansionStateManager.removeExpansionListener(mPanelExpansionListener); if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) { mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt index bf84d77224b1..7e5b26732e00 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt @@ -28,6 +28,7 @@ import android.os.UserHandle import android.service.controls.Control import android.service.controls.ControlsProviderService import android.util.Log +import java.lang.ClassCastException /** * Proxy to launch in user 0 @@ -59,20 +60,29 @@ class ControlsRequestReceiver : BroadcastReceiver() { return } - val packageName = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME) - ?.packageName + val targetComponent = try { + intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME) + } catch (e: ClassCastException) { + Log.e(TAG, "Malformed intent extra ComponentName", e) + return + } + + val control = try { + intent.getParcelableExtra<Control>(ControlsProviderService.EXTRA_CONTROL) + } catch (e: ClassCastException) { + Log.e(TAG, "Malformed intent extra Control", e) + return + } + + val packageName = targetComponent?.packageName if (packageName == null || !isPackageInForeground(context, packageName)) { return } val activityIntent = Intent(context, ControlsRequestDialog::class.java).apply { - Intent.EXTRA_COMPONENT_NAME.let { - putExtra(it, intent.getParcelableExtra<ComponentName>(it)) - } - ControlsProviderService.EXTRA_CONTROL.let { - putExtra(it, intent.getParcelableExtra<Control>(it)) - } + putExtra(Intent.EXTRA_COMPONENT_NAME, targetComponent) + putExtra(ControlsProviderService.EXTRA_CONTROL, control) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) } activityIntent.putExtra(Intent.EXTRA_USER_ID, context.userId) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index de8ed7013ab2..bce878434aa1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -22,7 +22,6 @@ import com.android.systemui.ForegroundServicesDialog; import com.android.systemui.keyguard.WorkLockActivity; import com.android.systemui.people.PeopleSpaceActivity; import com.android.systemui.people.widget.LaunchConversationActivity; -import com.android.systemui.screenrecord.ScreenRecordDialog; import com.android.systemui.screenshot.LongScreenshotActivity; import com.android.systemui.sensorprivacy.SensorUseStartedActivity; import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity; @@ -67,12 +66,6 @@ public abstract class DefaultActivityBinder { @ClassKey(BrightnessDialog.class) public abstract Activity bindBrightnessDialog(BrightnessDialog activity); - /** Inject into ScreenRecordDialog */ - @Binds - @IntoMap - @ClassKey(ScreenRecordDialog.class) - public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity); - /** Inject into UsbDebuggingActivity. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 669965bcbea5..25115200ba19 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -115,7 +115,7 @@ public class DozeLog implements Dumpable { } /** - * Appends dozing event to the logs + * Appends dozing event to the logs. Logs current dozing state when entering/exiting AOD. * @param dozing true if dozing, else false */ public void traceDozing(boolean dozing) { @@ -124,6 +124,14 @@ public class DozeLog implements Dumpable { } /** + * Appends dozing event to the logs when dozing has changed in AOD. + * @param dozing true if we're now dozing, else false + */ + public void traceDozingChanged(boolean dozing) { + mLogger.logDozingChanged(dozing); + } + + /** * Appends dozing event to the logs * @param suppressed true if dozing is suppressed */ diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index d79bf22cced2..4ba6b51c83c8 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -66,6 +66,14 @@ class DozeLogger @Inject constructor( }) } + fun logDozingChanged(isDozing: Boolean) { + buffer.log(TAG, INFO, { + bool1 = isDozing + }, { + "Dozing changed dozing=$bool1" + }) + } + fun logDozingSuppressed(isDozingSuppressed: Boolean) { buffer.log(TAG, INFO, { bool1 = isDozingSuppressed diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index 8f1486b0c7cb..908397bd775c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -107,6 +107,11 @@ public class DozeScreenState implements DozeMachine.Part { public void onAllAuthenticatorsRegistered() { updateUdfpsController(); } + + @Override + public void onEnrollmentsChanged() { + updateUdfpsController(); + } }); } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java deleted file mode 100644 index e78646a4839d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java +++ /dev/null @@ -1,175 +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 android.content.res.Resources; -import android.util.SparseArray; - -import androidx.annotation.BoolRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.systemui.Dumpable; -import com.android.systemui.R; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.util.wrapper.BuildInfo; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; - -import javax.inject.Inject; -/** - * Reads and caches feature flags for quick access - * - * Feature flags must be defined as boolean resources. For example:t - * - * {@code - * <bool name="flag_foo_bar_baz">false</bool> - * } - * - * It is strongly recommended that the name of the resource begin with "flag_". - * - * Flags can be overridden via adb on development builds. For example, to override the flag from the - * previous example, do the following: - * - * {@code - * $ adb shell setprop persist.systemui.flag_foo_bar_baz 1 - * } - * - * Note that all storage keys begin with "flag_", even if their associated resId does not. - * - * Calls to this class should probably be wrapped by a method in {@link FeatureFlags}. - */ -@SysUISingleton -public class FeatureFlagReader implements Dumpable { - private final Resources mResources; - private final boolean mAreFlagsOverrideable; - private final SystemPropertiesHelper mSystemPropertiesHelper; - private final SparseArray<CachedFlag> mCachedFlags = new SparseArray<>(); - - private final FlagReader mFlagReader; - - @Inject - public FeatureFlagReader( - @Main Resources resources, - BuildInfo build, - DumpManager dumpManager, - SystemPropertiesHelper systemPropertiesHelper, - FlagReader reader) { - mResources = resources; - mFlagReader = reader; - mSystemPropertiesHelper = systemPropertiesHelper; - mAreFlagsOverrideable = - build.isDebuggable() && mResources.getBoolean(R.bool.are_flags_overrideable); - dumpManager.registerDumpable("FeatureFlags", this); - } - - boolean isEnabled(BooleanFlag flag) { - return mFlagReader.isEnabled(flag.getId(), flag.getDefault()); - } - - void addListener(FlagReader.Listener listener) { - mFlagReader.addListener(listener); - } - - void removeListener(FlagReader.Listener listener) { - mFlagReader.removeListener(listener); - } - - /** - * Returns true if the specified feature flag has been enabled. - * - * @param resId The backing boolean resource that determines the value of the flag. This value - * can be overridden via DeviceConfig on development builds. - */ - public boolean isEnabled(@BoolRes int resId) { - synchronized (mCachedFlags) { - CachedFlag cachedFlag = mCachedFlags.get(resId); - - if (cachedFlag == null) { - String name = resourceIdToFlagName(resId); - boolean value = mResources.getBoolean(resId); - if (mAreFlagsOverrideable) { - value = mSystemPropertiesHelper.getBoolean(flagNameToStorageKey(name), value); - } - - cachedFlag = new CachedFlag(name, value); - mCachedFlags.put(resId, cachedFlag); - } - - return cachedFlag.value; - } - } - - private String resourceIdToFlagName(@BoolRes int resId) { - String resName = mResources.getResourceEntryName(resId); - if (resName.startsWith(RESNAME_PREFIX)) { - resName = resName.substring(RESNAME_PREFIX.length()); - } - return resName; - } - - private String flagNameToStorageKey(String flagName) { - if (flagName.startsWith(STORAGE_KEY_PREFIX)) { - return flagName; - } else { - return STORAGE_KEY_PREFIX + flagName; - } - } - - @Nullable - private String storageKeyToFlagName(String configName) { - if (configName.startsWith(STORAGE_KEY_PREFIX)) { - return configName.substring(STORAGE_KEY_PREFIX.length()); - } else { - return null; - } - } - - @Override - public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { - ArrayList<String> flagStrings = new ArrayList<>(mCachedFlags.size()); - for (int i = 0; i < mCachedFlags.size(); i++) { - int key = mCachedFlags.keyAt(i); - // get the object by the key. - CachedFlag flag = mCachedFlags.get(key); - flagStrings.add(" " + RESNAME_PREFIX + flag.name + ": " + flag.value + "\n"); - } - flagStrings.sort(String.CASE_INSENSITIVE_ORDER); - pw.println("AreFlagsOverrideable: " + mAreFlagsOverrideable); - pw.println("Cached FeatureFlags:"); - for (String flagString : flagStrings) { - pw.print(flagString); - } - } - - private static class CachedFlag { - public final String name; - public final boolean value; - - private CachedFlag(String name, boolean value) { - this.name = name; - this.value = value; - } - } - - private static final String STORAGE_KEY_PREFIX = "persist.systemui.flag_"; - private static final String RESNAME_PREFIX = "flag_"; -} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java index ab083a920a10..34f441510a7e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java @@ -21,15 +21,8 @@ import android.util.FeatureFlagUtils; import android.util.Log; import android.widget.Toast; -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import javax.inject.Inject; /** @@ -39,28 +32,13 @@ import javax.inject.Inject; */ @SysUISingleton public class FeatureFlags { - private final FeatureFlagReader mFlagReader; + 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<>(); @Inject - public FeatureFlags(FeatureFlagReader flagReader, Context context) { + 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); } /** @@ -71,23 +49,6 @@ public class FeatureFlags { return mFlagReader.isEnabled(flag); } - /** - * @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); - } - } - public void assertLegacyPipelineEnabled() { if (isNewNotifPipelineRenderingEnabled()) { throw new IllegalStateException("Old pipeline code running w/ new pipeline enabled"); @@ -118,13 +79,11 @@ public class FeatureFlags { } public boolean isPeopleTileEnabled() { - // TODO(b/202860494): different resource overlays have different values. - return mFlagReader.isEnabled(R.bool.flag_conversations); + return isEnabled(Flags.PEOPLE_TILE); } public boolean isMonetEnabled() { - // TODO(b/202860494): used in wallpaper picker. Always true, maybe delete. - return mFlagReader.isEnabled(R.bool.flag_monet); + return isEnabled(Flags.MONET); } public boolean isPMLiteEnabled() { @@ -132,8 +91,7 @@ public class FeatureFlags { } public boolean isChargingRippleEnabled() { - // TODO(b/202860494): different resource overlays have different values. - return mFlagReader.isEnabled(R.bool.flag_charging_ripple); + return isEnabled(Flags.CHARGING_RIPPLE); } public boolean isOngoingCallStatusBarChipEnabled() { @@ -150,8 +108,7 @@ public class FeatureFlags { } public boolean isSmartspaceEnabled() { - // TODO(b/202860494): different resource overlays have different values. - return mFlagReader.isEnabled(R.bool.flag_smartspace); + return isEnabled(Flags.SMARTSPACE); } public boolean isSmartspaceDedupingEnabled() { @@ -194,10 +151,4 @@ public class FeatureFlags { public static boolean isProviderModelSettingEnabled(Context context) { return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); } - - /** 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/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index f09c797a6608..c33aa9e5ee28 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -16,6 +16,8 @@ package com.android.systemui.flags; +import com.android.systemui.R; + import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; @@ -47,7 +49,6 @@ public class Flags { public static final BooleanFlag NOTIFICATION_UPDATES = new BooleanFlag(102, true); - /***************************************/ // 200 - keyguard/lockscreen public static final BooleanFlag KEYGUARD_LAYOUT = @@ -59,6 +60,9 @@ public class Flags { public static final BooleanFlag NEW_UNLOCK_SWIPE_ANIMATION = new BooleanFlag(202, true); + public static final BooleanFlag CHARGING_RIPPLE = + new BooleanFlag(203, false, R.bool.flag_charging_ripple); + /***************************************/ // 300 - power menu public static final BooleanFlag POWER_MENU_LITE = @@ -72,6 +76,9 @@ public class Flags { public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = new BooleanFlag(401, false); + public static final BooleanFlag SMARTSPACE = + new BooleanFlag(402, false, R.bool.flag_smartspace); + /***************************************/ // 500 - quick settings public static final BooleanFlag NEW_USER_SWITCHER = @@ -80,6 +87,9 @@ public class Flags { public static final BooleanFlag COMBINED_QS_HEADERS = new BooleanFlag(501, false); + public static final BooleanFlag PEOPLE_TILE = + new BooleanFlag(502, false, R.bool.flag_conversations); + /***************************************/ // 600- status bar public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS = @@ -96,6 +106,11 @@ public class Flags { public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = new BooleanFlag(702, true); + /***************************************/ + // 800 - general visual/theme + public static final BooleanFlag MONET = + new BooleanFlag(800, true, R.bool.flag_monet); + // Pay no attention to the reflection behind the curtain. // ========================== Curtain ========================== // | | diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index 292b0e291244..fbfb919ede1e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -109,11 +109,9 @@ class MediaDeviceManager @Inject constructor( } @MainThread - private fun processDevice(key: String, oldKey: String?, device: MediaDevice?) { - val enabled = device != null - val data = MediaDeviceData(enabled, device?.iconWithoutBackground, device?.name) + private fun processDevice(key: String, oldKey: String?, device: MediaDeviceData?) { listeners.forEach { - it.onMediaDeviceChanged(key, oldKey, data) + it.onMediaDeviceChanged(key, oldKey, device) } } @@ -135,7 +133,7 @@ class MediaDeviceManager @Inject constructor( get() = controller?.sessionToken private var started = false private var playbackType = PLAYBACK_TYPE_UNKNOWN - private var current: MediaDevice? = null + private var current: MediaDeviceData? = null set(value) { if (!started || value != field) { field = value @@ -201,15 +199,13 @@ class MediaDeviceManager @Inject constructor( @WorkerThread private fun updateCurrent() { - val device = localMediaManager.getCurrentConnectedDevice() - controller?.let { - val route = mr2manager.getRoutingSessionForMediaController(it) - // If we get a null route, then don't trust the device. Just set to null to disable the - // output switcher chip. - current = if (route != null) device else null - } ?: run { - current = device - } + val device = localMediaManager.currentConnectedDevice + val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it)} + + // If we have a controller but get a null route, then don't trust the device + val enabled = device != null && (controller == null || route != null) + val name = route?.name?.toString() ?: device?.name + current = MediaDeviceData(enabled, device?.iconWithoutBackground, name) } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 6a1eae75f9a9..1eef6e87cec0 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(); @@ -1558,7 +1525,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 97bcb00eddbd..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,13 +260,14 @@ public class NavigationBarController implements */ public void createNavigationBars(final boolean includeDefaultDisplay, RegisterStatusBarResult result) { - if (initializeTaskbarIfNecessary()) { - return; - } + updateAccessibilityButtonModeIfNeeded(); + // Don't need to create nav bar on the default display if we initialize TaskBar. + final boolean shouldCreateDefaultNavbar = includeDefaultDisplay + && !initializeTaskbarIfNecessary(); Display[] displays = mDisplayManager.getDisplays(); for (Display display : displays) { - if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) { + if (shouldCreateDefaultNavbar || display.getDisplayId() != DEFAULT_DISPLAY) { createNavigationBar(display, null /* savedState */, result); } } @@ -246,12 +285,15 @@ public class NavigationBarController implements return; } - if (mIsTablet) { + final int displayId = display.getDisplayId(); + final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY; + + // We may show TaskBar on the default display for large screen device. Don't need to create + // navigation bar for this case. + if (mIsTablet && isOnDefaultDisplay) { return; } - final int displayId = display.getDisplayId(); - final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY; final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); try { diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 1784f73e1f53..cdf770f80387 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -178,7 +178,10 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @Override public void setSquishinessFraction(float squishinessFraction) { - // No-op, paged layouts are not squishy. + int nPages = mPages.size(); + for (int i = 0; i < nPages; i++) { + mPages.get(i).setSquishinessFraction(squishinessFraction); + } } private void updateListening() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt index 4854600994aa..c1c146d40e38 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt @@ -3,15 +3,14 @@ package com.android.systemui.qs import android.view.ViewGroup import com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER import com.android.systemui.qs.dagger.QSScope -import com.android.systemui.qs.tileimpl.HeightOverrideable import javax.inject.Inject import javax.inject.Named @QSScope class QSSquishinessController @Inject constructor( - private val qsTileHost: QSTileHost, @Named(QQS_FOOTER) private val qqsFooterActionsView: FooterActionsView, private val qsAnimator: QSAnimator, + private val qsPanelController: QSPanelController, private val quickQSPanelController: QuickQSPanelController ) { @@ -34,18 +33,10 @@ class QSSquishinessController @Inject constructor( * Change the height of all tiles and repositions their siblings. */ private fun updateSquishiness() { - // Update tile positions in the layout + (qsPanelController.tileLayout as QSPanel.QSTileLayout).setSquishinessFraction(squishiness) val tileLayout = quickQSPanelController.tileLayout as TileLayout tileLayout.setSquishinessFraction(squishiness) - // Adjust their heights as well - for (tile in qsTileHost.tiles) { - val tileView = quickQSPanelController.getTileView(tile) - (tileView as? HeightOverrideable)?.let { - it.squishinessFraction = squishiness - } - } - // Calculate how much we should move the footer val tileHeightOffset = tileLayout.height - tileLayout.tilesHeight val footerTopMargin = (qqsFooterActionsView.layoutParams as ViewGroup.MarginLayoutParams) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 0fc4f4a94bda..a923effea1e1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -39,11 +39,11 @@ import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.qs.QSDetail.Callback; +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; import com.android.systemui.statusbar.phone.StatusIconContainer; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.policy.VariableDateView; -import com.android.systemui.statusbar.window.StatusBarWindowView; import java.util.List; @@ -86,6 +86,7 @@ public class QuickStatusBarHeader extends FrameLayout { private TintedIconManager mTintedIconManager; private QSExpansionPathInterpolator mQSExpansionPathInterpolator; + private StatusBarContentInsetsProvider mInsetsProvider; private int mRoundedCornerPadding = 0; private int mWaterfallTopInset; @@ -161,10 +162,12 @@ public class QuickStatusBarHeader extends FrameLayout { void onAttach(TintedIconManager iconManager, QSExpansionPathInterpolator qsExpansionPathInterpolator, List<String> rssiIgnoredSlots, - boolean useCombinedQSHeader) { + boolean useCombinedQSHeader, + StatusBarContentInsetsProvider insetsProvider) { mUseCombinedQSHeader = useCombinedQSHeader; mTintedIconManager = iconManager; mRssiIgnoredSlots = rssiIgnoredSlots; + mInsetsProvider = insetsProvider; int fillColor = Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary); @@ -436,22 +439,20 @@ public class QuickStatusBarHeader extends FrameLayout { public WindowInsets onApplyWindowInsets(WindowInsets insets) { // Handle padding of the views DisplayCutout cutout = insets.getDisplayCutout(); - Pair<Integer, Integer> cornerCutoutPadding = StatusBarWindowView.cornerCutoutMargins( - cutout, getDisplay()); - Pair<Integer, Integer> padding = - StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner( - cutout, cornerCutoutPadding, -1); - mDatePrivacyView.setPadding(padding.first, 0, padding.second, 0); - mStatusIconsView.setPadding(padding.first, 0, padding.second, 0); + + Pair<Integer, Integer> sbInsets = mInsetsProvider + .getStatusBarContentInsetsForCurrentRotation(); + boolean hasCornerCutout = mInsetsProvider.currentRotationHasCornerCutout(); + + mDatePrivacyView.setPadding(sbInsets.first, 0, sbInsets.second, 0); + mStatusIconsView.setPadding(sbInsets.first, 0, sbInsets.second, 0); LinearLayout.LayoutParams datePrivacySeparatorLayoutParams = (LinearLayout.LayoutParams) mDatePrivacySeparator.getLayoutParams(); LinearLayout.LayoutParams mClockIconsSeparatorLayoutParams = (LinearLayout.LayoutParams) mClockIconsSeparator.getLayoutParams(); - boolean cornerCutout = cornerCutoutPadding != null - && (cornerCutoutPadding.first == 0 || cornerCutoutPadding.second == 0); if (cutout != null) { Rect topCutout = cutout.getBoundingRectTop(); - if (topCutout.isEmpty() || cornerCutout) { + if (topCutout.isEmpty() || hasCornerCutout) { datePrivacySeparatorLayoutParams.width = 0; mDatePrivacySeparator.setVisibility(View.GONE); mClockIconsSeparatorLayoutParams.width = 0; @@ -469,8 +470,8 @@ public class QuickStatusBarHeader extends FrameLayout { } mDatePrivacySeparator.setLayoutParams(datePrivacySeparatorLayoutParams); mClockIconsSeparator.setLayoutParams(mClockIconsSeparatorLayoutParams); - mCutOutPaddingLeft = padding.first; - mCutOutPaddingRight = padding.second; + mCutOutPaddingLeft = sbInsets.first; + mCutOutPaddingRight = sbInsets.second; mWaterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top; updateBatteryMode(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index 1b3450436c6f..3a80764d4c25 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -39,6 +39,7 @@ import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.privacy.logging.PrivacyLogger; import com.android.systemui.qs.carrier.QSCarrierGroupController; import com.android.systemui.qs.dagger.QSScope; +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusIconContainer; import com.android.systemui.statusbar.policy.Clock; @@ -73,6 +74,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader private final QSExpansionPathInterpolator mQSExpansionPathInterpolator; private final BatteryMeterViewController mBatteryMeterViewController; private final FeatureFlags mFeatureFlags; + private final StatusBarContentInsetsProvider mInsetsProvider; private final VariableDateViewController mVariableDateViewControllerDateView; private final VariableDateViewController mVariableDateViewControllerClockDateView; @@ -142,7 +144,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader QSExpansionPathInterpolator qsExpansionPathInterpolator, BatteryMeterViewController batteryMeterViewController, FeatureFlags featureFlags, - VariableDateViewController.Factory variableDateViewControllerFactory) { + VariableDateViewController.Factory variableDateViewControllerFactory, + StatusBarContentInsetsProvider statusBarContentInsetsProvider) { super(view); mPrivacyItemController = privacyItemController; mActivityStarter = activityStarter; @@ -155,6 +158,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader mQSExpansionPathInterpolator = qsExpansionPathInterpolator; mBatteryMeterViewController = batteryMeterViewController; mFeatureFlags = featureFlags; + mInsetsProvider = statusBarContentInsetsProvider; mQSCarrierGroupController = qsCarrierGroupControllerBuilder .setQSCarrierGroup(mView.findViewById(R.id.carrier_group)) @@ -226,7 +230,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader } mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots, - mFeatureFlags.useCombinedQSHeaders()); + mFeatureFlags.useCombinedQSHeaders(), mInsetsProvider); mDemoModeController.addCallback(mDemoModeReceiver); diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 58c05089b062..7f08e5bdb575 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -13,6 +13,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanelControllerBase.TileRecord; +import com.android.systemui.qs.tileimpl.HeightOverrideable; import java.util.ArrayList; @@ -285,5 +286,11 @@ public class TileLayout extends ViewGroup implements QSTileLayout { } mSquishinessFraction = squishinessFraction; layoutTileRecords(mRecords.size(), false /* forLayout */); + + for (TileRecord record : mRecords) { + if (record.tileView instanceof HeightOverrideable) { + ((HeightOverrideable) record.tileView).setSquishinessFraction(mSquishinessFraction); + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java index fec61d911577..141c246db260 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java @@ -19,6 +19,7 @@ package com.android.systemui.qs.carrier; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import android.annotation.MainThread; +import android.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.os.Handler; @@ -42,8 +43,10 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.connectivity.IconState; +import com.android.systemui.statusbar.connectivity.MobileDataIndicators; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators; +import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.util.CarrierConfigTracker; import java.util.function.Consumer; @@ -81,10 +84,9 @@ public class QSCarrierGroupController { private final SlotIndexResolver mSlotIndexResolver; - private final NetworkController.SignalCallback mSignalCallback = - new NetworkController.SignalCallback() { + private final SignalCallback mSignalCallback = new SignalCallback() { @Override - public void setMobileDataIndicators(MobileDataIndicators indicators) { + public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) { if (mProviderModel) { return; } @@ -109,7 +111,7 @@ public class QSCarrierGroupController { } @Override - public void setCallIndicator(NetworkController.IconState statusIcon, int subId) { + public void setCallIndicator(@NonNull IconState statusIcon, int subId) { if (!mProviderModel) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 9de6ceb2de4b..0427e38aa811 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles; import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; +import android.annotation.NonNull; import android.app.Dialog; import android.content.Context; import android.content.Intent; @@ -52,7 +53,8 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators; +import com.android.systemui.statusbar.connectivity.SignalCallback; +import com.android.systemui.statusbar.connectivity.WifiIndicators; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; @@ -273,10 +275,9 @@ public class CastTile extends QSTileImpl<BooleanState> { return mWifiConnected || mHotspotConnected; } - private final NetworkController.SignalCallback mSignalCallback = - new NetworkController.SignalCallback() { + private final SignalCallback mSignalCallback = new SignalCallback() { @Override - public void setWifiIndicators(WifiIndicators indicators) { + public void setWifiIndicators(@NonNull WifiIndicators indicators) { // statusIcon.visible has the connected status information boolean enabledAndConnected = indicators.enabled && (indicators.qsIcon == null ? false : indicators.qsIcon.visible); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index 35dadd45eb3e..e5601f29af0b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles; import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA; +import android.annotation.NonNull; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.content.Context; @@ -56,10 +57,10 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.SignalTileView; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.connectivity.IconState; +import com.android.systemui.statusbar.connectivity.MobileDataIndicators; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.IconState; -import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; +import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.phone.SystemUIDialog; import javax.inject.Inject; @@ -269,7 +270,7 @@ public class CellularTile extends QSTileImpl<SignalState> { private final CallbackInfo mInfo = new CallbackInfo(); @Override - public void setMobileDataIndicators(MobileDataIndicators indicators) { + public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) { if (indicators.qsIcon == null) { // Not data sim, don't display. return; @@ -291,7 +292,7 @@ public class CellularTile extends QSTileImpl<SignalState> { } @Override - public void setIsAirplaneMode(IconState icon) { + public void setIsAirplaneMode(@NonNull IconState icon) { mInfo.airplaneModeEnabled = icon.visible; refreshState(mInfo); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index 5a11ff83ff9e..7ba9cc22bec9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -14,7 +14,6 @@ package com.android.systemui.qs.tiles; -import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Handler; import android.os.Looper; @@ -29,6 +28,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Prefs; import com.android.systemui.R; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -47,6 +47,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements DataSaverController.Listener{ private final DataSaverController mDataSaverController; + private final DialogLaunchAnimator mDialogLaunchAnimator; @Inject public DataSaverTile( @@ -58,11 +59,13 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, - DataSaverController dataSaverController + DataSaverController dataSaverController, + DialogLaunchAnimator dialogLaunchAnimator ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mDataSaverController = dataSaverController; + mDialogLaunchAnimator = dialogLaunchAnimator; mDataSaverController.observe(getLifecycle(), this); } @@ -83,18 +86,27 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements toggleDataSaver(); return; } - // Shows dialog first - SystemUIDialog dialog = new SystemUIDialog(mContext); - dialog.setTitle(com.android.internal.R.string.data_saver_enable_title); - dialog.setMessage(com.android.internal.R.string.data_saver_description); - dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button, - (OnClickListener) (dialogInterface, which) -> { - toggleDataSaver(); - Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true); - }); - dialog.setNegativeButton(com.android.internal.R.string.cancel, null); - dialog.setShowForAllUsers(true); - dialog.show(); + + // Show a dialog to confirm first. Dialogs shown by the DialogLaunchAnimator must be created + // and shown on the main thread, so we post it to the UI handler. + mUiHandler.post(() -> { + SystemUIDialog dialog = new SystemUIDialog(mContext); + dialog.setTitle(com.android.internal.R.string.data_saver_enable_title); + dialog.setMessage(com.android.internal.R.string.data_saver_description); + dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button, + (dialogInterface, which) -> { + toggleDataSaver(); + Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true); + }); + dialog.setNegativeButton(com.android.internal.R.string.cancel, null); + dialog.setShowForAllUsers(true); + + if (view != null) { + mDialogLaunchAnimator.showFromView(dialog, view); + } else { + dialog.show(); + } + }); } private void toggleDataSaver() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 23b2a7642e36..cd81b4a11703 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; +import android.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -52,13 +53,13 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; +import com.android.systemui.statusbar.connectivity.AccessPointController; +import com.android.systemui.statusbar.connectivity.IconState; +import com.android.systemui.statusbar.connectivity.MobileDataIndicators; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController; -import com.android.systemui.statusbar.connectivity.NetworkController.IconState; -import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; -import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators; +import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.connectivity.WifiIcons; +import com.android.systemui.statusbar.connectivity.WifiIndicators; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -250,7 +251,7 @@ public class InternetTile extends QSTileImpl<SignalState> { @Override - public void setWifiIndicators(WifiIndicators indicators) { + public void setWifiIndicators(@NonNull WifiIndicators indicators) { if (DEBUG) { Log.d(TAG, "setWifiIndicators: " + indicators); } @@ -271,7 +272,7 @@ public class InternetTile extends QSTileImpl<SignalState> { } @Override - public void setMobileDataIndicators(MobileDataIndicators indicators) { + public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) { if (DEBUG) { Log.d(TAG, "setMobileDataIndicators: " + indicators); } @@ -293,7 +294,7 @@ public class InternetTile extends QSTileImpl<SignalState> { } @Override - public void setEthernetIndicators(IconState icon) { + public void setEthernetIndicators(@NonNull IconState icon) { if (DEBUG) { Log.d(TAG, "setEthernetIndicators: " + "icon = " + (icon == null ? "" : icon.toString())); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 24b9208d4ed1..8ff75cb3662d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -29,6 +29,7 @@ import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -39,7 +40,9 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.screenrecord.ScreenRecordDialog; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; +import com.android.systemui.statusbar.policy.KeyguardStateController; import javax.inject.Inject; @@ -49,10 +52,13 @@ import javax.inject.Inject; public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> implements RecordingController.RecordingStateChangeCallback { private static final String TAG = "ScreenRecordTile"; - private RecordingController mController; - private KeyguardDismissUtil mKeyguardDismissUtil; + private final RecordingController mController; + private final KeyguardDismissUtil mKeyguardDismissUtil; + private final KeyguardStateController mKeyguardStateController; + private final Callback mCallback = new Callback(); + private final DialogLaunchAnimator mDialogLaunchAnimator; + private long mMillisUntilFinished = 0; - private Callback mCallback = new Callback(); @Inject public ScreenRecordTile( @@ -65,13 +71,17 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> ActivityStarter activityStarter, QSLogger qsLogger, RecordingController controller, - KeyguardDismissUtil keyguardDismissUtil + KeyguardDismissUtil keyguardDismissUtil, + KeyguardStateController keyguardStateController, + DialogLaunchAnimator dialogLaunchAnimator ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = controller; mController.observe(this, mCallback); mKeyguardDismissUtil = keyguardDismissUtil; + mKeyguardStateController = keyguardStateController; + mDialogLaunchAnimator = dialogLaunchAnimator; } @Override @@ -89,7 +99,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> } else if (mController.isRecording()) { stopRecording(); } else { - mUiHandler.post(() -> showPrompt()); + mUiHandler.post(() -> showPrompt(view)); } refreshState(); } @@ -136,15 +146,33 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> return mContext.getString(R.string.quick_settings_screen_record_label); } - private void showPrompt() { - // Close QS, otherwise the dialog appears beneath it - getHost().collapsePanels(); - Intent intent = mController.getPromptIntent(); + private void showPrompt(@Nullable View view) { + // We animate from the touched view only if we are not on the keyguard, given that if we + // are we will dismiss it which will also collapse the shade. + boolean shouldAnimateFromView = view != null && !mKeyguardStateController.isShowing(); + + // Create the recording dialog that will collapse the shade only if we start the recording. + Runnable onStartRecordingClicked = () -> { + // We dismiss the shade. Since starting the recording will also dismiss the dialog, we + // disable the exit animation which looks weird when it happens at the same time as the + // shade collapsing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); + getHost().collapsePanels(); + }; + ScreenRecordDialog dialog = mController.createScreenRecordDialog(mContext, + onStartRecordingClicked); + ActivityStarter.OnDismissAction dismissAction = () -> { - mHost.getUserContext().startActivity(intent); + if (shouldAnimateFromView) { + mDialogLaunchAnimator.showFromView(dialog, view); + } else { + dialog.show(); + } return false; }; - mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false, false); + + mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false /* requiresShadeOpen */, + true /* afterKeyguardDone */); } private void cancelCountdown() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index e6e7e21263bb..e79ca0c93212 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; +import android.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -52,11 +53,11 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSIconViewImpl; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; -import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators; +import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.connectivity.WifiIcons; +import com.android.systemui.statusbar.connectivity.WifiIndicators; import com.android.wifitrackerlib.WifiEntry; import java.util.List; @@ -310,7 +311,7 @@ public class WifiTile extends QSTileImpl<SignalState> { final CallbackInfo mInfo = new CallbackInfo(); @Override - public void setWifiIndicators(WifiIndicators indicators) { + public void setWifiIndicators(@NonNull WifiIndicators indicators) { if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + indicators.enabled); if (indicators.qsIcon == null) { return; @@ -332,7 +333,7 @@ public class WifiTile extends QSTileImpl<SignalState> { } protected class WifiDetailAdapter implements DetailAdapter, - NetworkController.AccessPointController.AccessPointCallback, QSDetailItems.Callback { + AccessPointController.AccessPointCallback, QSDetailItems.Callback { private QSDetailItems mItems; private WifiEntry[] mAccessPoints; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index f6dbb0b95ecd..563c4cd628d2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -128,7 +128,9 @@ public class InternetDialog extends SystemUIDialog implements private boolean mCanConfigMobileData; // Wi-Fi entries + @VisibleForTesting protected WifiEntry mConnectedWifiEntry; + @VisibleForTesting protected int mWifiEntriesCount; // Wi-Fi scanning progress bar @@ -334,6 +336,9 @@ public class InternetDialog extends SystemUIDialog implements mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton()); mWiFiToggle.setOnCheckedChangeListener( (buttonView, isChecked) -> { + if (isChecked) { + mWifiScanNotifyLayout.setVisibility(View.GONE); + } buttonView.setChecked(isChecked); mWifiManager.setWifiEnabled(isChecked); }); @@ -576,12 +581,12 @@ public class InternetDialog extends SystemUIDialog implements @WorkerThread public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries, @Nullable WifiEntry connectedEntry) { - mConnectedWifiEntry = connectedEntry; - mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size(); - mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount); mHandler.post(() -> { - mAdapter.notifyDataSetChanged(); + mConnectedWifiEntry = connectedEntry; + mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size(); updateDialog(false /* shouldUpdateMobileNetwork */); + mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount); + mAdapter.notifyDataSetChanged(); }); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 5673136e1828..1c8bd784a00d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -76,8 +76,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController; +import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.toast.SystemUIToast; @@ -100,7 +99,7 @@ import java.util.stream.Stream; import javax.inject.Inject; public class InternetDialogController implements WifiEntry.DisconnectCallback, - NetworkController.AccessPointController.AccessPointCallback { + AccessPointController.AccessPointCallback { private static final String TAG = "InternetDialogController"; private static final String ACTION_NETWORK_PROVIDER_SETTINGS = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt index 93828b3bcc99..79f7ac3aad3d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt @@ -63,7 +63,8 @@ class InternetDialogFactory @Inject constructor( canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler, executor) if (view != null) { - dialogLaunchAnimator.showFromView(internetDialog!!, view) + dialogLaunchAnimator.showFromView(internetDialog!!, view, + animateBackgroundBoundsChange = true) } else { internetDialog?.show() } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index 060d7b1a8ab8..1a08878cecfe 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -18,7 +18,6 @@ package com.android.systemui.screenrecord; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -27,10 +26,12 @@ import android.os.UserHandle; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.policy.CallbackController; import java.util.concurrent.CopyOnWriteArrayList; @@ -44,15 +45,13 @@ import javax.inject.Inject; public class RecordingController implements CallbackController<RecordingController.RecordingStateChangeCallback> { private static final String TAG = "RecordingController"; - private static final String SYSUI_PACKAGE = "com.android.systemui"; - private static final String SYSUI_SCREENRECORD_LAUNCHER = - "com.android.systemui.screenrecord.ScreenRecordDialog"; private boolean mIsStarting; private boolean mIsRecording; private PendingIntent mStopIntent; private CountDownTimer mCountDownTimer = null; private BroadcastDispatcher mBroadcastDispatcher; + private UserContextProvider mUserContextProvider; protected static final String INTENT_UPDATE_STATE = "com.android.systemui.screenrecord.UPDATE_STATE"; @@ -88,20 +87,16 @@ public class RecordingController * Create a new RecordingController */ @Inject - public RecordingController(BroadcastDispatcher broadcastDispatcher) { + public RecordingController(BroadcastDispatcher broadcastDispatcher, + UserContextProvider userContextProvider) { mBroadcastDispatcher = broadcastDispatcher; + mUserContextProvider = userContextProvider; } - /** - * Get an intent to show screen recording options to the user. - */ - public Intent getPromptIntent() { - final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE, - SYSUI_SCREENRECORD_LAUNCHER); - final Intent intent = new Intent(); - intent.setComponent(launcherComponent); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return intent; + /** Create a dialog to show screen recording options to the user. */ + public ScreenRecordDialog createScreenRecordDialog(Context context, + @Nullable Runnable onStartRecordingClicked) { + return new ScreenRecordDialog(context, this, mUserContextProvider, onStartRecordingClicked); } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index df766f3625e4..1fb88dfe9b52 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -26,7 +26,6 @@ import android.app.PendingIntent; import android.content.Context; import android.os.Bundle; import android.view.Gravity; -import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.ArrayAdapter; @@ -34,34 +33,38 @@ import android.widget.Spinner; import android.widget.Switch; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.systemui.R; import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.statusbar.phone.SystemUIDialog; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import javax.inject.Inject; - /** - * Activity to select screen recording options + * Dialog to select screen recording options */ -public class ScreenRecordDialog extends Activity { +public class ScreenRecordDialog extends SystemUIDialog { + private static final List<ScreenRecordingAudioSource> MODES = Arrays.asList(INTERNAL, MIC, + MIC_AND_INTERNAL); private static final long DELAY_MS = 3000; private static final long INTERVAL_MS = 1000; - private static final String TAG = "ScreenRecordDialog"; private final RecordingController mController; private final UserContextProvider mUserContextProvider; + @Nullable + private final Runnable mOnStartRecordingClicked; private Switch mTapsSwitch; private Switch mAudioSwitch; private Spinner mOptions; - private List<ScreenRecordingAudioSource> mModes; - @Inject - public ScreenRecordDialog(RecordingController controller, - UserContextProvider userContextProvider) { + public ScreenRecordDialog(Context context, RecordingController controller, + UserContextProvider userContextProvider, @Nullable Runnable onStartRecordingClicked) { + super(context); mController = controller; mUserContextProvider = userContextProvider; + mOnStartRecordingClicked = onStartRecordingClicked; } @Override @@ -69,37 +72,35 @@ public class ScreenRecordDialog extends Activity { super.onCreate(savedInstanceState); Window window = getWindow(); - // Inflate the decor view, so the attributes below are not overwritten by the theme. - window.getDecorView(); - window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS); - window.setGravity(Gravity.TOP); + + window.setGravity(Gravity.CENTER); setTitle(R.string.screenrecord_name); setContentView(R.layout.screen_record_dialog); TextView cancelBtn = findViewById(R.id.button_cancel); - cancelBtn.setOnClickListener(v -> { - finish(); - }); + cancelBtn.setOnClickListener(v -> dismiss()); TextView startBtn = findViewById(R.id.button_start); startBtn.setOnClickListener(v -> { + if (mOnStartRecordingClicked != null) { + // Note that it is important to run this callback before dismissing, so that the + // callback can disable the dialog exit animation if it wants to. + mOnStartRecordingClicked.run(); + } + requestScreenCapture(); - finish(); + dismiss(); }); - mModes = new ArrayList<>(); - mModes.add(INTERNAL); - mModes.add(MIC); - mModes.add(MIC_AND_INTERNAL); - mAudioSwitch = findViewById(R.id.screenrecord_audio_switch); mTapsSwitch = findViewById(R.id.screenrecord_taps_switch); mOptions = findViewById(R.id.screen_recording_options); - ArrayAdapter a = new ScreenRecordingAdapter(getApplicationContext(), + ArrayAdapter a = new ScreenRecordingAdapter(getContext().getApplicationContext(), android.R.layout.simple_spinner_dropdown_item, - mModes); + MODES); a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mOptions.setAdapter(a); mOptions.setOnItemClickListenerInt((parent, view, position, id) -> { @@ -116,7 +117,7 @@ public class ScreenRecordDialog extends Activity { PendingIntent startIntent = PendingIntent.getForegroundService(userContext, RecordingService.REQUEST_CODE, RecordingService.getStartIntent( - userContext, RESULT_OK, + userContext, Activity.RESULT_OK, audioMode.ordinal(), showTaps), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); PendingIntent stopIntent = PendingIntent.getService(userContext, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 5b4db1449b34..44b45401ad77 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -266,6 +266,7 @@ public class ScreenshotController { private Animator mScreenshotAnimation; private RequestCallback mCurrentRequestCallback; + private String mPackageName = ""; private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) { @Override @@ -275,7 +276,8 @@ public class ScreenshotController { if (DEBUG_UI) { Log.d(TAG, "Corner timeout hit"); } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT, 0, + mPackageName); ScreenshotController.this.dismissScreenshot(false); break; default: @@ -354,12 +356,13 @@ public class ScreenshotController { mCameraSound.load(MediaActionSound.SHUTTER_CLICK); } - void takeScreenshotFullscreen(Consumer<Uri> finisher, RequestCallback requestCallback) { + void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher, + RequestCallback requestCallback) { mCurrentRequestCallback = requestCallback; DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); takeScreenshotInternal( - finisher, + topComponent, finisher, new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)); } @@ -383,13 +386,15 @@ public class ScreenshotController { screenshotScreenBounds.set(0, 0, screenshot.getWidth(), screenshot.getHeight()); } mCurrentRequestCallback = requestCallback; - saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, showFlash); + saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent, + showFlash); } /** * Displays a screenshot selector */ - void takeScreenshotPartial(final Consumer<Uri> finisher, RequestCallback requestCallback) { + void takeScreenshotPartial(ComponentName topComponent, + final Consumer<Uri> finisher, RequestCallback requestCallback) { mScreenshotView.reset(); mCurrentRequestCallback = requestCallback; @@ -398,7 +403,7 @@ public class ScreenshotController { mScreenshotView.requestApplyInsets(); mScreenshotView.takePartialScreenshot( - rect -> takeScreenshotInternal(finisher, rect)); + rect -> takeScreenshotInternal(topComponent, finisher, rect)); } /** @@ -491,7 +496,8 @@ public class ScreenshotController { /** * Takes a screenshot of the current display and shows an animation. */ - private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) { + private void takeScreenshotInternal(ComponentName topComponent, Consumer<Uri> finisher, + Rect crop) { mScreenshotTakenInPortrait = mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; @@ -509,7 +515,7 @@ public class ScreenshotController { return; } - saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true); + saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true); } private Bitmap captureScreenshot(Rect crop) { @@ -539,7 +545,7 @@ public class ScreenshotController { } private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, - Insets screenInsets, boolean showFlash) { + Insets screenInsets, ComponentName topComponent, boolean showFlash) { if (mAccessibilityManager.isEnabled()) { AccessibilityEvent event = new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); @@ -552,7 +558,7 @@ public class ScreenshotController { if (mScreenshotView.isAttachedToWindow()) { // if we didn't already dismiss for another reason if (!mScreenshotView.isDismissing()) { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, mPackageName); } if (DEBUG_WINDOW) { Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " @@ -560,6 +566,8 @@ public class ScreenshotController { } mScreenshotView.reset(); } + mPackageName = topComponent == null ? "" : topComponent.getPackageName(); + mScreenshotView.setPackageName(mPackageName); mScreenshotView.updateOrientation( mWindowManager.getCurrentWindowMetrics().getWindowInsets()); @@ -772,6 +780,10 @@ public class ScreenshotController { } mWindowManager.removeViewImmediate(decorView); } + // Ensure that we remove the input monitor + if (mScreenshotView != null) { + mScreenshotView.stopInputListening(); + } } /** @@ -790,11 +802,11 @@ public class ScreenshotController { } finisher.accept(imageData.uri); if (imageData.uri == null) { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_save_text); } else { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName); mScreenshotHandler.post(() -> Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show()); } @@ -959,11 +971,11 @@ public class ScreenshotController { */ private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) { if (imageData.uri == null) { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_save_text); } else { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 7222b0313fb4..ca63ec269bf4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -163,6 +163,7 @@ public class ScreenshotView extends FrameLayout implements private SwipeDismissHandler mSwipeDismissHandler; private InputMonitorCompat mInputMonitor; private boolean mShowScrollablePreview; + private String mPackageName = ""; private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>(); private PendingInteraction mPendingInteraction; @@ -314,7 +315,7 @@ public class ScreenshotView extends FrameLayout implements }); } - private void stopInputListening() { + void stopInputListening() { if (mInputMonitor != null) { mInputMonitor.dispose(); mInputMonitor = null; @@ -409,6 +410,10 @@ public class ScreenshotView extends FrameLayout implements mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets)); } + void setPackageName(String packageName) { + mPackageName = packageName; + } + void updateInsets(WindowInsets insets) { int orientation = mContext.getResources().getConfiguration().orientation; mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT); @@ -585,7 +590,8 @@ public class ScreenshotView extends FrameLayout implements if (DEBUG_INPUT) { Log.d(TAG, "dismiss button clicked"); } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL); + mUiEventLogger.log( + ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, 0, mPackageName); animateDismissal(); }); mDismissButton.setAlpha(1); @@ -621,7 +627,7 @@ public class ScreenshotView extends FrameLayout implements ArrayList<ScreenshotActionChip> chips = new ArrayList<>(); - mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share)); + mShareChip.setContentDescription(mContext.getString(R.string.screenshot_share_description)); mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true); mShareChip.setOnClickListener(v -> { mShareChip.setIsPending(true); @@ -633,7 +639,7 @@ public class ScreenshotView extends FrameLayout implements }); chips.add(mShareChip); - mEditChip.setContentDescription(mContext.getString(R.string.screenshot_edit_label)); + mEditChip.setContentDescription(mContext.getString(R.string.screenshot_edit_description)); mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true); mEditChip.setOnClickListener(v -> { mEditChip.setIsPending(true); @@ -698,24 +704,25 @@ public class ScreenshotView extends FrameLayout implements void setChipIntents(ScreenshotController.SavedImageData imageData) { mShareChip.setOnClickListener(v -> { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName); startSharedTransition( imageData.shareTransition.get()); }); mEditChip.setOnClickListener(v -> { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName); startSharedTransition( imageData.editTransition.get()); }); mScreenshotPreview.setOnClickListener(v -> { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName); startSharedTransition( imageData.editTransition.get()); }); if (mQuickShareChip != null) { mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent, () -> { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED); + mUiEventLogger.log( + ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0, mPackageName); animateDismissal(); }); } @@ -745,7 +752,8 @@ public class ScreenshotView extends FrameLayout implements actionChip.setIcon(smartAction.getIcon(), false); actionChip.setPendingIntent(smartAction.actionIntent, () -> { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, + 0, mPackageName); animateDismissal(); }); actionChip.setAlpha(1); @@ -1121,7 +1129,7 @@ public class ScreenshotView extends FrameLayout implements if (DEBUG_INPUT) { Log.d(TAG, "dismiss triggered via swipe gesture"); } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0, mPackageName); animateDismissal(createSwipeDismissAnimation()); } else { // if we've moved, but not past the threshold, start the return animation diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index daa9d099de86..f380911b6403 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -186,20 +186,22 @@ public class TakeScreenshotService extends Service { ScreenshotHelper.ScreenshotRequest screenshotRequest = (ScreenshotHelper.ScreenshotRequest) msg.obj; - mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource())); + ComponentName topComponent = screenshotRequest.getTopComponent(); + mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0, + topComponent == null ? "" : topComponent.getPackageName()); switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: if (DEBUG_SERVICE) { Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN"); } - mScreenshot.takeScreenshotFullscreen(uriConsumer, requestCallback); + mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, requestCallback); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: if (DEBUG_SERVICE) { Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION"); } - mScreenshot.takeScreenshotPartial(uriConsumer, requestCallback); + mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, requestCallback); break; case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: if (DEBUG_SERVICE) { @@ -211,7 +213,6 @@ public class TakeScreenshotService extends Service { Insets insets = screenshotRequest.getInsets(); int taskId = screenshotRequest.getTaskId(); int userId = screenshotRequest.getUserId(); - ComponentName topComponent = screenshotRequest.getTopComponent(); if (screenshot == null) { Log.e(TAG, "Got null bitmap from screenshot message"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 77e329f94a36..03d8e7e03c0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -208,11 +208,6 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, lateinit var isScrimOpaqueChangedListener: Consumer<Boolean> /** - * A runnable to call when the scrim has been fully revealed. This is only invoked once - */ - var fullyRevealedRunnable: Runnable? = null - - /** * How much of the underlying views are revealed, in percent. 0 means they will be completely * obscured and 1 means they'll be fully visible. */ @@ -223,20 +218,10 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, revealEffect.setRevealAmountOnScrim(value, this) updateScrimOpaque() - maybeTriggerFullyRevealedRunnable() invalidate() } } - private fun maybeTriggerFullyRevealedRunnable() { - if (revealAmount == 1.0f) { - fullyRevealedRunnable?.let { - it.run() - fullyRevealedRunnable = null - } - } - } - /** * The [LightRevealEffect] used to manipulate the radial gradient whenever [revealAmount] * changes. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index eb89be1945c6..46004db3067a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -37,6 +37,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener @@ -73,10 +74,6 @@ class NotificationShadeDepthController @Inject constructor( private const val TAG = "DepthController" } - /** - * Did we already unblur while dozing? - */ - private var alreadyUnblurredWhileDozing = false lateinit var root: View private var blurRoot: View? = null private var keyguardAnimator: Animator? = null @@ -233,11 +230,9 @@ class NotificationShadeDepthController @Inject constructor( private val keyguardStateCallback = object : KeyguardStateController.Callback { override fun onKeyguardFadingAwayChanged() { if (!keyguardStateController.isKeyguardFadingAway || - !biometricUnlockController.isWakeAndUnlock) { + biometricUnlockController.mode != MODE_WAKE_AND_UNLOCK) { return } - // When wakeAndUnlocking the screen remains dozing, so we have to manually trigger - // the unblur earlier keyguardAnimator?.cancel() keyguardAnimator = ValueAnimator.ofFloat(1f, 0f).apply { @@ -259,7 +254,6 @@ class NotificationShadeDepthController @Inject constructor( }) start() } - alreadyUnblurredWhileDozing = statusBarStateController.dozeAmount != 0.0f } override fun onKeyguardShowingChanged() { @@ -281,24 +275,10 @@ class NotificationShadeDepthController @Inject constructor( if (isDozing) { shadeAnimation.finishIfRunning() brightnessMirrorSpring.finishIfRunning() - - // unset this for safety, to be ready for the next wakeup - alreadyUnblurredWhileDozing = false } } override fun onDozeAmountChanged(linear: Float, eased: Float) { - if (alreadyUnblurredWhileDozing) { - if (linear == 0.0f) { - // We finished waking up, let's reset - alreadyUnblurredWhileDozing = false - } else { - // We've already handled the unbluring from the keyguardAnimator above. - // if we would continue, we'd play another unzoom / blur animation from the - // dozing changing. - return - } - } wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased) scheduleUpdate() } @@ -458,7 +438,6 @@ class NotificationShadeDepthController @Inject constructor( it.println("blursDisabledForAppLaunch: $blursDisabledForAppLaunch") it.println("qsPanelExpansion: $qsPanelExpansion") it.println("transitionToFullShadeProgress: $transitionToFullShadeProgress") - it.println("alreadyUnblurredWhileDozing: $alreadyUnblurredWhileDozing") it.println("lastAppliedBlur: $lastAppliedBlur") } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java index bcba5cc09c5d..8a4c4b5ac5c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; +import android.annotation.NonNull; import android.os.Bundle; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; @@ -26,7 +27,9 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.demomode.DemoModeCommandReceiver; import com.android.systemui.plugins.DarkIconDispatcher; +import com.android.systemui.statusbar.connectivity.IconState; import com.android.systemui.statusbar.connectivity.NetworkController; +import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.ViewController; @@ -135,10 +138,9 @@ public class OperatorNameViewController extends ViewController<OperatorNameView> (area, darkIntensity, tint) -> mView.setTextColor(DarkIconDispatcher.getTint(area, mView, tint)); - private final NetworkController.SignalCallback mSignalCallback = - new NetworkController.SignalCallback() { + private final SignalCallback mSignalCallback = new SignalCallback() { @Override - public void setIsAirplaneMode(NetworkController.IconState icon) { + public void setIsAirplaneMode(@NonNull IconState icon) { update(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt new file mode 100644 index 000000000000..490994d805fe --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.connectivity + +import android.content.Intent +import android.os.UserManager +import android.provider.Settings + +import com.android.wifitrackerlib.MergedCarrierEntry +import com.android.wifitrackerlib.WifiEntry + +/** + * Tracks changes in access points. Allows listening for changes, scanning for new APs, + * and connecting to new ones. + */ +interface AccessPointController { + fun addAccessPointCallback(callback: AccessPointCallback) + fun removeAccessPointCallback(callback: AccessPointCallback) + + /** + * Request an updated list of available access points + * + * This method will trigger a call to [AccessPointCallback.onAccessPointsChanged] + */ + fun scanForAccessPoints() + + /** + * Gets the current [MergedCarrierEntry]. If null, this call generates a call to + * [AccessPointCallback.onAccessPointsChanged] + * + * @return the current [MergedCarrierEntry], if one exists + */ + fun getMergedCarrierEntry(): MergedCarrierEntry? + + /** @return the appropriate icon id for the given [WifiEntry]'s level */ + fun getIcon(ap: WifiEntry): Int + + /** + * Connects to a [WifiEntry] if it's saved or does not require security. + * + * If the entry is not saved and requires security, will trigger + * [AccessPointCallback.onSettingsActivityTriggered]. + * + * @param ap + * @return `true` if [AccessPointCallback.onSettingsActivityTriggered] is triggered + */ + fun connect(ap: WifiEntry?): Boolean + + /** + * `true` if the current user does not have the [UserManager.DISALLOW_CONFIG_WIFI] restriction + */ + fun canConfigWifi(): Boolean + + /** + * `true` if the current user does not have the [UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS] + * restriction set + */ + fun canConfigMobileData(): Boolean + + interface AccessPointCallback { + /** + * Called whenever [scanForAccessPoints] is called, or [getMergedCarrierEntry] is called + * with a null entry + * + * @param accessPoints the list of available access points, including the current connected + * one if it exists + */ + fun onAccessPointsChanged(accessPoints: List<@JvmSuppressWildcards WifiEntry>) + + /** + * Called whenever [connecting][connect] to an unknown access point which has security. + * Implementers should launch the intent in the appropriate context + * + * @param settingsIntent an intent for [Settings.ACTION_WIFI_SETTINGS] with + * "wifi_start_connect_ssid" set as an extra + */ + fun onSettingsActivityTriggered(settingsIntent: Intent?) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java index a23d73d7ed84..893b836dedfc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java @@ -56,9 +56,9 @@ import java.util.concurrent.Executor; import javax.inject.Inject; /** */ -public class AccessPointControllerImpl - implements NetworkController.AccessPointController, - WifiPickerTracker.WifiPickerTrackerCallback, LifecycleOwner { +public class AccessPointControllerImpl implements AccessPointController, + WifiPickerTracker.WifiPickerTrackerCallback, + LifecycleOwner { private static final String TAG = "AccessPointController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -117,13 +117,11 @@ public class AccessPointControllerImpl super.finalize(); } - /** */ public boolean canConfigWifi() { return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, new UserHandle(mCurrentUser)); } - /** */ public boolean canConfigMobileData() { return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, UserHandle.of(mCurrentUser)) && mUserTracker.getUserInfo().isAdmin(); @@ -156,7 +154,7 @@ public class AccessPointControllerImpl @Override public void scanForAccessPoints() { if (mWifiPickerTracker == null) { - fireAcccessPointsCallback(Collections.emptyList()); + fireAccessPointsCallback(Collections.emptyList()); return; } List<WifiEntry> entries = mWifiPickerTracker.getWifiEntries(); @@ -164,13 +162,13 @@ public class AccessPointControllerImpl if (connectedEntry != null) { entries.add(0, connectedEntry); } - fireAcccessPointsCallback(entries); + fireAccessPointsCallback(entries); } @Override public MergedCarrierEntry getMergedCarrierEntry() { if (mWifiPickerTracker == null) { - fireAcccessPointsCallback(Collections.emptyList()); + fireAccessPointsCallback(Collections.emptyList()); return null; } return mWifiPickerTracker.getMergedCarrierEntry(); @@ -190,7 +188,7 @@ public class AccessPointControllerImpl * @param ap * @return {@code true} if {@link AccessPointCallback#onSettingsActivityTriggered} is triggered */ - public boolean connect(WifiEntry ap) { + public boolean connect(@Nullable WifiEntry ap) { if (ap == null) return false; if (DEBUG) { if (ap.getWifiConfiguration() != null) { @@ -222,7 +220,7 @@ public class AccessPointControllerImpl } } - private void fireAcccessPointsCallback(List<WifiEntry> aps) { + private void fireAccessPointsCallback(List<WifiEntry> aps) { for (AccessPointCallback callback : mCallbacks) { callback.onAccessPointsChanged(aps); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java index 052a789200e6..6914ae67f4ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java @@ -23,10 +23,6 @@ import android.telephony.SubscriptionInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener; -import com.android.systemui.statusbar.connectivity.NetworkController.IconState; -import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; -import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators; import java.io.PrintWriter; import java.text.SimpleDateFormat; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt new file mode 100644 index 000000000000..9c3c10c9219b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.connectivity + +import android.annotation.SuppressLint +import com.android.settingslib.SignalIcon.IconGroup +import java.text.SimpleDateFormat + +/** + * Base type for various connectivity states, for use with [SignalController] and its subtypes + */ +open class ConnectivityState { + @JvmField var connected = false + @JvmField var enabled = false + @JvmField var activityIn = false + @JvmField var activityOut = false + @JvmField var level = 0 + @JvmField var iconGroup: IconGroup? = null + @JvmField var inetCondition = 0 + // Only for logging. + @JvmField var rssi = 0 + // Not used for comparison, just used for logging. + @JvmField var time: Long = 0 + + override fun toString(): String { + return if (time != 0L) { + val builder = StringBuilder() + toString(builder) + builder.toString() + } else { + "Empty " + javaClass.simpleName + } + } + + protected open fun copyFrom(other: ConnectivityState) { + connected = other.connected + enabled = other.enabled + activityIn = other.activityIn + activityOut = other.activityOut + level = other.level + iconGroup = other.iconGroup + inetCondition = other.inetCondition + rssi = other.rssi + time = other.time + } + + protected open fun toString(builder: StringBuilder) { + builder.append("connected=$connected,") + .append("enabled=$enabled,") + .append("level=$level,") + .append("inetCondition=$inetCondition,") + .append("iconGroup=$iconGroup,") + .append("activityIn=$activityIn,") + .append("activityOut=$activityOut,") + .append("rssi=$rssi,") + .append("lastModified=${sSDF.format(time)}") + } + + override fun equals(other: Any?): Boolean { + if (other == null) return false + if (other.javaClass != javaClass) return false + + val o = other as ConnectivityState + return o.connected == connected && + o.enabled == enabled && + o.level == level && + o.inetCondition == inetCondition && + o.iconGroup === iconGroup && + o.activityIn == activityIn && + o.activityOut == activityOut && + o.rssi == rssi + } + + override fun hashCode(): Int { + var result = connected.hashCode() + result = 31 * result + enabled.hashCode() + result = 31 * result + activityIn.hashCode() + result = 31 * result + activityOut.hashCode() + result = 31 * result + level + result = 31 * result + (iconGroup?.hashCode() ?: 0) + result = 31 * result + inetCondition + result = 31 * result + rssi + result = 31 * result + time.hashCode() + return result + } +} + +// No locale as it's only used for logging purposes +@SuppressLint("SimpleDateFormat") +private val sSDF = SimpleDateFormat("MM-dd HH:mm:ss.SSS") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetSignalController.java index c9d40adde644..acd97795c128 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetSignalController.java @@ -20,15 +20,12 @@ import android.net.NetworkCapabilities; import com.android.settingslib.AccessibilityContentDescriptions; import com.android.settingslib.SignalIcon.IconGroup; -import com.android.settingslib.SignalIcon.State; -import com.android.systemui.statusbar.connectivity.NetworkController.IconState; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; import java.util.BitSet; /** */ public class EthernetSignalController extends - SignalController<State, IconGroup> { + SignalController<ConnectivityState, IconGroup> { public EthernetSignalController(Context context, CallbackHandler callbackHandler, NetworkControllerImpl networkController) { @@ -68,7 +65,7 @@ public class EthernetSignalController extends } @Override - public State cleanState() { - return new State(); + public ConnectivityState cleanState() { + return new ConnectivityState(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java index 20ef4eec0b97..9ae7ea2bdded 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java @@ -33,7 +33,6 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; -import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; import android.telephony.ims.ImsException; import android.telephony.ims.ImsMmTelManager; @@ -47,8 +46,6 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.AccessibilityContentDescriptions; import com.android.settingslib.SignalIcon.MobileIconGroup; -import com.android.settingslib.SignalIcon.MobileState; -import com.android.settingslib.Utils; import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.mobile.MobileMappings.Config; import com.android.settingslib.mobile.MobileStatusTracker; @@ -58,9 +55,6 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.SignalStrengthUtil; import com.android.systemui.R; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.statusbar.connectivity.NetworkController.IconState; -import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; import com.android.systemui.util.CarrierConfigTracker; import java.io.PrintWriter; @@ -93,15 +87,6 @@ public class MobileSignalController extends SignalController<MobileState, Mobile final SubscriptionInfo mSubscriptionInfo; private Map<String, MobileIconGroup> mNetworkToIconLookup; - // Since some pieces of the phone state are interdependent we store it locally, - // this could potentially become part of MobileState for simplification/complication - // of code. - private int mDataState = TelephonyManager.DATA_DISCONNECTED; - private TelephonyDisplayInfo mTelephonyDisplayInfo = - new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN, - TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE); - private ServiceState mServiceState; - private SignalStrength mSignalStrength; private int mLastLevel; private MobileIconGroup mDefaultIcons; private Config mConfig; @@ -468,16 +453,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile return new MobileState(); } - private boolean isCdma() { - return (mSignalStrength != null) && !mSignalStrength.isGsm(); - } - - public boolean isEmergencyOnly() { - return (mServiceState != null && mServiceState.isEmergencyOnly()); - } - public boolean isInService() { - return Utils.isInService(mServiceState); + return mCurrentState.isInService(); } String getNetworkNameForCarrierWiFi() { @@ -485,15 +462,15 @@ public class MobileSignalController extends SignalController<MobileState, Mobile } private boolean isRoaming() { - // During a carrier change, roaming indications need to be supressed. + // During a carrier change, roaming indications need to be suppressed. if (isCarrierNetworkChangeActive()) { return false; } - if (isCdma()) { + if (mCurrentState.isCdma()) { return mPhone.getCdmaEnhancedRoamingIndicatorDisplayNumber() != TelephonyManager.ERI_OFF; } else { - return mServiceState != null && mServiceState.getRoaming(); + return mCurrentState.isRoaming(); } } @@ -585,27 +562,29 @@ public class MobileSignalController extends SignalController<MobileState, Mobile } private void updateMobileStatus(MobileStatus mobileStatus) { - mCurrentState.activityIn = mobileStatus.activityIn; - mCurrentState.activityOut = mobileStatus.activityOut; - mCurrentState.dataSim = mobileStatus.dataSim; - mCurrentState.carrierNetworkChangeMode = mobileStatus.carrierNetworkChangeMode; - mDataState = mobileStatus.dataState; + int lastVoiceState = mCurrentState.getVoiceServiceState(); + mCurrentState.setFromMobileStatus(mobileStatus); + notifyMobileLevelChangeIfNecessary(mobileStatus.signalStrength); - mSignalStrength = mobileStatus.signalStrength; - mTelephonyDisplayInfo = mobileStatus.telephonyDisplayInfo; - int lastVoiceState = mServiceState != null ? mServiceState.getState() : -1; - mServiceState = mobileStatus.serviceState; - int currentVoiceState = mServiceState != null ? mServiceState.getState() : -1; + if (mProviderModelBehavior) { + maybeNotifyCallStateChanged(lastVoiceState); + } + } + + /** Call state changed is only applicable when provider model behavior is true */ + private void maybeNotifyCallStateChanged(int lastVoiceState) { + int currentVoiceState = mCurrentState.getVoiceServiceState(); + if (lastVoiceState == currentVoiceState) { + return; + } // Only update the no calling Status in the below scenarios // 1. The first valid voice state has been received // 2. The voice state has been changed and either the last or current state is // ServiceState.STATE_IN_SERVICE - if (mProviderModelBehavior - && lastVoiceState != currentVoiceState - && (lastVoiceState == -1 - || (lastVoiceState == ServiceState.STATE_IN_SERVICE - || currentVoiceState == ServiceState.STATE_IN_SERVICE))) { - boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE; + if (lastVoiceState == -1 + || (lastVoiceState == ServiceState.STATE_IN_SERVICE + || currentVoiceState == ServiceState.STATE_IN_SERVICE)) { + boolean isNoCalling = mCurrentState.isNoCalling(); isNoCalling &= !hideNoCalling(); IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms, @@ -615,7 +594,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile } void updateNoCallingState() { - int currentVoiceState = mServiceState != null ? mServiceState.getState() : -1; + int currentVoiceState = mCurrentState.getVoiceServiceState(); boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE; isNoCalling &= !hideNoCalling(); IconState statusIcon = new IconState(isNoCalling, @@ -643,8 +622,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile } void refreshCallIndicator(SignalCallback callback) { - boolean isNoCalling = mServiceState != null - && mServiceState.getState() != ServiceState.STATE_IN_SERVICE; + boolean isNoCalling = mCurrentState.isNoCalling(); isNoCalling &= !hideNoCalling(); IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms, @@ -736,30 +714,30 @@ public class MobileSignalController extends SignalController<MobileState, Mobile } /** - * Updates the current state based on mServiceState, mSignalStrength, mDataState, - * mTelephonyDisplayInfo, and mSimState. It should be called any time one of these is updated. + * Updates the current state based on ServiceState, SignalStrength, DataState, + * TelephonyDisplayInfo, and sim state. It should be called any time one of these is updated. * This will call listeners if necessary. */ private void updateTelephony() { if (Log.isLoggable(mTag, Log.DEBUG)) { Log.d(mTag, "updateTelephonySignalStrength: hasService=" - + Utils.isInService(mServiceState) + " ss=" + mSignalStrength - + " displayInfo=" + mTelephonyDisplayInfo); + + mCurrentState.isInService() + + " ss=" + mCurrentState.signalStrength + + " displayInfo=" + mCurrentState.telephonyDisplayInfo); } checkDefaultData(); - mCurrentState.connected = Utils.isInService(mServiceState) && mSignalStrength != null; + mCurrentState.connected = mCurrentState.isInService(); if (mCurrentState.connected) { - mCurrentState.level = getSignalLevel(mSignalStrength); + mCurrentState.level = getSignalLevel(mCurrentState.signalStrength); } - String iconKey = getIconKey(mTelephonyDisplayInfo); + String iconKey = getIconKey(mCurrentState.telephonyDisplayInfo); if (mNetworkToIconLookup.get(iconKey) != null) { mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey); } else { mCurrentState.iconGroup = mDefaultIcons; } - mCurrentState.dataConnected = mCurrentState.connected - && mDataState == TelephonyManager.DATA_CONNECTED; + mCurrentState.dataConnected = mCurrentState.isDataConnected(); mCurrentState.roaming = isRoaming(); if (isCarrierNetworkChangeActive()) { @@ -771,20 +749,20 @@ public class MobileSignalController extends SignalController<MobileState, Mobile mCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED; } } - if (isEmergencyOnly() != mCurrentState.isEmergency) { - mCurrentState.isEmergency = isEmergencyOnly(); + if (mCurrentState.isEmergencyOnly() != mCurrentState.isEmergency) { + mCurrentState.isEmergency = mCurrentState.isEmergencyOnly(); mNetworkController.recalculateEmergency(); } // Fill in the network name if we think we have it. - if (mCurrentState.networkName.equals(mNetworkNameDefault) && mServiceState != null - && !TextUtils.isEmpty(mServiceState.getOperatorAlphaShort())) { - mCurrentState.networkName = mServiceState.getOperatorAlphaShort(); + if (mCurrentState.networkName.equals(mNetworkNameDefault) + && !TextUtils.isEmpty(mCurrentState.getOperatorAlphaShort())) { + mCurrentState.networkName = mCurrentState.getOperatorAlphaShort(); } // If this is the data subscription, update the currentState data name - if (mCurrentState.networkNameData.equals(mNetworkNameDefault) && mServiceState != null + if (mCurrentState.networkNameData.equals(mNetworkNameDefault) && mCurrentState.dataSim - && !TextUtils.isEmpty(mServiceState.getOperatorAlphaShort())) { - mCurrentState.networkNameData = mServiceState.getOperatorAlphaShort(); + && !TextUtils.isEmpty(mCurrentState.getOperatorAlphaShort())) { + mCurrentState.networkNameData = mCurrentState.getOperatorAlphaShort(); } notifyListenersIfNecessary(); @@ -836,10 +814,6 @@ public class MobileSignalController extends SignalController<MobileState, Mobile pw.println(" mSubscription=" + mSubscriptionInfo + ","); pw.println(" mProviderModelSetting=" + mProviderModelSetting + ","); pw.println(" mProviderModelBehavior=" + mProviderModelBehavior + ","); - pw.println(" mServiceState=" + mServiceState + ","); - pw.println(" mSignalStrength=" + mSignalStrength + ","); - pw.println(" mTelephonyDisplayInfo=" + mTelephonyDisplayInfo + ","); - pw.println(" mDataState=" + mDataState + ","); pw.println(" mInflateSignalStrengths=" + mInflateSignalStrengths + ","); pw.println(" isDataDisabled=" + isDataDisabled() + ","); pw.println(" mNetworkToIconLookup=" + mNetworkToIconLookup + ","); @@ -884,5 +858,4 @@ public class MobileSignalController extends SignalController<MobileState, Mobile icon = iconState; } } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt new file mode 100644 index 000000000000..8a3b00662900 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.connectivity + +import android.telephony.ServiceState +import android.telephony.SignalStrength +import android.telephony.TelephonyDisplayInfo +import android.telephony.TelephonyManager +import com.android.settingslib.Utils +import com.android.settingslib.mobile.MobileStatusTracker.MobileStatus +import com.android.settingslib.mobile.TelephonyIcons +import java.lang.IllegalArgumentException + +/** + * Box for all policy-related state used in [MobileSignalController] + */ +internal class MobileState( + @JvmField var networkName: String? = null, + @JvmField var networkNameData: String? = null, + @JvmField var dataSim: Boolean = false, + @JvmField var dataConnected: Boolean = false, + @JvmField var isEmergency: Boolean = false, + @JvmField var airplaneMode: Boolean = false, + @JvmField var carrierNetworkChangeMode: Boolean = false, + @JvmField var isDefault: Boolean = false, + @JvmField var userSetup: Boolean = false, + @JvmField var roaming: Boolean = false, + @JvmField var dataState: Int = TelephonyManager.DATA_DISCONNECTED, + // Tracks the on/off state of the defaultDataSubscription + @JvmField var defaultDataOff: Boolean = false +) : ConnectivityState() { + + @JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN, + TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE) + @JvmField var serviceState: ServiceState? = null + @JvmField var signalStrength: SignalStrength? = null + + /** @return true if this state is disabled or not default data */ + val isDataDisabledOrNotDefault: Boolean + get() = (iconGroup === TelephonyIcons.DATA_DISABLED || + iconGroup === TelephonyIcons.NOT_DEFAULT_DATA) && userSetup + + /** @return if this state is considered to have inbound activity */ + fun hasActivityIn(): Boolean { + return dataConnected && !carrierNetworkChangeMode && activityIn + } + + /** @return if this state is considered to have outbound activity */ + fun hasActivityOut(): Boolean { + return dataConnected && !carrierNetworkChangeMode && activityOut + } + + /** @return true if this state should show a RAT icon in quick settings */ + fun showQuickSettingsRatIcon(): Boolean { + return dataConnected || isDataDisabledOrNotDefault + } + + override fun copyFrom(other: ConnectivityState) { + val o = other as? MobileState ?: throw IllegalArgumentException( + "MobileState can only update from another MobileState") + + super.copyFrom(o) + networkName = o.networkName + networkNameData = o.networkNameData + dataSim = o.dataSim + dataConnected = o.dataConnected + isEmergency = o.isEmergency + airplaneMode = o.airplaneMode + carrierNetworkChangeMode = o.carrierNetworkChangeMode + isDefault = o.isDefault + userSetup = o.userSetup + roaming = o.roaming + dataState = o.dataState + defaultDataOff = o.defaultDataOff + + telephonyDisplayInfo = o.telephonyDisplayInfo + serviceState = o.serviceState + signalStrength = o.signalStrength + } + + fun isDataConnected(): Boolean { + return connected && dataState == TelephonyManager.DATA_CONNECTED + } + + /** @return the current voice service state, or -1 if null */ + fun getVoiceServiceState(): Int { + return serviceState?.state ?: -1 + } + + fun isNoCalling(): Boolean { + return serviceState?.state != ServiceState.STATE_IN_SERVICE + } + + fun getOperatorAlphaShort(): String { + return serviceState?.operatorAlphaShort ?: "" + } + + fun isCdma(): Boolean { + return signalStrength != null && !signalStrength!!.isGsm + } + + fun isEmergencyOnly(): Boolean { + return serviceState != null && serviceState!!.isEmergencyOnly + } + + fun isInService(): Boolean { + return Utils.isInService(serviceState) + } + + fun isRoaming(): Boolean { + return serviceState != null && serviceState!!.roaming + } + + fun setFromMobileStatus(mobileStatus: MobileStatus) { + activityIn = mobileStatus.activityIn + activityOut = mobileStatus.activityOut + dataSim = mobileStatus.dataSim + carrierNetworkChangeMode = mobileStatus.carrierNetworkChangeMode + dataState = mobileStatus.dataState + signalStrength = mobileStatus.signalStrength + telephonyDisplayInfo = mobileStatus.telephonyDisplayInfo + serviceState = mobileStatus.serviceState + } + + override fun toString(builder: StringBuilder) { + super.toString(builder) + builder.append(',') + builder.append("dataSim=$dataSim,") + builder.append("networkName=$networkName,") + builder.append("networkNameData=$networkNameData,") + builder.append("dataConnected=$dataConnected,") + builder.append("roaming=$roaming,") + builder.append("isDefault=$isDefault,") + builder.append("isEmergency=$isEmergency,") + builder.append("airplaneMode=$airplaneMode,") + builder.append("carrierNetworkChangeMode=$carrierNetworkChangeMode,") + builder.append("userSetup=$userSetup,") + builder.append("dataState=$dataState,") + builder.append("defaultDataOff=$defaultDataOff,") + + // Computed properties + builder.append("showQuickSettingsRatIcon=${showQuickSettingsRatIcon()},") + builder.append("voiceServiceState=${getVoiceServiceState()},") + builder.append("isInService=${isInService()},") + + builder.append("serviceState=${serviceState?.minLog() ?: "(null)"},") + builder.append("signalStrength=${signalStrength?.minLog() ?: "(null)"},") + builder.append("displayInfo=$telephonyDisplayInfo") + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + if (!super.equals(other)) return false + + other as MobileState + + if (networkName != other.networkName) return false + if (networkNameData != other.networkNameData) return false + if (dataSim != other.dataSim) return false + if (dataConnected != other.dataConnected) return false + if (isEmergency != other.isEmergency) return false + if (airplaneMode != other.airplaneMode) return false + if (carrierNetworkChangeMode != other.carrierNetworkChangeMode) return false + if (isDefault != other.isDefault) return false + if (userSetup != other.userSetup) return false + if (roaming != other.roaming) return false + if (dataState != other.dataState) return false + if (defaultDataOff != other.defaultDataOff) return false + if (telephonyDisplayInfo != other.telephonyDisplayInfo) return false + if (serviceState != other.serviceState) return false + if (signalStrength != other.signalStrength) return false + + return true + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + (networkName?.hashCode() ?: 0) + result = 31 * result + (networkNameData?.hashCode() ?: 0) + result = 31 * result + dataSim.hashCode() + result = 31 * result + dataConnected.hashCode() + result = 31 * result + isEmergency.hashCode() + result = 31 * result + airplaneMode.hashCode() + result = 31 * result + carrierNetworkChangeMode.hashCode() + result = 31 * result + isDefault.hashCode() + result = 31 * result + userSetup.hashCode() + result = 31 * result + roaming.hashCode() + result = 31 * result + dataState + result = 31 * result + defaultDataOff.hashCode() + result = 31 * result + telephonyDisplayInfo.hashCode() + result = 31 * result + (serviceState?.hashCode() ?: 0) + result = 31 * result + (signalStrength?.hashCode() ?: 0) + return result + } +} + +/** toString() is a little more verbose than we need. Just log the fields we read */ +private fun ServiceState.minLog(): String { + return "serviceState={" + + "state=$state," + + "isEmergencyOnly=$isEmergencyOnly," + + "roaming=$roaming," + + "operatorNameAlphaShort=$operatorAlphaShort}" +} + +private fun SignalStrength.minLog(): String { + return "signalStrength={" + + "isGsm=$isGsm," + + "level=$level}" +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java index 143309658779..f960eb7b6e9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java @@ -16,19 +16,10 @@ package com.android.systemui.statusbar.connectivity; -import android.content.Context; -import android.content.Intent; -import android.telephony.SubscriptionInfo; - import com.android.settingslib.net.DataUsageController; import com.android.systemui.demomode.DemoMode; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.statusbar.policy.DataSaverController; -import com.android.wifitrackerlib.MergedCarrierEntry; -import com.android.wifitrackerlib.WifiEntry; - -import java.util.List; /** */ public interface NetworkController extends CallbackController<SignalCallback>, DemoMode { @@ -62,197 +53,8 @@ public interface NetworkController extends CallbackController<SignalCallback>, D /** */ boolean isRadioOn(); - /** - * Wrapper class for all the WiFi signals used for WiFi indicators. - */ - final class WifiIndicators { - public boolean enabled; - public IconState statusIcon; - public IconState qsIcon; - public boolean activityIn; - public boolean activityOut; - public String description; - public boolean isTransient; - public String statusLabel; - - public WifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, - boolean activityIn, boolean activityOut, String description, - boolean isTransient, String statusLabel) { - this.enabled = enabled; - this.statusIcon = statusIcon; - this.qsIcon = qsIcon; - this.activityIn = activityIn; - this.activityOut = activityOut; - this.description = description; - this.isTransient = isTransient; - this.statusLabel = statusLabel; - } - - @Override - public String toString() { - return new StringBuilder("WifiIndicators[") - .append("enabled=").append(enabled) - .append(",statusIcon=").append(statusIcon == null ? "" : statusIcon.toString()) - .append(",qsIcon=").append(qsIcon == null ? "" : qsIcon.toString()) - .append(",activityIn=").append(activityIn) - .append(",activityOut=").append(activityOut) - .append(",qsDescription=").append(description) - .append(",isTransient=").append(isTransient) - .append(",statusLabel=").append(statusLabel) - .append(']').toString(); - } - } - - /** - * Wrapper class for all the mobile signals used for mobile data indicators. - */ - final class MobileDataIndicators { - public IconState statusIcon; - public IconState qsIcon; - public int statusType; - public int qsType; - public boolean activityIn; - public boolean activityOut; - public CharSequence typeContentDescription; - public CharSequence typeContentDescriptionHtml; - public CharSequence qsDescription; - public int subId; - public boolean roaming; - public boolean showTriangle; - - public MobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, - int qsType, boolean activityIn, boolean activityOut, - CharSequence typeContentDescription, CharSequence typeContentDescriptionHtml, - CharSequence qsDescription, int subId, boolean roaming, - boolean showTriangle) { - this.statusIcon = statusIcon; - this.qsIcon = qsIcon; - this.statusType = statusType; - this.qsType = qsType; - this.activityIn = activityIn; - this.activityOut = activityOut; - this.typeContentDescription = typeContentDescription; - this.typeContentDescriptionHtml = typeContentDescriptionHtml; - this.qsDescription = qsDescription; - this.subId = subId; - this.roaming = roaming; - this.showTriangle = showTriangle; - } - - @Override - public String toString() { - return new StringBuilder("MobileDataIndicators[") - .append("statusIcon=").append(statusIcon == null ? "" : statusIcon.toString()) - .append(",qsIcon=").append(qsIcon == null ? "" : qsIcon.toString()) - .append(",statusType=").append(statusType) - .append(",qsType=").append(qsType) - .append(",activityIn=").append(activityIn) - .append(",activityOut=").append(activityOut) - .append(",typeContentDescription=").append(typeContentDescription) - .append(",typeContentDescriptionHtml=").append(typeContentDescriptionHtml) - .append(",description=").append(qsDescription) - .append(",subId=").append(subId) - .append(",roaming=").append(roaming) - .append(",showTriangle=").append(showTriangle) - .append(']').toString(); - } - } - - /** */ - interface SignalCallback { - /** - * Callback for listeners to be able to update the state of any UI tracking connectivity of - * WiFi networks. - */ - default void setWifiIndicators(WifiIndicators wifiIndicators) {} - - /** - * Callback for listeners to be able to update the state of any UI tracking connectivity - * of Mobile networks. - */ - default void setMobileDataIndicators(MobileDataIndicators mobileDataIndicators) {} - - /** */ - default void setSubs(List<SubscriptionInfo> subs) {} - - /** */ - default void setNoSims(boolean show, boolean simDetected) {} - - /** */ - default void setEthernetIndicators(IconState icon) {} - - /** */ - default void setIsAirplaneMode(IconState icon) {} - - /** */ - default void setMobileDataEnabled(boolean enabled) {} - - /** - * Callback for listeners to be able to update the connectivity status - * @param noDefaultNetwork whether there is any default network. - * @param noValidatedNetwork whether there is any validated network. - * @param noNetworksAvailable whether there is any WiFi networks available. - */ - default void setConnectivityStatus(boolean noDefaultNetwork, boolean noValidatedNetwork, - boolean noNetworksAvailable) {} - - /** - * Callback for listeners to be able to update the call indicator - * @param statusIcon the icon for the call indicator - * @param subId subscription ID for which to update the UI - */ - default void setCallIndicator(IconState statusIcon, int subId) {} - } - /** */ interface EmergencyListener { void setEmergencyCallsOnly(boolean emergencyOnly); } - - /** */ - class IconState { - public final boolean visible; - public final int icon; - public final String contentDescription; - - public IconState(boolean visible, int icon, String contentDescription) { - this.visible = visible; - this.icon = icon; - this.contentDescription = contentDescription; - } - - public IconState(boolean visible, int icon, int contentDescription, - Context context) { - this(visible, icon, context.getString(contentDescription)); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - return builder.append("[visible=").append(visible).append(',') - .append("icon=").append(icon).append(',') - .append("contentDescription=").append(contentDescription).append(']') - .toString(); - } - } - - /** - * Tracks changes in access points. Allows listening for changes, scanning for new APs, - * and connecting to new ones. - */ - interface AccessPointController { - void addAccessPointCallback(AccessPointCallback callback); - void removeAccessPointCallback(AccessPointCallback callback); - void scanForAccessPoints(); - MergedCarrierEntry getMergedCarrierEntry(); - int getIcon(WifiEntry ap); - boolean connect(WifiEntry ap); - boolean canConfigWifi(); - boolean canConfigMobileData(); - - interface AccessPointCallback { - void onAccessPointsChanged(List<WifiEntry> accessPoints); - void onSettingsActivityTriggered(Intent settingsIntent); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index daae43f69d3b..3f5ef4806a1e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -721,8 +721,11 @@ public class NetworkControllerImpl extends BroadcastReceiver @Override public void addCallback(@NonNull SignalCallback cb) { cb.setSubs(mCurrentSubscriptions); - cb.setIsAirplaneMode(new IconState(mAirplaneMode, - TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext)); + cb.setIsAirplaneMode( + new IconState( + mAirplaneMode, + TelephonyIcons.FLIGHT_MODE_ICON, + mContext.getString(R.string.accessibility_airplane_mode))); cb.setNoSims(mHasNoSubs, mSimDetected); if (mProviderModelSetting) { cb.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition, mNoNetworksAvailable); @@ -1056,8 +1059,11 @@ public class NetworkControllerImpl extends BroadcastReceiver * notifyAllListeners. */ private void notifyListeners() { - mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode, - TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext)); + mCallbackHandler.setIsAirplaneMode( + new IconState( + mAirplaneMode, + TelephonyIcons.FLIGHT_MODE_ICON, + mContext.getString(R.string.accessibility_airplane_mode))); mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected); } @@ -1206,7 +1212,7 @@ public class NetworkControllerImpl extends BroadcastReceiver } private boolean mDemoInetCondition; - private WifiSignalController.WifiState mDemoWifiState; + private WifiState mDemoWifiState; @Override public void onDemoModeStarted() { @@ -1241,9 +1247,11 @@ public class NetworkControllerImpl extends BroadcastReceiver String airplane = args.getString("airplane"); if (airplane != null) { boolean show = airplane.equals("show"); - mCallbackHandler.setIsAirplaneMode(new IconState(show, - TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, - mContext)); + mCallbackHandler.setIsAirplaneMode( + new IconState( + show, + TelephonyIcons.FLIGHT_MODE_ICON, + mContext.getString(R.string.accessibility_airplane_mode))); } String fully = args.getString("fully"); if (fully != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt new file mode 100644 index 000000000000..599beecb0e00 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.connectivity + +import android.telephony.SubscriptionInfo + +/** + * SignalCallback contains all of the connectivity updates from [NetworkController]. Implement this + * interface to be able to draw iconography for Wi-Fi, mobile data, ethernet, call strength + * indicators, etc. + */ +interface SignalCallback { + /** + * Called when the Wi-Fi iconography has been updated. Implement this method to draw Wi-Fi icons + * + * @param wifiIndicators a box type containing enough information to properly draw a Wi-Fi icon + */ + @JvmDefault + fun setWifiIndicators(wifiIndicators: WifiIndicators) {} + + /** + * Called when the mobile iconography has been updated. Implement this method to draw mobile + * indicators + * + * @param mobileDataIndicators a box type containing enough information to properly draw + * mobile data icons + * + * NOTE: phones can have multiple subscriptions, so this [mobileDataIndicators] object should be + * indexed based on its [subId][MobileDataIndicators.subId] + */ + @JvmDefault + fun setMobileDataIndicators(mobileDataIndicators: MobileDataIndicators) {} + + /** + * Called when the list of mobile data subscriptions has changed. Use this method as a chance + * to remove views that are no longer needed, or to make room for new icons to come in + * + * @param subs a [SubscriptionInfo] for each subscription that we know about + */ + @JvmDefault + fun setSubs(subs: List<@JvmSuppressWildcards SubscriptionInfo>) {} + + /** + * Called when: + * 1. The number of [MobileSignalController]s goes to 0 while mobile data is enabled + * OR + * 2. The presence of any SIM changes + * + * @param show whether or not to show a "no sim" view + * @param simDetected whether any SIM is detected or not + */ + @JvmDefault + fun setNoSims(show: Boolean, simDetected: Boolean) {} + + /** + * Called when there is any update to the ethernet iconography. Implement this method to set an + * ethernet icon + * + * @param icon an [IconState] for the current ethernet status + */ + @JvmDefault + fun setEthernetIndicators(icon: IconState) {} + + /** + * Called whenever airplane mode changes + * + * @param icon an [IconState] for the current airplane mode status + */ + @JvmDefault + fun setIsAirplaneMode(icon: IconState) {} + + /** + * Called whenever the mobile data feature enabled state changes + * + * @param enabled the current mobile data feature ennabled state + */ + @JvmDefault + fun setMobileDataEnabled(enabled: Boolean) {} + + /** + * Callback for listeners to be able to update the connectivity status + * @param noDefaultNetwork whether there is any default network. + * @param noValidatedNetwork whether there is any validated network. + * @param noNetworksAvailable whether there is any WiFi networks available. + */ + @JvmDefault + fun setConnectivityStatus( + noDefaultNetwork: Boolean, + noValidatedNetwork: Boolean, + noNetworksAvailable: Boolean + ) { } + + /** + * Callback for listeners to be able to update the call indicator + * @param statusIcon the icon for the call indicator + * @param subId subscription ID for which to update the UI + */ + @JvmDefault + fun setCallIndicator(statusIcon: IconState, subId: Int) {} +} + +/** Box type for [SignalCallback.setWifiIndicators] */ +data class WifiIndicators( + @JvmField val enabled: Boolean, + @JvmField val statusIcon: IconState?, + @JvmField val qsIcon: IconState?, + @JvmField val activityIn: Boolean, + @JvmField val activityOut: Boolean, + @JvmField val description: String?, + @JvmField val isTransient: Boolean, + @JvmField val statusLabel: String? +) { + override fun toString(): String { + return StringBuilder("WifiIndicators[") + .append("enabled=").append(enabled) + .append(",statusIcon=").append(statusIcon?.toString() ?: "") + .append(",qsIcon=").append(qsIcon?.toString() ?: "") + .append(",activityIn=").append(activityIn) + .append(",activityOut=").append(activityOut) + .append(",qsDescription=").append(description) + .append(",isTransient=").append(isTransient) + .append(",statusLabel=").append(statusLabel) + .append(']').toString() + } +} + +/** Box type for [SignalCallback.setMobileDataIndicators] */ +data class MobileDataIndicators( + @JvmField val statusIcon: IconState?, + @JvmField val qsIcon: IconState?, + @JvmField val statusType: Int, + @JvmField val qsType: Int, + @JvmField val activityIn: Boolean, + @JvmField val activityOut: Boolean, + @JvmField val typeContentDescription: CharSequence?, + @JvmField val typeContentDescriptionHtml: CharSequence?, + @JvmField val qsDescription: CharSequence?, + @JvmField val subId: Int, + @JvmField val roaming: Boolean, + @JvmField val showTriangle: Boolean +) { + override fun toString(): String { + return java.lang.StringBuilder("MobileDataIndicators[") + .append("statusIcon=").append(statusIcon?.toString() ?: "") + .append(",qsIcon=").append(qsIcon?.toString() ?: "") + .append(",statusType=").append(statusType) + .append(",qsType=").append(qsType) + .append(",activityIn=").append(activityIn) + .append(",activityOut=").append(activityOut) + .append(",typeContentDescription=").append(typeContentDescription) + .append(",typeContentDescriptionHtml=").append(typeContentDescriptionHtml) + .append(",description=").append(qsDescription) + .append(",subId=").append(subId) + .append(",roaming=").append(roaming) + .append(",showTriangle=").append(showTriangle) + .append(']').toString() + } +} + +/** Box for an icon with its visibility and content description */ +data class IconState( + @JvmField val visible: Boolean, + @JvmField val icon: Int, + @JvmField val contentDescription: String +) { + override fun toString(): String { + val builder = java.lang.StringBuilder() + return builder.append("[visible=").append(visible).append(',') + .append("icon=").append(icon).append(',') + .append("contentDescription=").append(contentDescription).append(']') + .toString() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java index d23dba579be6..cd2006899cfc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java @@ -22,9 +22,6 @@ import android.content.Context; import android.util.Log; import com.android.settingslib.SignalIcon.IconGroup; -import com.android.settingslib.SignalIcon.State; -import com.android.systemui.statusbar.connectivity.NetworkController.IconState; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; import java.io.PrintWriter; import java.util.BitSet; @@ -36,7 +33,7 @@ import java.util.BitSet; * @param <T> State of the SysUI controller. * @param <I> Icon groups of the SysUI controller for a given State. */ -public abstract class SignalController<T extends State, I extends IconGroup> { +public abstract class SignalController<T extends ConnectivityState, I extends IconGroup> { // Save the previous SignalController.States of all SignalControllers for dumps. static final boolean RECORD_HISTORY = true; // If RECORD_HISTORY how many to save, must be a power of 2. @@ -58,7 +55,7 @@ public abstract class SignalController<T extends State, I extends IconGroup> { private final CallbackHandler mCallbackHandler; // Save the previous HISTORY_SIZE states for logging. - private final State[] mHistory; + private final ConnectivityState[] mHistory; // Where to copy the next state into. private int mHistoryIndex; @@ -72,7 +69,7 @@ public abstract class SignalController<T extends State, I extends IconGroup> { mCurrentState = cleanState(); mLastState = cleanState(); if (RECORD_HISTORY) { - mHistory = new State[HISTORY_SIZE]; + mHistory = new ConnectivityState[HISTORY_SIZE]; for (int i = 0; i < HISTORY_SIZE; i++) { mHistory[i] = cleanState(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java index 3622a66767a3..103ca0ebc6ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java @@ -26,28 +26,20 @@ import android.net.NetworkCapabilities; import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.text.Html; -import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.SignalIcon.IconGroup; import com.android.settingslib.SignalIcon.MobileIconGroup; -import com.android.settingslib.SignalIcon.State; import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.wifi.WifiStatusTracker; import com.android.systemui.R; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.statusbar.connectivity.NetworkController.IconState; -import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; -import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators; import java.io.PrintWriter; -import java.util.Objects; /** */ -public class WifiSignalController extends - SignalController<WifiSignalController.WifiState, IconGroup> { +public class WifiSignalController extends SignalController<WifiState, IconGroup> { private final boolean mHasMobileDataFeature; private final WifiStatusTracker mWifiTracker; private final IconGroup mUnmergedWifiIconGroup = WifiIcons.UNMERGED_WIFI; @@ -272,50 +264,4 @@ public class WifiSignalController extends setActivity(state); } } - - static class WifiState extends State { - public String ssid; - public boolean isTransient; - public boolean isDefault; - public String statusLabel; - public boolean isCarrierMerged; - public int subId; - - @Override - public void copyFrom(State s) { - super.copyFrom(s); - WifiState state = (WifiState) s; - ssid = state.ssid; - isTransient = state.isTransient; - isDefault = state.isDefault; - statusLabel = state.statusLabel; - isCarrierMerged = state.isCarrierMerged; - subId = state.subId; - } - - @Override - protected void toString(StringBuilder builder) { - super.toString(builder); - builder.append(",ssid=").append(ssid) - .append(",isTransient=").append(isTransient) - .append(",isDefault=").append(isDefault) - .append(",statusLabel=").append(statusLabel) - .append(",isCarrierMerged=").append(isCarrierMerged) - .append(",subId=").append(subId); - } - - @Override - public boolean equals(Object o) { - if (!super.equals(o)) { - return false; - } - WifiState other = (WifiState) o; - return Objects.equals(other.ssid, ssid) - && other.isTransient == isTransient - && other.isDefault == isDefault - && TextUtils.equals(other.statusLabel, statusLabel) - && other.isCarrierMerged == isCarrierMerged - && other.subId == subId; - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt new file mode 100644 index 000000000000..ac15f78191f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.connectivity + +import java.lang.StringBuilder + +internal class WifiState( + @JvmField var ssid: String? = null, + @JvmField var isTransient: Boolean = false, + @JvmField var isDefault: Boolean = false, + @JvmField var statusLabel: String? = null, + @JvmField var isCarrierMerged: Boolean = false, + @JvmField var subId: Int = 0 +) : ConnectivityState() { + + public override fun copyFrom(s: ConnectivityState) { + super.copyFrom(s) + val state = s as WifiState + ssid = state.ssid + isTransient = state.isTransient + isDefault = state.isDefault + statusLabel = state.statusLabel + isCarrierMerged = state.isCarrierMerged + subId = state.subId + } + + override fun toString(builder: StringBuilder) { + super.toString(builder) + builder.append(",ssid=").append(ssid) + .append(",isTransient=").append(isTransient) + .append(",isDefault=").append(isDefault) + .append(",statusLabel=").append(statusLabel) + .append(",isCarrierMerged=").append(isCarrierMerged) + .append(",subId=").append(subId) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + if (!super.equals(other)) return false + + other as WifiState + + if (ssid != other.ssid) return false + if (isTransient != other.isTransient) return false + if (isDefault != other.isDefault) return false + if (statusLabel != other.statusLabel) return false + if (isCarrierMerged != other.isCarrierMerged) return false + if (subId != other.subId) return false + + return true + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + (ssid?.hashCode() ?: 0) + result = 31 * result + isTransient.hashCode() + result = 31 * result + isDefault.hashCode() + result = 31 * result + (statusLabel?.hashCode() ?: 0) + result = 31 * result + isCarrierMerged.hashCode() + result = 31 * result + subId + return result + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt index b97bac261ff0..3b118a34b5be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -393,11 +393,11 @@ class PrivacyDotViewController @Inject constructor( animationScheduler.addCallback(systemStatusAnimationCallback) } - val left = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_SEASCAPE) - val top = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_NONE) - val right = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_LANDSCAPE) + val left = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_SEASCAPE) + val top = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_NONE) + val right = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_LANDSCAPE) val bottom = contentInsetsProvider - .getStatusBarContentInsetsForRotation(ROTATION_UPSIDE_DOWN) + .getStatusBarContentAreaForRotation(ROTATION_UPSIDE_DOWN) val paddingTop = contentInsetsProvider.getStatusBarPaddingTop() synchronized(lock) { @@ -528,11 +528,11 @@ class PrivacyDotViewController @Inject constructor( // Returns [left, top, right, bottom] aka [seascape, none, landscape, upside-down] private fun getLayoutRects(): List<Rect> { - val left = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_SEASCAPE) - val top = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_NONE) - val right = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_LANDSCAPE) + val left = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_SEASCAPE) + val top = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_NONE) + val right = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_LANDSCAPE) val bottom = contentInsetsProvider - .getStatusBarContentInsetsForRotation(ROTATION_UPSIDE_DOWN) + .getStatusBarContentAreaForRotation(ROTATION_UPSIDE_DOWN) return listOf(left, top, right, bottom) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index d5912e0ddbe7..fe621da17767 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -26,6 +26,7 @@ import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedul import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT; import android.animation.ValueAnimator; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Fragment; import android.os.Bundle; @@ -46,13 +47,15 @@ import com.android.systemui.statusbar.DisableFlagsLogger.DisableState; import com.android.systemui.statusbar.OperatorNameView; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.connectivity.IconState; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; +import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.EncryptionHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -101,13 +104,15 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final StatusBarLocationPublisher mLocationPublisher; private final FeatureFlags mFeatureFlags; private final NotificationIconAreaController mNotificationIconAreaController; + private final PanelExpansionStateManager mPanelExpansionStateManager; private final StatusBarIconController mStatusBarIconController; + private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; private List<String> mBlockedIcons = new ArrayList<>(); private SignalCallback mSignalCallback = new SignalCallback() { @Override - public void setIsAirplaneMode(NetworkController.IconState icon) { + public void setIsAirplaneMode(@NonNull IconState icon) { mCommandQueue.recomputeDisableFlags(getContext().getDisplayId(), true /* animate */); } }; @@ -126,9 +131,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue SystemStatusAnimationScheduler animationScheduler, StatusBarLocationPublisher locationPublisher, NotificationIconAreaController notificationIconAreaController, + PanelExpansionStateManager panelExpansionStateManager, FeatureFlags featureFlags, Lazy<Optional<StatusBar>> statusBarOptionalLazy, StatusBarIconController statusBarIconController, + StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, NetworkController networkController, StatusBarStateController statusBarStateController, @@ -140,9 +147,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mAnimationScheduler = animationScheduler; mLocationPublisher = locationPublisher; mNotificationIconAreaController = notificationIconAreaController; + mPanelExpansionStateManager = panelExpansionStateManager; mFeatureFlags = featureFlags; mStatusBarOptionalLazy = statusBarOptionalLazy; mStatusBarIconController = statusBarIconController; + mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mKeyguardStateController = keyguardStateController; mNetworkController = networkController; mStatusBarStateController = statusBarStateController; @@ -363,15 +372,12 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private boolean shouldHideNotificationIcons() { final Optional<StatusBar> statusBarOptional = mStatusBarOptionalLazy.get(); - if (!mStatusBar.isClosed() + if (!mPanelExpansionStateManager.isClosed() && statusBarOptional.map( StatusBar::hideStatusBarIconsWhenExpanded).orElse(false)) { return true; } - if (statusBarOptional.map(StatusBar::hideStatusBarIconsForBouncer).orElse(false)) { - return true; - } - return false; + return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer(); } private void hideSystemIconArea(boolean animate) { @@ -409,7 +415,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * don't set the clock GONE otherwise it'll mess up the animation. */ private int clockHiddenMode() { - if (!mStatusBar.isClosed() && !mKeyguardStateController.isShowing() + if (!mPanelExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing() && !mStatusBarStateController.isDozing()) { return View.INVISIBLE; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java index 21c3e5e0a8d0..7de4668abe28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java @@ -21,7 +21,6 @@ import android.os.Handler; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Dependency; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; @@ -92,10 +91,14 @@ public class DozeScrimController implements StateListener { }; @Inject - public DozeScrimController(DozeParameters dozeParameters, DozeLog dozeLog) { + public DozeScrimController( + DozeParameters dozeParameters, + DozeLog dozeLog, + StatusBarStateController statusBarStateController + ) { mDozeParameters = dozeParameters; - //Never expected to be destroyed - Dependency.get(StatusBarStateController.class).addCallback(this); + // Never expected to be destroyed + statusBarStateController.addCallback(this); mDozeLog = dozeLog; } @@ -219,6 +222,10 @@ public class DozeScrimController implements StateListener { @Override public void onDozingChanged(boolean isDozing) { + if (mDozing != isDozing) { + mDozeLog.traceDozingChanged(isDozing); + } + setDozing(isDozing); } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 03786b9b58ee..f068a8ec8294 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -47,7 +47,6 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; -import com.android.systemui.statusbar.window.StatusBarWindowView; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -239,35 +238,32 @@ public class KeyguardStatusBarView extends RelativeLayout { } } - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { + /** Should only be called from {@link KeyguardStatusBarViewController}. */ + WindowInsets updateWindowInsets( + WindowInsets insets, + StatusBarContentInsetsProvider insetsProvider) { mLayoutState = LAYOUT_NONE; - if (updateLayoutConsideringCutout()) { + if (updateLayoutConsideringCutout(insetsProvider)) { requestLayout(); } return super.onApplyWindowInsets(insets); } - private boolean updateLayoutConsideringCutout() { + private boolean updateLayoutConsideringCutout(StatusBarContentInsetsProvider insetsProvider) { mDisplayCutout = getRootWindowInsets().getDisplayCutout(); updateKeyguardStatusBarHeight(); - - Pair<Integer, Integer> cornerCutoutMargins = - StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay()); - updatePadding(cornerCutoutMargins); - if (mDisplayCutout == null || cornerCutoutMargins != null) { + updatePadding(insetsProvider); + if (mDisplayCutout == null || insetsProvider.currentRotationHasCornerCutout()) { return updateLayoutParamsNoCutout(); } else { return updateLayoutParamsForCutout(); } } - private void updatePadding(Pair<Integer, Integer> cornerCutoutMargins) { + private void updatePadding(StatusBarContentInsetsProvider insetsProvider) { final int waterfallTop = mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top; - mPadding = - StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner( - mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding); + mPadding = insetsProvider.getStatusBarContentInsetsForCurrentRotation(); // consider privacy dot space final int minLeft = (isLayoutRtl() && mIsPrivacyDotEnabled) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 90550818bbdd..e7d5724fa9bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -91,6 +91,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final BiometricUnlockController mBiometricUnlockController; private final SysuiStatusBarStateController mStatusBarStateController; + private final StatusBarContentInsetsProvider mInsetsProvider; private final ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @@ -228,7 +229,9 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat KeyguardBypassController bypassController, KeyguardUpdateMonitor keyguardUpdateMonitor, BiometricUnlockController biometricUnlockController, - SysuiStatusBarStateController statusBarStateController) { + SysuiStatusBarStateController statusBarStateController, + StatusBarContentInsetsProvider statusBarContentInsetsProvider + ) { super(view); mCarrierTextController = carrierTextController; mConfigurationController = configurationController; @@ -244,6 +247,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mKeyguardUpdateMonitor = keyguardUpdateMonitor; mBiometricUnlockController = biometricUnlockController; mStatusBarStateController = statusBarStateController; + mInsetsProvider = statusBarContentInsetsProvider; mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled(); mKeyguardStateController.addCallback( @@ -287,6 +291,9 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mTintedIconManager.setBlockList(mBlockedIcons); mStatusBarIconController.addIconGroup(mTintedIconManager); } + mView.setOnApplyWindowInsetsListener( + (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider)); + onThemeChanged(); } 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 df0f872d979c..32659e416535 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -35,9 +35,9 @@ import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; -import static com.android.systemui.statusbar.phone.PanelBar.STATE_CLOSED; -import static com.android.systemui.statusbar.phone.PanelBar.STATE_OPEN; -import static com.android.systemui.statusbar.phone.PanelBar.STATE_OPENING; +import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED; +import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN; +import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING; import static java.lang.Float.isNaN; @@ -173,6 +173,7 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; +import com.android.systemui.statusbar.phone.panelstate.PanelState; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -782,6 +783,8 @@ public class NotificationPanelViewController extends PanelViewController { new DynamicPrivacyControlListener(); dynamicPrivacyController.addListener(dynamicPrivacyControlListener); + panelExpansionStateManager.addStateListener(this::onPanelStateChanged); + mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0); mBottomAreaShadeAlphaAnimator.addUpdateListener(animation -> { mBottomAreaShadeAlpha = (float) animation.getAnimatedValue(); @@ -1561,7 +1564,7 @@ public class NotificationPanelViewController extends PanelViewController { // it's possible that nothing animated, so we replicate the termination // conditions of panelExpansionChanged here // TODO(b/200063118): This can likely go away in a future refactor CL. - mBar.updateState(STATE_CLOSED); + getPanelExpansionStateManager().updateState(STATE_CLOSED); } } @@ -1646,7 +1649,7 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void fling(float vel, boolean expand) { - GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); + GestureRecorder gr = mStatusBar.getGestureRecorder(); if (gr != null) { gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); } @@ -2203,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 @@ -2238,14 +2241,22 @@ public class NotificationPanelViewController extends PanelViewController { private void updateQsExpansion() { if (mQs == null) return; float qsExpansionFraction = computeQsExpansionFraction(); + float squishiness = mNotificationStackScrollLayoutController + .getNotificationSquishinessFraction(); mQs.setQsExpansion(qsExpansionFraction, getExpandedFraction(), getHeaderTranslation(), - mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()); + mQsExpandImmediate || mQsExpanded ? 1f : squishiness); mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); mMediaHierarchyManager.setQsExpansion(qsExpansionFraction); 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); } @@ -3787,45 +3798,6 @@ public class NotificationPanelViewController extends PanelViewController { private long mLastTouchDownTime = -1L; @Override - public boolean onTouchForwardedFromStatusBar(MotionEvent event) { - // TODO(b/202981994): Move the touch debugging in this method to a central location. - // (Right now, it's split between StatusBar and here.) - - // If panels aren't enabled, ignore the gesture and don't pass it down to the - // panel view. - if (!mCommandQueue.panelsEnabled()) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - Log.v( - TAG, - String.format( - "onTouchForwardedFromStatusBar: " - + "panel disabled, ignoring touch at (%d,%d)", - (int) event.getX(), - (int) event.getY() - ) - ); - } - return false; - } - - // If the view that would receive the touch is disabled, just have status bar eat - // the gesture. - if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) { - Log.v(TAG, - String.format( - "onTouchForwardedFromStatusBar: " - + "panel view disabled, eating touch at (%d,%d)", - (int) event.getX(), - (int) event.getY() - ) - ); - return true; - } - - return mView.dispatchTouchEvent(event); - } - - @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (mBlockTouches || mQs.disallowPanelTouches()) { return false; @@ -3936,6 +3908,55 @@ public class NotificationPanelViewController extends PanelViewController { }; } + private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler = + new PhoneStatusBarView.TouchEventHandler() { + @Override + public void onInterceptTouchEvent(MotionEvent event) { + mStatusBar.onTouchEvent(event); + } + + @Override + public boolean handleTouchEvent(MotionEvent event) { + mStatusBar.onTouchEvent(event); + + // TODO(b/202981994): Move the touch debugging in this method to a central + // location. (Right now, it's split between StatusBar and here.) + + // If panels aren't enabled, ignore the gesture and don't pass it down to the + // panel view. + if (!mCommandQueue.panelsEnabled()) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + Log.v( + TAG, + String.format( + "onTouchForwardedFromStatusBar: " + + "panel disabled, ignoring touch at (%d,%d)", + (int) event.getX(), + (int) event.getY() + ) + ); + } + return false; + } + + // If the view that would receive the touch is disabled, just have status bar + // eat the gesture. + if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) { + Log.v(TAG, + String.format( + "onTouchForwardedFromStatusBar: " + + "panel view disabled, eating touch at (%d,%d)", + (int) event.getX(), + (int) event.getY() + ) + ); + return true; + } + + return mView.dispatchTouchEvent(event); + } + }; + @Override protected PanelViewController.OnConfigurationChangedListener createOnConfigurationChangedListener() { @@ -4672,39 +4693,29 @@ public class NotificationPanelViewController extends PanelViewController { mView.removeCallbacks(mMaybeHideExpandedRunnable); } - private final PanelBar.PanelStateChangeListener mPanelStateChangeListener = - new PanelBar.PanelStateChangeListener() { + @PanelState + private int mCurrentPanelState = STATE_CLOSED; - @PanelBar.PanelState - private int mCurrentState = STATE_CLOSED; - - @Override - public void onStateChanged(@PanelBar.PanelState int state) { - mAmbientState.setIsShadeOpening(state == STATE_OPENING); - updateQSExpansionEnabledAmbient(); - - if (state == STATE_OPEN && mCurrentState != state) { - mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - } - if (state == STATE_OPENING) { - mStatusBar.makeExpandedVisible(false); - } - if (state == STATE_CLOSED) { - // Close the status bar in the next frame so we can show the end of the - // animation. - mView.post(mMaybeHideExpandedRunnable); - } - mCurrentState = state; - } - }; + private void onPanelStateChanged(@PanelState int state) { + mAmbientState.setIsShadeOpening(state == STATE_OPENING); + updateQSExpansionEnabledAmbient(); - public PanelBar.PanelStateChangeListener getPanelStateChangeListener() { - return mPanelStateChangeListener; + if (state == STATE_OPEN && mCurrentPanelState != state) { + mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + } + if (state == STATE_OPENING) { + mStatusBar.makeExpandedVisible(false); + } + if (state == STATE_CLOSED) { + // Close the status bar in the next frame so we can show the end of the + // animation. + mView.post(mMaybeHideExpandedRunnable); + } + mCurrentPanelState = state; } - /** Returns the handler that the status bar should forward touches to. */ public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() { - return getTouchHandler()::onTouchForwardedFromStatusBar; + return mStatusBarViewTouchEventHandler; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index 0b3e040bd321..01587f7fe98c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -447,7 +447,7 @@ public class NotificationShadeWindowViewController { setDragDownHelper(mLockscreenShadeTransitionController.getTouchHelper()); mDepthController.setRoot(mView); - mPanelExpansionStateManager.addListener(mDepthController); + mPanelExpansionStateManager.addExpansionListener(mDepthController); } public NotificationShadeWindowView getView() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java deleted file mode 100644 index e90258db8571..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone; - -import static java.lang.Float.isNaN; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.annotation.IntDef; -import android.content.Context; -import android.os.Bundle; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.util.Log; -import android.widget.FrameLayout; - -import androidx.annotation.Nullable; - -import java.lang.annotation.Retention; - -public abstract class PanelBar extends FrameLayout { - public static final boolean DEBUG = false; - public static final String TAG = PanelBar.class.getSimpleName(); - private static final boolean SPEW = false; - private static final String PANEL_BAR_SUPER_PARCELABLE = "panel_bar_super_parcelable"; - private static final String STATE = "state"; - protected float mPanelFraction; - - public static final void LOG(String fmt, Object... args) { - if (!DEBUG) return; - Log.v(TAG, String.format(fmt, args)); - } - - /** Enum for the current state of the panel. */ - @Retention(SOURCE) - @IntDef({STATE_CLOSED, STATE_OPENING, STATE_OPEN}) - @interface PanelState {} - public static final int STATE_CLOSED = 0; - public static final int STATE_OPENING = 1; - public static final int STATE_OPEN = 2; - - @Nullable private PanelStateChangeListener mPanelStateChangeListener; - private int mState = STATE_CLOSED; - private boolean mTracking; - - /** Updates the panel state if necessary. */ - public void updateState(@PanelState int state) { - if (DEBUG) LOG("update state: %d -> %d", mState, state); - if (mState != state) { - go(state); - } - } - - private void go(@PanelState int state) { - if (DEBUG) LOG("go state: %d -> %d", mState, state); - mState = state; - if (mPanelStateChangeListener != null) { - mPanelStateChangeListener.onStateChanged(state); - } - } - - @Override - protected Parcelable onSaveInstanceState() { - Bundle bundle = new Bundle(); - bundle.putParcelable(PANEL_BAR_SUPER_PARCELABLE, super.onSaveInstanceState()); - bundle.putInt(STATE, mState); - return bundle; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - if (state == null || !(state instanceof Bundle)) { - super.onRestoreInstanceState(state); - return; - } - - Bundle bundle = (Bundle) state; - super.onRestoreInstanceState(bundle.getParcelable(PANEL_BAR_SUPER_PARCELABLE)); - if (((Bundle) state).containsKey(STATE)) { - go(bundle.getInt(STATE, STATE_CLOSED)); - } - } - - public PanelBar(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - } - - /** Sets the listener that will be notified of panel state changes. */ - public void setPanelStateChangeListener(PanelStateChangeListener listener) { - mPanelStateChangeListener = listener; - } - - /** - * @param frac the fraction from the expansion in [0, 1] - * @param expanded whether the panel is currently expanded; this is independent from the - * fraction as the panel also might be expanded if the fraction is 0 - */ - public void panelExpansionChanged(float frac, boolean expanded) { - if (isNaN(frac)) { - throw new IllegalArgumentException("frac cannot be NaN"); - } - boolean fullyClosed = true; - boolean fullyOpened = false; - if (SPEW) LOG("panelExpansionChanged: start state=%d, f=%.1f", mState, frac); - mPanelFraction = frac; - // adjust any other panels that may be partially visible - if (expanded) { - if (mState == STATE_CLOSED) { - go(STATE_OPENING); - } - fullyClosed = false; - fullyOpened = frac >= 1f; - } - if (fullyOpened && !mTracking) { - go(STATE_OPEN); - } else if (fullyClosed && !mTracking && mState != STATE_CLOSED) { - go(STATE_CLOSED); - } - - if (SPEW) LOG("panelExpansionChanged: end state=%d [%s%s ]", mState, - fullyOpened?" fullyOpened":"", fullyClosed?" fullyClosed":""); - } - - public boolean isClosed() { - return mState == STATE_CLOSED; - } - - public void onTrackingStarted() { - mTracking = true; - } - - public void onTrackingStopped(boolean expand) { - mTracking = false; - } - - /** An interface that will be notified of panel state changes. */ - public interface PanelStateChangeListener { - /** Called when the state changes. */ - void onStateChanged(@PanelState int state); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 481401b3eb7b..249f9886253c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -23,7 +23,7 @@ import android.view.MotionEvent; import android.widget.FrameLayout; public abstract class PanelView extends FrameLayout { - public static final boolean DEBUG = PanelBar.DEBUG; + public static final boolean DEBUG = false; public static final String TAG = PanelView.class.getSimpleName(); private PanelViewController.TouchHandler mTouchHandler; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index b508ddfdafba..2823d985102f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -67,7 +67,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; public abstract class PanelViewController { - public static final boolean DEBUG = PanelBar.DEBUG; + public static final boolean DEBUG = PanelView.DEBUG; public static final String TAG = PanelView.class.getSimpleName(); private static final int NO_FIXED_DURATION = -1; private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L; @@ -153,8 +153,6 @@ public abstract class PanelViewController { private boolean mAnimateAfterExpanding; private boolean mIsFlinging; - PanelBar mBar; - private String mViewName; private float mInitialTouchY; private float mInitialTouchX; @@ -294,10 +292,6 @@ public abstract class PanelViewController { : mTouchSlop; } - protected TouchHandler getTouchHandler() { - return mTouchHandler; - } - private void addMovement(MotionEvent event) { // Add movement to velocity tracker using raw screen X and Y coordinates instead // of window coordinates because the window frame may be moving at the same time. @@ -462,7 +456,6 @@ public abstract class PanelViewController { protected void onTrackingStopped(boolean expand) { mTracking = false; - mBar.onTrackingStopped(expand); mStatusBar.onTrackingStopped(expand); updatePanelExpansionAndVisibility(); } @@ -470,7 +463,6 @@ public abstract class PanelViewController { protected void onTrackingStarted() { endClosing(); mTracking = true; - mBar.onTrackingStarted(); mStatusBar.onTrackingStarted(); notifyExpandingStarted(); updatePanelExpansionAndVisibility(); @@ -848,10 +840,6 @@ public abstract class PanelViewController { return mTracking; } - public void setBar(PanelBar panelBar) { - mBar = panelBar; - } - public void collapse(boolean delayed, float speedUpFactor) { if (DEBUG) logf("collapse: " + this); if (canPanelBeCollapsed()) { @@ -1089,12 +1077,9 @@ public abstract class PanelViewController { * {@link #updateVisibility()}? That would allow us to make this method private. */ public void updatePanelExpansionAndVisibility() { - if (mBar != null) { - mBar.panelExpansionChanged(mExpandedFraction, isExpanded()); - } - updateVisibility(); mPanelExpansionStateManager.onPanelExpansionChanged( mExpandedFraction, isExpanded(), mTracking); + updateVisibility(); } public boolean isExpanded() { @@ -1172,17 +1157,7 @@ public abstract class PanelViewController { return new OnConfigurationChangedListener(); } - public abstract class TouchHandler implements View.OnTouchListener { - /** - * Method called when a touch has occurred on {@link PhoneStatusBarView}. - * - * Touches that occur on the status bar view may have ramifications for the notification - * panel (e.g. a touch that pulls down the shade could start on the status bar), so we need - * to notify the panel controller when these touches occur. - * - * Returns true if the event was handled and false otherwise. - */ - public abstract boolean onTouchForwardedFromStatusBar(MotionEvent event); + public class TouchHandler implements View.OnTouchListener { public boolean onInterceptTouchEvent(MotionEvent event) { if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted @@ -1453,4 +1428,8 @@ public abstract class PanelViewController { protected float getExpansionFraction() { return mExpandedFraction; } + + protected PanelExpansionStateManager getPanelExpansionStateManager() { + return mPanelExpansionStateManager; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 41312243be34..1a0b55a747c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -16,23 +16,21 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; -import android.graphics.Point; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; import android.view.DisplayCutout; -import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; +import android.widget.FrameLayout; import android.widget.LinearLayout; import com.android.internal.policy.SystemBarUtils; @@ -40,20 +38,14 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; -import com.android.systemui.statusbar.window.StatusBarWindowView; import com.android.systemui.util.leak.RotationUtils; import java.util.Objects; -public class PhoneStatusBarView extends PanelBar { +public class PhoneStatusBarView extends FrameLayout { private static final String TAG = "PhoneStatusBarView"; - private static final boolean DEBUG = StatusBar.DEBUG; - private static final boolean DEBUG_GESTURES = false; private final StatusBarContentInsetsProvider mContentInsetsProvider; - StatusBar mBar; - - private ScrimController mScrimController; private DarkReceiver mBattery; private DarkReceiver mClock; private int mRotationOrientation = -1; @@ -77,18 +69,10 @@ public class PhoneStatusBarView extends PanelBar { mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class); } - public void setBar(StatusBar bar) { - mBar = bar; - } - void setTouchEventHandler(TouchEventHandler handler) { mTouchEventHandler = handler; } - public void setScrimController(ScrimController scrimController) { - mScrimController = scrimController; - } - @Override public void onFinishInflate() { mBattery = findViewById(R.id.battery); @@ -175,7 +159,6 @@ public class PhoneStatusBarView extends PanelBar { @Override public boolean onTouchEvent(MotionEvent event) { - mBar.onTouchEvent(event); if (mTouchEventHandler == null) { Log.w( TAG, @@ -192,7 +175,7 @@ public class PhoneStatusBarView extends PanelBar { @Override public boolean onInterceptTouchEvent(MotionEvent event) { - mBar.onTouchEvent(event); + mTouchEventHandler.onInterceptTouchEvent(event); return super.onInterceptTouchEvent(event); } @@ -232,17 +215,18 @@ public class PhoneStatusBarView extends PanelBar { private void updateLayoutForCutout() { updateStatusBarHeight(); - updateCutoutLocation(StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay())); + updateCutoutLocation(); updateSafeInsets(); } - private void updateCutoutLocation(Pair<Integer, Integer> cornerCutoutMargins) { + private void updateCutoutLocation() { // Not all layouts have a cutout (e.g., Car) if (mCutoutSpace == null) { return; } - if (mDisplayCutout == null || mDisplayCutout.isEmpty() || cornerCutoutMargins != null) { + boolean hasCornerCutout = mContentInsetsProvider.currentRotationHasCornerCutout(); + if (mDisplayCutout == null || mDisplayCutout.isEmpty() || hasCornerCutout) { mCenterIconSpace.setVisibility(View.VISIBLE); mCutoutSpace.setVisibility(View.GONE); return; @@ -252,8 +236,7 @@ public class PhoneStatusBarView extends PanelBar { mCutoutSpace.setVisibility(View.VISIBLE); LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mCutoutSpace.getLayoutParams(); - Rect bounds = new Rect(); - boundsFromDirection(mDisplayCutout, Gravity.TOP, bounds); + Rect bounds = mDisplayCutout.getBoundingRectTop(); bounds.left = bounds.left + mCutoutSideNudge; bounds.right = bounds.right - mCutoutSideNudge; @@ -262,27 +245,37 @@ public class PhoneStatusBarView extends PanelBar { } private void updateSafeInsets() { - Rect contentRect = mContentInsetsProvider - .getStatusBarContentInsetsForRotation(RotationUtils.getExactRotation(getContext())); - - Point size = new Point(); - getDisplay().getRealSize(size); + Pair<Integer, Integer> insets = mContentInsetsProvider + .getStatusBarContentInsetsForCurrentRotation(); setPadding( - contentRect.left, + insets.first, getPaddingTop(), - size.x - contentRect.right, + insets.second, getPaddingBottom()); } /** - * A handler repsonsible for all touch event handling on the status bar. + * A handler responsible for all touch event handling on the status bar. + * + * Touches that occur on the status bar view may have ramifications for the notification + * panel (e.g. a touch that pulls down the shade could start on the status bar), so this + * interface provides a way to notify the panel controller when these touches occur. * - * The handler will be notified each time {@link this#onTouchEvent} is called, and the return - * value from the handler will be returned from {@link this#onTouchEvent}. + * The handler will be notified each time {@link PhoneStatusBarView#onTouchEvent} and + * {@link PhoneStatusBarView#onInterceptTouchEvent} are called. **/ public interface TouchEventHandler { - /** Called each time {@link this#onTouchEvent} is called. */ + /** Called each time {@link PhoneStatusBarView#onInterceptTouchEvent} is called. */ + void onInterceptTouchEvent(MotionEvent event); + + /** + * Called each time {@link PhoneStatusBarView#onTouchEvent} is called. + * + * Should return true if the touch was handled by this handler and false otherwise. The + * return value from the handler will be returned from + * {@link PhoneStatusBarView#onTouchEvent}. + */ boolean handleTouchEvent(MotionEvent event); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index cef0613c3f9a..1077347eab0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -271,7 +271,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump ScrimController.this.onThemeChanged(); } }); - panelExpansionStateManager.addListener( + panelExpansionStateManager.addExpansionListener( (fraction, expanded, tracking) -> setRawPanelExpansionFraction(fraction) ); 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 73b9b3831163..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; @@ -335,12 +334,17 @@ public class StatusBar extends SystemUI implements } private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; - private boolean mCallingFadingAwayAfterReveal; private StatusBarCommandQueueCallbacks mCommandQueueCallbacks; void setWindowState(int state) { mStatusBarWindowState = state; mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN; + mStatusBarHideIconsForBouncerManager.setStatusBarWindowHidden(mStatusBarWindowHidden); + if (getStatusBarView() != null) { + // Should #updateHideIconsForBouncer always be called, regardless of whether we have a + // status bar view? If so, we can make #updateHideIconsForBouncer private. + mStatusBarHideIconsForBouncerManager.updateHideIconsForBouncer(/* animate= */ false); + } } void acquireGestureWakeLock(long time) { @@ -360,14 +364,6 @@ public class StatusBar extends SystemUI implements return mStatusBarMode; } - boolean getWereIconsJustHidden() { - return mWereIconsJustHidden; - } - - void setWereIconsJustHidden(boolean justHidden) { - mWereIconsJustHidden = justHidden; - } - void resendMessage(int msg) { mMessageRouter.cancelMessages(msg); mMessageRouter.sendMessage(msg); @@ -510,6 +506,7 @@ public class StatusBar extends SystemUI implements private final SystemStatusAnimationScheduler mAnimationScheduler; private final StatusBarLocationPublisher mStatusBarLocationPublisher; private final StatusBarIconController mStatusBarIconController; + private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; // expanded notifications // the sliding/resizing panel within the notification window @@ -642,10 +639,7 @@ public class StatusBar extends SystemUI implements private int mLastLoggedStateFingerprint; private boolean mTopHidesStatusBar; private boolean mStatusBarWindowHidden; - private boolean mHideIconsForBouncer; private boolean mIsOccluded; - private boolean mWereIconsJustHidden; - private boolean mBouncerWasShowingWhenHidden; private boolean mIsLaunchingActivityOverLockscreen; private final UserSwitcherController mUserSwitcherController; @@ -778,6 +772,7 @@ public class StatusBar extends SystemUI implements SystemStatusAnimationScheduler animationScheduler, StatusBarLocationPublisher locationPublisher, StatusBarIconController statusBarIconController, + StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, LockscreenShadeTransitionController lockscreenShadeTransitionController, FeatureFlags featureFlags, KeyguardUnlockAnimationController keyguardUnlockAnimationController, @@ -874,6 +869,7 @@ public class StatusBar extends SystemUI implements mAnimationScheduler = animationScheduler; mStatusBarLocationPublisher = locationPublisher; mStatusBarIconController = statusBarIconController; + mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mFeatureFlags = featureFlags; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; mMainHandler = mainHandler; @@ -887,7 +883,7 @@ public class StatusBar extends SystemUI implements mStartingSurfaceOptional = startingSurfaceOptional; lockscreenShadeTransitionController.setStatusbar(this); - mPanelExpansionStateManager.addListener(this::onPanelExpansionChanged); + mPanelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged); mBubbleExpandListener = (isExpanding, key) -> mContext.getMainExecutor().execute(() -> { @@ -939,6 +935,7 @@ public class StatusBar extends SystemUI implements mDisplay = mContext.getDisplay(); mDisplayId = mDisplay.getDisplayId(); updateDisplaySize(); + mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId); // start old BaseStatusBar.start(). mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); @@ -1128,7 +1125,7 @@ public class StatusBar extends SystemUI implements mNotificationLogger.setUpWithContainer(notifListContainer); mNotificationIconAreaController.setupShelf(mNotificationShelfController); - mPanelExpansionStateManager.addListener(mWakeUpCoordinator); + mPanelExpansionStateManager.addExpansionListener(mWakeUpCoordinator); mUserSwitcherController.init(mNotificationShadeWindowView); @@ -1142,12 +1139,6 @@ public class StatusBar extends SystemUI implements PhoneStatusBarView oldStatusBarView = mStatusBarView; mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView(); - mStatusBarView.setBar(this); - mStatusBarView.setPanelStateChangeListener( - mNotificationPanelViewController.getPanelStateChangeListener()); - mStatusBarView.setScrimController(mScrimController); - - mNotificationPanelViewController.setBar(mStatusBarView); mPhoneStatusBarViewController = mPhoneStatusBarViewControllerFactory .create(mStatusBarView, mNotificationPanelViewController @@ -1200,9 +1191,11 @@ public class StatusBar extends SystemUI implements mAnimationScheduler, mStatusBarLocationPublisher, mNotificationIconAreaController, + mPanelExpansionStateManager, mFeatureFlags, () -> Optional.of(this), mStatusBarIconController, + mStatusBarHideIconsForBouncerManager, mKeyguardStateController, mNetworkController, mStatusBarStateController, @@ -1877,7 +1870,7 @@ public class StatusBar extends SystemUI implements mNotificationLogger.onPanelExpandedChanged(isExpanded); } mPanelExpanded = isExpanded; - updateHideIconsForBouncer(false /* animate */); + mStatusBarHideIconsForBouncerManager.setPanelExpandedAndTriggerUpdate(isExpanded); mNotificationShadeWindowController.setPanelExpanded(isExpanded); mStatusBarStateController.setPanelExpanded(isExpanded); if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) { @@ -1921,46 +1914,8 @@ public class StatusBar extends SystemUI implements public void setOccluded(boolean occluded) { mIsOccluded = occluded; + mStatusBarHideIconsForBouncerManager.setIsOccludedAndTriggerUpdate(occluded); mScrimController.setKeyguardOccluded(occluded); - updateHideIconsForBouncer(false /* animate */); - } - - public boolean hideStatusBarIconsForBouncer() { - return mHideIconsForBouncer || mWereIconsJustHidden; - } - - /** - * Decides if the status bar (clock + notifications + signal cluster) should be visible - * or not when showing the bouncer. - * - * We want to hide it when: - * • User swipes up on the keyguard - * • Locked activity that doesn't show a status bar requests the bouncer - * - * @param animate should the change of the icons be animated. - */ - void updateHideIconsForBouncer(boolean animate) { - boolean hideBecauseApp = mTopHidesStatusBar && mIsOccluded - && (mStatusBarWindowHidden || mBouncerShowing); - boolean hideBecauseKeyguard = !mPanelExpanded && !mIsOccluded && mBouncerShowing; - boolean shouldHideIconsForBouncer = hideBecauseApp || hideBecauseKeyguard; - if (mHideIconsForBouncer != shouldHideIconsForBouncer) { - mHideIconsForBouncer = shouldHideIconsForBouncer; - if (!shouldHideIconsForBouncer && mBouncerWasShowingWhenHidden) { - // We're delaying the showing, since most of the time the fullscreen app will - // hide the icons again and we don't want them to fade in and out immediately again. - mWereIconsJustHidden = true; - mMainExecutor.executeDelayed(() -> { - mWereIconsJustHidden = false; - mCommandQueue.recomputeDisableFlags(mDisplayId, true); - }, 500); - } else { - mCommandQueue.recomputeDisableFlags(mDisplayId, animate); - } - } - if (shouldHideIconsForBouncer) { - mBouncerWasShowingWhenHidden = mBouncerShowing; - } } public boolean headsUpShouldBeVisible() { @@ -2430,6 +2385,8 @@ public class StatusBar extends SystemUI implements if (mLightRevealScrim != null) { pw.println( + "mLightRevealScrim.getRevealEffect(): " + mLightRevealScrim.getRevealEffect()); + pw.println( "mLightRevealScrim.getRevealAmount(): " + mLightRevealScrim.getRevealAmount()); } @@ -3100,20 +3057,8 @@ public class StatusBar extends SystemUI implements public void fadeKeyguardWhilePulsing() { mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING, ()-> { - Runnable finishFading = () -> { - mCallingFadingAwayAfterReveal = false; - hideKeyguard(); - mStatusBarKeyguardViewManager.onKeyguardFadedAway(); - }; - if (mLightRevealScrim.getRevealAmount() != 1.0f) { - mCallingFadingAwayAfterReveal = true; - // We're still revealing the Light reveal, let's only go to keyguard once - // that has finished and nothing moves anymore. - // Going there introduces lots of jank - mLightRevealScrim.setFullyRevealedRunnable(finishFading); - } else { - finishFading.run(); - } + hideKeyguard(); + mStatusBarKeyguardViewManager.onKeyguardFadedAway(); }).start(); } @@ -3427,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()); } } @@ -3517,7 +3469,7 @@ public class StatusBar extends SystemUI implements mKeyguardBypassController.setBouncerShowing(bouncerShowing); mPulseExpansionHandler.setBouncerShowing(bouncerShowing); setBouncerShowingForStatusBarComponents(bouncerShowing); - updateHideIconsForBouncer(true /* animate */); + mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing); mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */); updateScrimController(); if (!mBouncerShowing) { @@ -4260,7 +4212,7 @@ public class StatusBar extends SystemUI implements + "mStatusBarKeyguardViewManager was null"); return; } - if (mKeyguardStateController.isKeyguardFadingAway() && !mCallingFadingAwayAfterReveal) { + if (mKeyguardStateController.isKeyguardFadingAway()) { mStatusBarKeyguardViewManager.onKeyguardFadedAway(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java index bb1daa252cdf..a77a097f0453 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java @@ -95,6 +95,7 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { private final SysuiStatusBarStateController mStatusBarStateController; private final NotificationShadeWindowView mNotificationShadeWindowView; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; + private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; private final PowerManager mPowerManager; private final VibratorHelper mVibratorHelper; private final Optional<Vibrator> mVibratorOptional; @@ -132,6 +133,7 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { SysuiStatusBarStateController statusBarStateController, NotificationShadeWindowView notificationShadeWindowView, NotificationStackScrollLayoutController notificationStackScrollLayoutController, + StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, PowerManager powerManager, VibratorHelper vibratorHelper, Optional<Vibrator> vibratorOptional, @@ -158,6 +160,7 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { mStatusBarStateController = statusBarStateController; mNotificationShadeWindowView = notificationShadeWindowView; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; + mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mPowerManager = powerManager; mVibratorHelper = vibratorHelper; mVibratorOptional = vibratorOptional; @@ -509,14 +512,8 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { @Override public void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) { - mStatusBar.setTopHidesStatusBar(topAppHidesStatusBar); - if (!topAppHidesStatusBar && mStatusBar.getWereIconsJustHidden()) { - // Immediately update the icon hidden state, since that should only apply if we're - // staying fullscreen. - mStatusBar.setWereIconsJustHidden(false); - mCommandQueue.recomputeDisableFlags(mDisplayId, true); - } - mStatusBar.updateHideIconsForBouncer(true /* animate */); + mStatusBarHideIconsForBouncerManager + .setTopAppHidesStatusBarAndTriggerUpdate(topAppHidesStatusBar); } @Override @@ -534,13 +531,11 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { if (StatusBar.DEBUG_WINDOW_STATE) { Log.d(StatusBar.TAG, "Status bar " + windowStateToString(state)); } - if (mStatusBar.getStatusBarView() != null) { - if (!showing && mStatusBarStateController.getState() == StatusBarState.SHADE) { + if (mStatusBar.getStatusBarView() != null + && !showing + && mStatusBarStateController.getState() == StatusBarState.SHADE) { mNotificationPanelViewController.collapsePanel( false /* animate */, false /* delayed */, 1.0f /* speedUpFactor */); - } - - mStatusBar.updateHideIconsForBouncer(false /* animate */); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt index 6ae8331dfde5..b7988bcc6f45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt @@ -18,13 +18,14 @@ package com.android.systemui.statusbar.phone import android.content.Context import android.content.res.Resources +import android.graphics.Point import android.graphics.Rect import android.util.LruCache import android.util.Pair import android.view.DisplayCutout -import android.view.View.LAYOUT_DIRECTION_RTL -import android.view.WindowMetrics + import androidx.annotation.VisibleForTesting + import com.android.internal.policy.SystemBarUtils import com.android.systemui.Dumpable import com.android.systemui.R @@ -32,16 +33,18 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.util.leak.RotationUtils import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN import com.android.systemui.util.leak.RotationUtils.Rotation +import com.android.systemui.util.leak.RotationUtils.getExactRotation import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation + import java.io.FileDescriptor import java.io.PrintWriter import java.lang.Math.max + import javax.inject.Inject /** @@ -112,48 +115,118 @@ class StatusBarContentInsetsProvider @Inject constructor( } /** + * Some views may need to care about whether or not the current top display cutout is located + * in the corner rather than somewhere in the center. In the case of a corner cutout, the + * status bar area is contiguous. + */ + fun currentRotationHasCornerCutout(): Boolean { + val cutout = context.display.cutout ?: return false + val topBounds = cutout.boundingRectTop + + val point = Point() + context.display.getRealSize(point) + + return topBounds.left <= 0 || topBounds.right >= point.y + } + + /** * Calculates the maximum bounding rectangle for the privacy chip animation + ongoing privacy * dot in the coordinates relative to the given rotation. + * + * @param rotation the rotation for which the bounds are required. This is an absolute value + * (i.e., ROTATION_NONE will always return the same bounds regardless of the context + * from which this method is called) */ fun getBoundingRectForPrivacyChipForRotation(@Rotation rotation: Int): Rect { var insets = insetsCache[getCacheKey(rotation = rotation)] - val rotatedResources = getResourcesForRotation(rotation, context) if (insets == null) { - insets = getStatusBarContentInsetsForRotation(rotation, rotatedResources) + insets = getStatusBarContentAreaForRotation(rotation) } + val rotatedResources = getResourcesForRotation(rotation, context) + val dotWidth = rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_dot_diameter) val chipWidth = rotatedResources.getDimensionPixelSize( R.dimen.ongoing_appops_chip_max_width) - val isRtl = context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL + val isRtl = configurationController.isLayoutRtl return getPrivacyChipBoundingRectForInsets(insets, dotWidth, chipWidth, isRtl) } /** - * Calculates the necessary left and right locations for the status bar contents invariant of - * the current device rotation, in the target rotation's coordinates + * Calculate the distance from the left and right edges of the screen to the status bar + * content area. This differs from the content area rects in that these values can be used + * directly as padding. + * + * @param rotation the target rotation for which to calculate insets + */ + fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> { + val key = getCacheKey(rotation) + + val point = Point() + context.display.getRealSize(point) + // Target rotation can be a different orientation than the current device rotation + point.orientToRotZero(getExactRotation(context)) + val width = point.logicalWidth(rotation) + + val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation( + rotation, getResourcesForRotation(rotation, context), key) + + return Pair(area.left, width - area.right) + } + + /** + * Calculate the left and right insets for the status bar content in the device's current + * rotation + * @see getStatusBarContentAreaForRotation + */ + fun getStatusBarContentInsetsForCurrentRotation(): Pair<Int, Int> { + return getStatusBarContentInsetsForRotation(getExactRotation(context)) + } + + /** + * Calculates the area of the status bar contents invariant of the current device rotation, + * in the target rotation's coordinates + * + * @param rotation the rotation for which the bounds are required. This is an absolute value + * (i.e., ROTATION_NONE will always return the same bounds regardless of the context + * from which this method is called) */ @JvmOverloads - fun getStatusBarContentInsetsForRotation( - @Rotation rotation: Int, - rotatedResources: Resources = getResourcesForRotation(rotation, context) + fun getStatusBarContentAreaForRotation( + @Rotation rotation: Int + ): Rect { + val key = getCacheKey(rotation) + return insetsCache[key] ?: getAndSetCalculatedAreaForRotation( + rotation, getResourcesForRotation(rotation, context), key) + } + + /** + * Get the status bar content area for the given rotation, in absolute bounds + */ + fun getStatusBarContentAreaForCurrentRotation(): Rect { + val rotation = getExactRotation(context) + return getStatusBarContentAreaForRotation(rotation) + } + + private fun getAndSetCalculatedAreaForRotation( + @Rotation targetRotation: Int, + rotatedResources: Resources, + key: CacheKey ): Rect { - val key = getCacheKey(rotation = rotation) - return insetsCache[key] ?: getCalculatedInsetsForRotation(rotation, rotatedResources) - .also { - insetsCache.put(key, it) - } + return getCalculatedAreaForRotation(targetRotation, rotatedResources) + .also { + insetsCache.put(key, it) + } } - private fun getCalculatedInsetsForRotation( + private fun getCalculatedAreaForRotation( @Rotation targetRotation: Int, rotatedResources: Resources ): Rect { val dc = context.display.cutout - val currentRotation = RotationUtils.getExactRotation(context) + val currentRotation = getExactRotation(context) - val isRtl = rotatedResources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL val roundedCornerPadding = rotatedResources .getDimensionPixelSize(R.dimen.rounded_corner_content_padding) val minDotPadding = if (isPrivacyDotEnabled) @@ -165,7 +238,7 @@ class StatusBarContentInsetsProvider @Inject constructor( val minLeft: Int val minRight: Int - if (isRtl) { + if (configurationController.isLayoutRtl) { minLeft = max(minDotPadding, roundedCornerPadding) minRight = roundedCornerPadding } else { @@ -178,10 +251,10 @@ class StatusBarContentInsetsProvider @Inject constructor( targetRotation, dc, context.resources.configuration.windowConfiguration.maxBounds, - SystemBarUtils.getStatusBarHeight(context), + SystemBarUtils.getStatusBarHeightForRotation(context, targetRotation), minLeft, minRight, - isRtl, + configurationController.isLayoutRtl, dotWidth) } @@ -252,7 +325,7 @@ fun getPrivacyChipBoundingRectForInsets( * @param currentRotation current device rotation * @param targetRotation rotation for which to calculate the status bar content rect * @param displayCutout [DisplayCutout] for the current display. possibly null - * @param windowMetrics [WindowMetrics] for the current window + * @param maxBounds the display bounds in our current rotation * @param statusBarHeight height of the status bar for the target rotation * @param minLeft the minimum padding to enforce on the left * @param minRight the minimum padding to enforce on the right @@ -464,3 +537,22 @@ private fun Rect.logicalWidth(@Rotation rot: Int): Int { private fun Int.isHorizontal(): Boolean { return this == ROTATION_LANDSCAPE || this == ROTATION_SEASCAPE } + +private fun Point.orientToRotZero(@Rotation rot: Int) { + when (rot) { + ROTATION_NONE, ROTATION_UPSIDE_DOWN -> return + else -> { + // swap width and height to zero-orient bounds + val yTmp = y + y = x + x = yTmp + } + } +} + +private fun Point.logicalWidth(@Rotation rot: Int): Int { + return when (rot) { + ROTATION_NONE, ROTATION_UPSIDE_DOWN -> x + else -> y + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt new file mode 100644 index 000000000000..d2181d0480d2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt @@ -0,0 +1,133 @@ +package com.android.systemui.statusbar.phone + +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.statusbar.CommandQueue +import com.android.systemui.util.concurrency.DelayableExecutor +import java.io.FileDescriptor +import java.io.PrintWriter +import javax.inject.Inject + +/** + * A class that manages if the status bar (clock + notifications + signal cluster) should be visible + * or not when showing the bouncer. + * + * We want to hide it when: + * • User swipes up on the keyguard + * • Locked activity that doesn't show a status bar requests the bouncer. + * + * [getShouldHideStatusBarIconsForBouncer] is the main exported method for this class. The other + * methods set state variables that are used in the calculation or manually trigger an update. + */ +@SysUISingleton +class StatusBarHideIconsForBouncerManager @Inject constructor( + private val commandQueue: CommandQueue, + @Main private val mainExecutor: DelayableExecutor, + dumpManager: DumpManager +) : Dumpable { + // State variables set by external classes. + private var panelExpanded: Boolean = false + private var isOccluded: Boolean = false + private var bouncerShowing: Boolean = false + private var topAppHidesStatusBar: Boolean = false + private var statusBarWindowHidden: Boolean = false + private var displayId: Int = 0 + + // State variables calculated internally. + private var hideIconsForBouncer: Boolean = false + private var bouncerWasShowingWhenHidden: Boolean = false + private var wereIconsJustHidden: Boolean = false + + init { + dumpManager.registerDumpable(this) + } + + /** Returns true if the status bar icons should be hidden in the bouncer. */ + fun getShouldHideStatusBarIconsForBouncer(): Boolean { + return hideIconsForBouncer || wereIconsJustHidden + } + + fun setStatusBarWindowHidden(statusBarWindowHidden: Boolean) { + this.statusBarWindowHidden = statusBarWindowHidden + } + + fun setDisplayId(displayId: Int) { + this.displayId = displayId + } + + fun setPanelExpandedAndTriggerUpdate(panelExpanded: Boolean) { + this.panelExpanded = panelExpanded + updateHideIconsForBouncer(animate = false) + } + + fun setIsOccludedAndTriggerUpdate(isOccluded: Boolean) { + this.isOccluded = isOccluded + updateHideIconsForBouncer(animate = false) + } + + fun setBouncerShowingAndTriggerUpdate(bouncerShowing: Boolean) { + this.bouncerShowing = bouncerShowing + updateHideIconsForBouncer(animate = true) + } + + fun setTopAppHidesStatusBarAndTriggerUpdate(topAppHidesStatusBar: Boolean) { + this.topAppHidesStatusBar = topAppHidesStatusBar + if (!topAppHidesStatusBar && wereIconsJustHidden) { + // Immediately update the icon hidden state, since that should only apply if we're + // staying fullscreen. + wereIconsJustHidden = false + commandQueue.recomputeDisableFlags(displayId, /* animate= */ true) + } + updateHideIconsForBouncer(animate = true) + } + + /** + * Updates whether the status bar icons should be hidden in the bouncer. May trigger + * [commandQueue.recomputeDisableFlags] if the icon visibility status changes. + */ + fun updateHideIconsForBouncer(animate: Boolean) { + val hideBecauseApp = + topAppHidesStatusBar && + isOccluded && + (statusBarWindowHidden || bouncerShowing) + val hideBecauseKeyguard = !panelExpanded && !isOccluded && bouncerShowing + val shouldHideIconsForBouncer = hideBecauseApp || hideBecauseKeyguard + if (hideIconsForBouncer != shouldHideIconsForBouncer) { + hideIconsForBouncer = shouldHideIconsForBouncer + if (!shouldHideIconsForBouncer && bouncerWasShowingWhenHidden) { + // We're delaying the showing, since most of the time the fullscreen app will + // hide the icons again and we don't want them to fade in and out immediately again. + wereIconsJustHidden = true + mainExecutor.executeDelayed( + { + wereIconsJustHidden = false + commandQueue.recomputeDisableFlags(displayId, true) + }, + 500 + ) + } else { + commandQueue.recomputeDisableFlags(displayId, animate) + } + } + if (shouldHideIconsForBouncer) { + bouncerWasShowingWhenHidden = bouncerShowing + } + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + pw.println("---- State variables set externally ----") + pw.println("panelExpanded=$panelExpanded") + pw.println("isOccluded=$isOccluded") + pw.println("bouncerShowing=$bouncerShowing") + pw.println("topAppHideStatusBar=$topAppHidesStatusBar") + pw.println("statusBarWindowHidden=$statusBarWindowHidden") + pw.println("displayId=$displayId") + + pw.println("---- State variables calculated internally ----") + pw.println("hideIconsForBouncer=$hideIconsForBouncer") + pw.println("bouncerWasShowingWhenHidden=$bouncerWasShowingWhenHidden") + pw.println("wereIconsJustHidden=$wereIconsJustHidden") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 30e668ac4431..58d28813df0c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -279,7 +279,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback); mNotificationPanelViewController = notificationPanelViewController; if (panelExpansionStateManager != null) { - panelExpansionStateManager.addListener(this); + panelExpansionStateManager.addExpansionListener(this); } mBypassController = bypassController; mNotificationContainer = notificationContainer; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java index fbd9ef7e3707..9c69f51332a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import android.annotation.NonNull; import android.content.Context; import android.os.Handler; import android.telephony.SubscriptionInfo; @@ -26,11 +27,11 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.statusbar.connectivity.IconState; +import com.android.systemui.statusbar.connectivity.MobileDataIndicators; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.IconState; -import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators; -import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators; -import com.android.systemui.statusbar.connectivity.NetworkControllerImpl; +import com.android.systemui.statusbar.connectivity.SignalCallback; +import com.android.systemui.statusbar.connectivity.WifiIndicators; import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -44,7 +45,7 @@ import javax.inject.Inject; /** Controls the signal policies for icons shown in the StatusBar. **/ @SysUISingleton -public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallback, +public class StatusBarSignalPolicy implements SignalCallback, SecurityController.SecurityControllerCallback, Tunable { private static final String TAG = "StatusBarSignalPolicy"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -169,7 +170,7 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba } @Override - public void setWifiIndicators(WifiIndicators indicators) { + public void setWifiIndicators(@NonNull WifiIndicators indicators) { if (DEBUG) { Log.d(TAG, "setWifiIndicators: " + indicators); } @@ -219,7 +220,7 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba } @Override - public void setCallIndicator(IconState statusIcon, int subId) { + public void setCallIndicator(@NonNull IconState statusIcon, int subId) { if (DEBUG) { Log.d(TAG, "setCallIndicator: " + "statusIcon = " + statusIcon + "," @@ -247,7 +248,7 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba } @Override - public void setMobileDataIndicators(MobileDataIndicators indicators) { + public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) { if (DEBUG) { Log.d(TAG, "setMobileDataIndicators: " + indicators); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 5a3cb6f569a6..3259f6b8027b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -92,6 +92,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; @@ -224,6 +225,7 @@ public interface StatusBarPhoneModule { SystemStatusAnimationScheduler animationScheduler, StatusBarLocationPublisher locationPublisher, StatusBarIconController statusBarIconController, + StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, LockscreenShadeTransitionController transitionController, FeatureFlags featureFlags, KeyguardUnlockAnimationController keyguardUnlockAnimationController, @@ -322,6 +324,7 @@ public interface StatusBarPhoneModule { animationScheduler, locationPublisher, statusBarIconController, + statusBarHideIconsForBouncerManager, transitionController, featureFlags, keyguardUnlockAnimationController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt index aa748b00d570..2c7c8e113fc5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone.panelstate +import android.annotation.IntDef +import android.util.Log import androidx.annotation.FloatRange import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject @@ -23,42 +25,136 @@ import javax.inject.Inject /** * A class responsible for managing the notification panel's current state. * - * TODO(b/200063118): Move [PanelBar.panelExpansionChanged] logic to this class and make this class - * the one source of truth for the state of panel expansion. + * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion. */ @SysUISingleton class PanelExpansionStateManager @Inject constructor() { - private val listeners: MutableList<PanelExpansionListener> = mutableListOf() + private val expansionListeners = mutableListOf<PanelExpansionListener>() + private val stateListeners = mutableListOf<PanelStateListener>() + @PanelState private var state: Int = STATE_CLOSED @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f private var expanded: Boolean = false private var tracking: Boolean = false /** - * Adds a listener that will be notified when the panel expansion has changed. + * Adds a listener that will be notified when the panel expansion fraction has changed. * * Listener will also be immediately notified with the current values. */ - fun addListener(listener: PanelExpansionListener) { - listeners.add(listener) + fun addExpansionListener(listener: PanelExpansionListener) { + expansionListeners.add(listener) listener.onPanelExpansionChanged(fraction, expanded, tracking) } - /** Removes a listener. */ - fun removeListener(listener: PanelExpansionListener) { - listeners.remove(listener) + /** Removes an expansion listener. */ + fun removeExpansionListener(listener: PanelExpansionListener) { + expansionListeners.remove(listener) } - /** Called when the panel expansion has changed. Notifies all listeners of change. */ + /** Adds a listener that will be notified when the panel state has changed. */ + fun addStateListener(listener: PanelStateListener) { + stateListeners.add(listener) + } + + /** Removes a state listener. */ + fun removeStateListener(listener: PanelStateListener) { + stateListeners.remove(listener) + } + + /** Returns true if the panel is currently closed and false otherwise. */ + fun isClosed(): Boolean = state == STATE_CLOSED + + /** + * Called when the panel expansion has changed. + * + * @param fraction the fraction from the expansion in [0, 1] + * @param expanded whether the panel is currently expanded; this is independent from the + * fraction as the panel also might be expanded if the fraction is 0. + * @param tracking whether we're currently tracking the user's gesture. + */ fun onPanelExpansionChanged( @FloatRange(from = 0.0, to = 1.0) fraction: Float, expanded: Boolean, tracking: Boolean ) { + require(!fraction.isNaN()) { "fraction cannot be NaN" } + val oldState = state + this.fraction = fraction this.expanded = expanded this.tracking = tracking - listeners.forEach { it.onPanelExpansionChanged(fraction, expanded, tracking) } + + var fullyClosed = true + var fullyOpened = false + + if (expanded) { + if (this.state == STATE_CLOSED) { + updateStateInternal(STATE_OPENING) + } + fullyClosed = false + fullyOpened = fraction >= 1f + } + + if (fullyOpened && !tracking) { + updateStateInternal(STATE_OPEN) + } else if (fullyClosed && !tracking && this.state != STATE_CLOSED) { + updateStateInternal(STATE_CLOSED) + } + + debugLog( + "panelExpansionChanged:" + + "start state=${oldState.stateToString()} " + + "end state=${state.stateToString()} " + + "f=$fraction " + + "expanded=$expanded " + + "tracking=$tracking" + + "${if (fullyOpened) " fullyOpened" else ""} " + + if (fullyClosed) " fullyClosed" else "" + ) + + expansionListeners.forEach { it.onPanelExpansionChanged(fraction, expanded, tracking) } + } + + /** Updates the panel state if necessary. */ + fun updateState(@PanelState state: Int) { + debugLog("update state: ${this.state.stateToString()} -> ${state.stateToString()}") + if (this.state != state) { + updateStateInternal(state) + } + } + + private fun updateStateInternal(@PanelState state: Int) { + debugLog("go state: ${this.state.stateToString()} -> ${state.stateToString()}") + this.state = state + stateListeners.forEach { it.onPanelStateChanged(state) } + } + + private fun debugLog(msg: String) { + if (!DEBUG) return + Log.v(TAG, msg) } } + +/** Enum for the current state of the panel. */ +@Retention(AnnotationRetention.SOURCE) +@IntDef(value = [STATE_CLOSED, STATE_OPENING, STATE_OPEN]) +internal annotation class PanelState + +const val STATE_CLOSED = 0 +const val STATE_OPENING = 1 +const val STATE_OPEN = 2 + +@PanelState +private fun Int.stateToString(): String { + return when (this) { + STATE_CLOSED -> "CLOSED" + STATE_OPENING -> "OPENING" + STATE_OPEN -> "OPEN" + else -> this.toString() + } +} + +private const val DEBUG = false +private val TAG = PanelExpansionStateManager::class.simpleName diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt new file mode 100644 index 000000000000..e29959290355 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone.panelstate + +/** A listener interface to be notified of state change events for the notification panel. */ +interface PanelStateListener { + /** Called when the panel's expansion state has changed. */ + fun onPanelStateChanged(@PanelState state: Int) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 92908790770a..b6a96a7e49b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -23,6 +23,7 @@ import com.android.internal.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.connectivity.AccessPointControllerImpl; import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.NetworkControllerImpl; @@ -135,7 +136,7 @@ public interface StatusBarPolicyModule { /** */ @Binds - NetworkController.AccessPointController provideAccessPointController( + AccessPointController provideAccessPointController( AccessPointControllerImpl accessPointControllerImpl); /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java index 8b11e4c1b352..06cc96e2e0cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java @@ -21,26 +21,15 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowInsets.Type.systemBars; -import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; - import android.content.Context; import android.graphics.Insets; -import android.graphics.Point; -import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Pair; -import android.view.Display; import android.view.DisplayCutout; -import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.WindowInsets; import android.widget.FrameLayout; -import androidx.annotation.NonNull; - -import com.android.systemui.util.leak.RotationUtils; - /** * Status bar view. */ @@ -111,86 +100,4 @@ public class StatusBarWindowView extends FrameLayout { } } } - - /** - * Compute the padding needed for status bar related views, e.g., PhoneStatusBar, - * QuickStatusBarHeader and KeyguardStatusBarView). - * - * @param cutout - * @param cornerCutoutPadding - * @param roundedCornerContentPadding - * @return - */ - @NonNull - public static Pair<Integer, Integer> paddingNeededForCutoutAndRoundedCorner( - DisplayCutout cutout, Pair<Integer, Integer> cornerCutoutPadding, - int roundedCornerContentPadding) { - if (cutout == null) { - return new Pair<>(roundedCornerContentPadding, roundedCornerContentPadding); - } - - // padding needed for corner cutout. - int leftCornerCutoutPadding = cutout.getSafeInsetLeft(); - int rightCornerCutoutPadding = cutout.getSafeInsetRight(); - if (cornerCutoutPadding != null) { - leftCornerCutoutPadding = Math.max(leftCornerCutoutPadding, cornerCutoutPadding.first); - rightCornerCutoutPadding = Math.max(rightCornerCutoutPadding, - cornerCutoutPadding.second); - } - - return new Pair<>( - Math.max(leftCornerCutoutPadding, roundedCornerContentPadding), - Math.max(rightCornerCutoutPadding, roundedCornerContentPadding)); - } - - - /** - * Compute the corner cutout margins in portrait mode - */ - public static Pair<Integer, Integer> cornerCutoutMargins(DisplayCutout cutout, - Display display) { - return statusBarCornerCutoutMargins(cutout, display, RotationUtils.ROTATION_NONE, 0); - } - - /** - * Compute the corner cutout margins in the given orientation (exactRotation) - */ - public static Pair<Integer, Integer> statusBarCornerCutoutMargins(DisplayCutout cutout, - Display display, int exactRotation, int statusBarHeight) { - if (cutout == null) { - return null; - } - Point size = new Point(); - display.getRealSize(size); - - Rect bounds = new Rect(); - switch (exactRotation) { - case RotationUtils.ROTATION_LANDSCAPE: - boundsFromDirection(cutout, Gravity.LEFT, bounds); - break; - case RotationUtils.ROTATION_SEASCAPE: - boundsFromDirection(cutout, Gravity.RIGHT, bounds); - break; - case RotationUtils.ROTATION_NONE: - boundsFromDirection(cutout, Gravity.TOP, bounds); - break; - case RotationUtils.ROTATION_UPSIDE_DOWN: - // we assume the cutout is always on top in portrait mode - return null; - } - - if (statusBarHeight >= 0 && bounds.top > statusBarHeight) { - return null; - } - - if (bounds.left <= 0) { - return new Pair<>(bounds.right, 0); - } - - if (bounds.right >= size.x) { - return new Pair<>(0, size.x - bounds.left); - } - - return null; - } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt index cc2c2083cbab..65106f1df93c 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt @@ -41,7 +41,9 @@ annotation class SysUIUnfoldScope * no objects will get constructed if these parameters are empty. */ @Module(subcomponents = [SysUIUnfoldComponent::class]) -object SysUIUnfoldModule { +class SysUIUnfoldModule { + constructor() {} + @Provides @SysUISingleton fun provideSysUIUnfoldComponent( 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 e53b450a895e..ff5960bc33ce 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -87,7 +87,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -170,8 +169,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private TelephonyListenerManager mTelephonyListenerManager; @Mock - private FeatureFlags mFeatureFlags; - @Mock private InteractionJankMonitor mInteractionJankMonitor; @Mock private LatencyTracker mLatencyTracker; @@ -179,6 +176,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor; // Direct executor private Executor mBackgroundExecutor = Runnable::run; + private Executor mMainExecutor = Runnable::run; private TestableLooper mTestableLooper; private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor; private TestableContext mSpiedContext; @@ -474,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()); } @@ -882,6 +881,25 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void testRegisterAuthControllerCallback() { + assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isFalse(); + + // verify AuthController.Callback is added: + ArgumentCaptor<AuthController.Callback> captor = ArgumentCaptor.forClass( + AuthController.Callback.class); + verify(mAuthController).addCallback(captor.capture()); + AuthController.Callback callback = captor.getValue(); + + // WHEN udfps is now enrolled + when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true); + callback.onEnrollmentsChanged(); + + // THEN isUdfspEnrolled is TRUE + assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isTrue(); + } + + + @Test public void testStartUdfpsServiceBeginsOnKeyguard() { // GIVEN // - status bar state is on the keyguard @@ -1060,9 +1078,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { super(context, TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(), mBroadcastDispatcher, mDumpManager, - mRingerModeTracker, mBackgroundExecutor, + mRingerModeTracker, mBackgroundExecutor, mMainExecutor, mStatusBarStateController, mLockPatternUtils, - mAuthController, mTelephonyListenerManager, mFeatureFlags, + mAuthController, mTelephonyListenerManager, mInteractionJankMonitor, mLatencyTracker); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } 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 54278066b5d3..209df6b54f8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -60,9 +60,20 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { assertEquals(0, dialog.findViewById<ViewGroup>(android.R.id.content).childCount) assertEquals(1, hostDialogContent.childCount) + // The original dialog content is added to another view that is the same size as the + // original dialog window. val hostDialogRoot = hostDialogContent.getChildAt(0) as ViewGroup assertEquals(1, hostDialogRoot.childCount) - assertEquals(dialog.contentView, hostDialogRoot.getChildAt(0)) + + val dialogContentParent = hostDialogRoot.getChildAt(0) as ViewGroup + assertEquals(1, dialogContentParent.childCount) + assertEquals(TestDialog.DIALOG_WIDTH, dialogContentParent.layoutParams.width) + assertEquals(TestDialog.DIALOG_HEIGHT, dialogContentParent.layoutParams.height) + + val dialogContent = dialogContentParent.getChildAt(0) + assertEquals(dialog.contentView, dialogContent) + assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, dialogContent.layoutParams.width) + assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, dialogContent.layoutParams.height) // Hiding/showing/dismissing the dialog should hide/show/dismiss the host dialog given that // it's a ListenableDialog. @@ -126,6 +137,11 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { } private class TestDialog(context: Context) : Dialog(context), ListenableDialog { + companion object { + const val DIALOG_WIDTH = 100 + const val DIALOG_HEIGHT = 200 + } + private val listeners = hashSetOf<DialogListener>() val contentView = View(context) var onStartCalled = false @@ -138,6 +154,7 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT) setContentView(contentView) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt index c98a504b8784..2d510923b942 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt @@ -18,8 +18,12 @@ package com.android.systemui.biometrics import android.animation.Animator import android.graphics.Insets +import android.app.ActivityManager +import android.app.ActivityTaskManager +import android.content.ComponentName import android.graphics.Rect import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN import android.hardware.biometrics.SensorProperties import android.hardware.display.DisplayManager @@ -60,9 +64,11 @@ import org.mockito.Mockito.`when` import org.mockito.Mockito.any import org.mockito.Mockito.anyFloat import org.mockito.Mockito.anyLong +import org.mockito.Mockito.anyInt import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @@ -84,6 +90,8 @@ class SidefpsControllerTest : SysuiTestCase() { @Mock lateinit var windowManager: WindowManager @Mock + lateinit var activityTaskManager: ActivityTaskManager + @Mock lateinit var sidefpsView: View @Mock lateinit var displayManager: DisplayManager @@ -144,7 +152,8 @@ class SidefpsControllerTest : SysuiTestCase() { sideFpsController = SidefpsController( context.createDisplayContext(display), layoutInflater, fingerprintManager, - windowManager, overviewProxyService, displayManager, executor, handler + windowManager, activityTaskManager, overviewProxyService, displayManager, executor, + handler ) overlayController = ArgumentCaptor.forClass(ISidefpsController::class.java).apply { @@ -211,12 +220,23 @@ class SidefpsControllerTest : SysuiTestCase() { testIgnoredFor(REASON_AUTH_KEYGUARD) } - private fun testIgnoredFor(reason: Int) { - overlayController.show(SENSOR_ID, reason) + @Test + fun testShowsForMostSettings() = testWithDisplay { + `when`(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpEnrollTask())) + testIgnoredFor(REASON_AUTH_SETTINGS, ignored = false) + } + + @Test + fun testIgnoredForVerySpecificSettings() = testWithDisplay { + `when`(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpSettingsTask())) + testIgnoredFor(REASON_AUTH_SETTINGS) + } + private fun testIgnoredFor(reason: Int, ignored: Boolean = true) { + overlayController.show(SENSOR_ID, reason) executor.runAllReady() - verify(windowManager, never()).addView(any(), any()) + verify(windowManager, if (ignored) never() else times(1)).addView(any(), any()) } @Test @@ -267,4 +287,9 @@ private fun insetsForSmallNavbar() = insetsWithBottom(60) private fun insetsForLargeNavbar() = insetsWithBottom(100) private fun insetsWithBottom(bottom: Int) = WindowInsets.Builder() .setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, bottom)) - .build()
\ No newline at end of file + .build() +private fun fpEnrollTask() = settingsTask(".biometrics.fingerprint.FingerprintEnrollEnrolling") +private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings") +private fun settingsTask(cls: String) = ActivityManager.RunningTaskInfo().apply { + topActivity = ComponentName.createRelative("com.android.settings", cls) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index cfac9cb592a0..d90eb73a1595 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -405,15 +405,75 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void showUdfpsOverlay_addsViewToWindow() throws RemoteException { - mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + public void showUdfpsOverlay_addsViewToWindow_bp() throws RemoteException { + showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_BP); + } + + @Test + public void showUdfpsOverlay_addsViewToWindow_keyguard() throws RemoteException { + showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_KEYGUARD); + } + + @Test + public void showUdfpsOverlay_addsViewToWindow_settings() throws RemoteException { + showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_SETTINGS); + } + + @Test + public void showUdfpsOverlay_addsViewToWindow_enroll_locate() throws RemoteException { + showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR); + } + + @Test + public void showUdfpsOverlay_addsViewToWindow_enroll() throws RemoteException { + showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_ENROLL_ENROLLING); + } + + @Test + public void showUdfpsOverlay_addsViewToWindow_other() throws RemoteException { + showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_OTHER); + } + + private void showUdfpsOverlay_addsViewToWindow( + @BiometricOverlayConstants.ShowReason int reason) throws RemoteException { + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, reason, + mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mWindowManager).addView(eq(mUdfpsView), any()); } @Test - public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException { + public void hideUdfpsOverlay_removesViewFromWindow_bp() throws RemoteException { + hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_BP); + } + + @Test + public void hideUdfpsOverlay_removesViewFromWindow_keyguard() throws RemoteException { + hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_KEYGUARD); + } + + @Test + public void hideUdfpsOverlay_removesViewFromWindow_settings() throws RemoteException { + hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_SETTINGS); + } + + @Test + public void hideUdfpsOverlay_removesViewFromWindow_enroll_locate() throws RemoteException { + hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR); + } + + @Test + public void hideUdfpsOverlay_removesViewFromWindow_enroll() throws RemoteException { + hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_ENROLL_ENROLLING); + } + + @Test + public void hideUdfpsOverlay_removesViewFromWindow_other() throws RemoteException { + hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_OTHER); + } + + private void hideUdfpsOverlay_removesViewFromWindow( + @BiometricOverlayConstants.ShowReason int reason) throws RemoteException { mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index 6f0456ef8f5b..0e86964147d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -171,7 +171,7 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { verify(mStatusBarStateController).removeCallback(mStatusBarStateListener); for (PanelExpansionListener listener : mExpansionListeners) { - verify(mPanelExpansionStateManager).removeListener(listener); + verify(mPanelExpansionStateManager).removeExpansionListener(listener); } verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback); } @@ -435,7 +435,7 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { private void captureExpansionListeners() { verify(mPanelExpansionStateManager, times(2)) - .addListener(mExpansionListenerCaptor.capture()); + .addExpansionListener(mExpansionListenerCaptor.capture()); // first (index=0) is from super class, UdfpsAnimationViewController. // second (index=1) is from UdfpsKeyguardViewController mExpansionListeners = mExpansionListenerCaptor.getAllValues(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt index ee1cc7b1ab71..890b9aec69bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt @@ -154,6 +154,28 @@ class ControlsRequestReceiverTest : SysuiTestCase() { assertNull(wrapper.intent) } + @Test + fun testClassCastExceptionComponentName_noCrash() { + val badIntent = Intent(ControlsProviderService.ACTION_ADD_CONTROL).apply { + putExtra(Intent.EXTRA_COMPONENT_NAME, Intent()) + putExtra(ControlsProviderService.EXTRA_CONTROL, control) + } + receiver.onReceive(wrapper, badIntent) + + assertNull(wrapper.intent) + } + + @Test + fun testClassCastExceptionControl_noCrash() { + val badIntent = Intent(ControlsProviderService.ACTION_ADD_CONTROL).apply { + putExtra(Intent.EXTRA_COMPONENT_NAME, componentName) + putExtra(ControlsProviderService.EXTRA_CONTROL, Intent()) + } + receiver.onReceive(wrapper, badIntent) + + assertNull(wrapper.intent) + } + class MyWrapper(context: Context) : ContextWrapper(context) { var intent: Intent? = null 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/FeatureFlagReaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java deleted file mode 100644 index fc6f3fd1d9c6..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java +++ /dev/null @@ -1,148 +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 org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.res.Resources; - -import androidx.annotation.BoolRes; -import androidx.test.filters.SmallTest; - -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.util.wrapper.BuildInfo; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -public class FeatureFlagReaderTest extends SysuiTestCase { - @Mock private Resources mResources; - @Mock private BuildInfo mBuildInfo; - @Mock private DumpManager mDumpManager; - @Mock private SystemPropertiesHelper mSystemPropertiesHelper; - @Mock private FlagReader mFlagReader; - - private FeatureFlagReader mReader; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - when(mSystemPropertiesHelper.getBoolean(anyString(), anyBoolean())) - .thenAnswer(invocation -> invocation.getArgument(1)); - - defineFlag(FLAG_RESID_0, false); - defineFlag(FLAG_RESID_1, true); - - initialize(true, true); - } - - private void initialize(boolean isDebuggable, boolean isOverrideable) { - when(mBuildInfo.isDebuggable()).thenReturn(isDebuggable); - when(mResources.getBoolean(R.bool.are_flags_overrideable)).thenReturn(isOverrideable); - mReader = new FeatureFlagReader( - mResources, mBuildInfo, mDumpManager, mSystemPropertiesHelper, mFlagReader); - } - - @Test - public void testCantOverrideIfNotDebuggable() { - // GIVEN that the build is not debuggable - initialize(false, true); - - // GIVEN that a flag has been overridden to true - overrideFlag(FLAG_RESID_0, true); - - // THEN the flag is still false - assertFalse(mReader.isEnabled(FLAG_RESID_0)); - } - - @Test - public void testCantOverrideIfNotOverrideable() { - // GIVEN that flags are not overrideable - initialize(true, false); - - // GIVEN that a flag has been overridden to true - overrideFlag(FLAG_RESID_0, true); - - // THEN the flag is still false - assertFalse(mReader.isEnabled(FLAG_RESID_0)); - } - - @Test - public void testReadFlags() { - assertFalse(mReader.isEnabled(FLAG_RESID_0)); - assertTrue(mReader.isEnabled(FLAG_RESID_1)); - } - - @Test - public void testOverrideFlags() { - // GIVEN that flags are overridden - overrideFlag(FLAG_RESID_0, true); - overrideFlag(FLAG_RESID_1, false); - - // THEN the reader returns the overridden values - assertTrue(mReader.isEnabled(FLAG_RESID_0)); - assertFalse(mReader.isEnabled(FLAG_RESID_1)); - } - - @Test - public void testThatFlagReadsAreCached() { - // GIVEN that a flag is overridden - overrideFlag(FLAG_RESID_0, true); - - // WHEN the flag is queried many times - mReader.isEnabled(FLAG_RESID_0); - mReader.isEnabled(FLAG_RESID_0); - mReader.isEnabled(FLAG_RESID_0); - mReader.isEnabled(FLAG_RESID_0); - - // THEN the underlying resource and override are only queried once - verify(mResources, times(1)).getBoolean(FLAG_RESID_0); - verify(mSystemPropertiesHelper, times(1)) - .getBoolean(fakeStorageKey(FLAG_RESID_0), false); - } - - private void defineFlag(int resId, boolean value) { - when(mResources.getBoolean(resId)).thenReturn(value); - when(mResources.getResourceEntryName(resId)).thenReturn(fakeStorageKey(resId)); - } - - private void overrideFlag(int resId, boolean value) { - when(mSystemPropertiesHelper.getBoolean(eq(fakeStorageKey(resId)), anyBoolean())) - .thenReturn(value); - } - - private String fakeStorageKey(@BoolRes int resId) { - return "persist.systemui.flag_testname_" + resId; - } - - private static final int FLAG_RESID_0 = 47; - private static final int FLAG_RESID_1 = 48; -} 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 a850f70ae318..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java +++ /dev/null @@ -1,108 +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.Mockito.verify; - -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; - -@SmallTest -public class FeatureFlagsTest extends SysuiTestCase { - - @Mock FeatureFlagReader mFeatureFlagReader; - - private FeatureFlags mFeatureFlags; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - - mFeatureFlags = new FeatureFlags(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(); - - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index bf5a6e4086f3..bbeadf62ae0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -122,7 +122,7 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var session: MediaSession private val device = MediaDeviceData(true, null, DEVICE_NAME) - private val disabledDevice = MediaDeviceData(false, null, null) + private val disabledDevice = MediaDeviceData(false, null, "Disabled Device") @JvmField @Rule val mockito = MockitoJUnit.rule() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index ab3b20898b23..7dadbad8025f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -56,6 +56,7 @@ private const val PACKAGE = "PKG" private const val SESSION_KEY = "SESSION_KEY" private const val SESSION_TITLE = "SESSION_TITLE" private const val DEVICE_NAME = "DEVICE_NAME" +private const val REMOTE_DEVICE_NAME = "REMOTE_DEVICE_NAME" private const val USER_ID = 0 private fun <T> eq(value: T): T = Mockito.eq(value) ?: value @@ -195,8 +196,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { // THEN the device should be disabled val data = captureDeviceData(KEY) assertThat(data.enabled).isFalse() - assertThat(data.name).isNull() - assertThat(data.icon).isNull() } @Test @@ -263,6 +262,20 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test + fun deviceNameFromMR2RouteInfo() { + // GIVEN that MR2Manager returns a valid routing session + whenever(route.name).thenReturn(REMOTE_DEVICE_NAME) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN it uses the route name (instead of device name) + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) + } + + @Test fun deviceDisabledWhenMR2ReturnsNullRouteInfo() { // GIVEN that MR2Manager returns null for routing session whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) @@ -273,8 +286,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { // THEN the device is disabled val data = captureDeviceData(KEY) assertThat(data.enabled).isFalse() - assertThat(data.name).isNull() - assertThat(data.icon).isNull() } @Test @@ -294,8 +305,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { // THEN the device is disabled val data = captureDeviceData(KEY) assertThat(data.enabled).isFalse() - assertThat(data.name).isNull() - assertThat(data.icon).isNull() } @Test @@ -315,8 +324,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { // THEN the device is disabled val data = captureDeviceData(KEY) assertThat(data.enabled).isFalse() - assertThat(data.name).isNull() - assertThat(data.icon).isNull() } @Test 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/qs/QSSquishinessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt index 3059aa1ae658..f41d7b127a9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt @@ -4,13 +4,10 @@ import android.testing.AndroidTestingRunner import android.view.ViewGroup import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.plugins.qs.QSTile -import com.android.systemui.qs.tileimpl.QSTileViewImpl import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.never @@ -21,14 +18,13 @@ import org.mockito.junit.MockitoJUnit @SmallTest class QSSquishinessControllerTest : SysuiTestCase() { - @Mock private lateinit var qsTileHost: QSTileHost @Mock private lateinit var qqsFooterActionsView: FooterActionsView @Mock private lateinit var qqsFooterActionsViewLP: ViewGroup.MarginLayoutParams @Mock private lateinit var qsAnimator: QSAnimator + @Mock private lateinit var qsPanelController: QSPanelController @Mock private lateinit var quickQsPanelController: QuickQSPanelController - @Mock private lateinit var qstileView: QSTileViewImpl - @Mock private lateinit var qstile: QSTile @Mock private lateinit var tileLayout: TileLayout + @Mock private lateinit var pagedTileLayout: PagedTileLayout @JvmField @Rule val mockitoRule = MockitoJUnit.rule() @@ -36,11 +32,10 @@ class QSSquishinessControllerTest : SysuiTestCase() { @Before fun setup() { - qsSquishinessController = QSSquishinessController(qsTileHost, qqsFooterActionsView, - qsAnimator, quickQsPanelController) - `when`(qsTileHost.tiles).thenReturn(mutableListOf(qstile)) - `when`(quickQsPanelController.getTileView(any())).thenReturn(qstileView) + qsSquishinessController = QSSquishinessController(qqsFooterActionsView, qsAnimator, + qsPanelController, quickQsPanelController) `when`(quickQsPanelController.tileLayout).thenReturn(tileLayout) + `when`(qsPanelController.tileLayout).thenReturn(pagedTileLayout) `when`(qqsFooterActionsView.layoutParams).thenReturn(qqsFooterActionsViewLP) } @@ -56,7 +51,7 @@ class QSSquishinessControllerTest : SysuiTestCase() { @Test fun setSquishiness_updatesTiles() { qsSquishinessController.squishiness = 0.5f - verify(qstileView).squishinessFraction = 0.5f verify(tileLayout).setSquishinessFraction(0.5f) + verify(pagedTileLayout).setSquishinessFraction(0.5f) } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index f32ac849b000..f85167e6aa63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.qs.carrier.QSCarrierGroup import com.android.systemui.qs.carrier.QSCarrierGroupController +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.policy.Clock @@ -106,6 +107,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { private lateinit var context: Context @Mock private lateinit var featureFlags: FeatureFlags + @Mock + private lateinit var insetsProvider: StatusBarContentInsetsProvider private val qsExpansionPathInterpolator = QSExpansionPathInterpolator() @@ -149,7 +152,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { qsExpansionPathInterpolator, batteryMeterViewController, featureFlags, - variableDateViewControllerFactory + variableDateViewControllerFactory, + insetsProvider ) } @@ -248,7 +252,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { controller.init() val captor = argumentCaptor<List<String>>() - verify(view).onAttach(any(), any(), capture(captor), anyBoolean()) + verify(view).onAttach(any(), any(), capture(captor), anyBoolean(), any()) assertThat(captor.value).containsExactly( mContext.getString(com.android.internal.R.string.status_bar_mobile) @@ -261,7 +265,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { controller.init() val captor = argumentCaptor<List<String>>() - verify(view).onAttach(any(), any(), capture(captor), anyBoolean()) + verify(view).onAttach(any(), any(), capture(captor), anyBoolean(), any()) assertThat(captor.value).containsExactly( mContext.getString(com.android.internal.R.string.status_bar_no_calling), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java index 8ae7100e2e60..bd794d6813ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java @@ -39,8 +39,10 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.CarrierTextManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.connectivity.IconState; +import com.android.systemui.statusbar.connectivity.MobileDataIndicators; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators; +import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.util.CarrierConfigTracker; import com.android.systemui.utils.leaks.LeakCheckedTest; import com.android.systemui.utils.os.FakeHandler; @@ -57,7 +59,7 @@ import org.mockito.MockitoAnnotations; public class QSCarrierGroupControllerTest extends LeakCheckedTest { private QSCarrierGroupController mQSCarrierGroupController; - private NetworkController.SignalCallback mSignalCallback; + private SignalCallback mSignalCallback; private CarrierTextManager.CarrierTextCallback mCallback; @Mock private QSCarrierGroup mQSCarrierGroup; @@ -94,7 +96,7 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { when(mNetworkController.hasVoiceCallingFeature()).thenReturn(true); doAnswer(invocation -> mSignalCallback = invocation.getArgument(0)) .when(mNetworkController) - .addCallback(any(NetworkController.SignalCallback.class)); + .addCallback(any(SignalCallback.class)); when(mCarrierTextControllerBuilder.setShowAirplaneMode(anyBoolean())) .thenReturn(mCarrierTextControllerBuilder); @@ -230,8 +232,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mSlotIndexResolver.overrideInvalid = true; MobileDataIndicators indicators = new MobileDataIndicators( - mock(NetworkController.IconState.class), - mock(NetworkController.IconState.class), + mock(IconState.class), + mock(IconState.class), 0, 0, true, true, "", "", "", 0, true, true); mSignalCallback.setMobileDataIndicators(indicators); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index f0bd06571eb9..5a49337fe640 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -43,8 +43,10 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.statusbar.connectivity.IconState; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators; +import com.android.systemui.statusbar.connectivity.SignalCallback; +import com.android.systemui.statusbar.connectivity.WifiIndicators; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.HotspotController; @@ -77,7 +79,7 @@ public class CastTileTest extends SysuiTestCase { @Mock private QSTileHost mHost; @Mock - NetworkController.SignalCallback mSignalCallback; + SignalCallback mSignalCallback; @Mock private MetricsLogger mMetricsLogger; @Mock @@ -122,8 +124,8 @@ public class CastTileTest extends SysuiTestCase { mTestableLooper.processAllMessages(); mCastTile.handleSetListening(true); - ArgumentCaptor<NetworkController.SignalCallback> signalCallbackArgumentCaptor = - ArgumentCaptor.forClass(NetworkController.SignalCallback.class); + ArgumentCaptor<SignalCallback> signalCallbackArgumentCaptor = + ArgumentCaptor.forClass(SignalCallback.class); verify(mNetworkController).observe(any(LifecycleOwner.class), signalCallbackArgumentCaptor.capture()); mSignalCallback = signalCallbackArgumentCaptor.getValue(); @@ -139,10 +141,9 @@ public class CastTileTest extends SysuiTestCase { // All these tests for enabled/disabled wifi have hotspot not enabled @Test public void testStateUnavailable_wifiDisabled() { - NetworkController.IconState qsIcon = - new NetworkController.IconState(false, 0, ""); + IconState qsIcon = new IconState(false, 0, ""); WifiIndicators indicators = new WifiIndicators( - false, mock(NetworkController.IconState.class), + false, mock(IconState.class), qsIcon, false,false, "", false, ""); mSignalCallback.setWifiIndicators(indicators); @@ -153,10 +154,9 @@ public class CastTileTest extends SysuiTestCase { @Test public void testStateUnavailable_wifiNotConnected() { - NetworkController.IconState qsIcon = - new NetworkController.IconState(false, 0, ""); + IconState qsIcon = new IconState(false, 0, ""); WifiIndicators indicators = new WifiIndicators( - true, mock(NetworkController.IconState.class), + true, mock(IconState.class), qsIcon, false,false, "", false, ""); mSignalCallback.setWifiIndicators(indicators); @@ -166,10 +166,9 @@ public class CastTileTest extends SysuiTestCase { } private void enableWifiAndProcessMessages() { - NetworkController.IconState qsIcon = - new NetworkController.IconState(true, 0, ""); + IconState qsIcon = new IconState(true, 0, ""); WifiIndicators indicators = new WifiIndicators( - true, mock(NetworkController.IconState.class), + true, mock(IconState.class), qsIcon, false,false, "", false, ""); mSignalCallback.setWifiIndicators(indicators); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index 964ce01312bf..e4c5299a0cc5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -17,9 +17,11 @@ package com.android.systemui.qs.tiles; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -34,6 +36,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -41,10 +44,12 @@ import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; +import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -67,6 +72,10 @@ public class ScreenRecordTileTest extends SysuiTestCase { private ActivityStarter mActivityStarter; @Mock private QSLogger mQSLogger; + @Mock + private KeyguardStateController mKeyguardStateController; + @Mock + private DialogLaunchAnimator mDialogLaunchAnimator; private TestableLooper mTestableLooper; private ScreenRecordTile mTile; @@ -89,7 +98,9 @@ public class ScreenRecordTileTest extends SysuiTestCase { mActivityStarter, mQSLogger, mController, - mKeyguardDismissUtil + mKeyguardDismissUtil, + mKeyguardStateController, + mDialogLaunchAnimator ); mTile.initialize(); @@ -112,7 +123,15 @@ public class ScreenRecordTileTest extends SysuiTestCase { mTile.handleClick(null /* view */); mTestableLooper.processAllMessages(); - verify(mController, times(1)).getPromptIntent(); + + ArgumentCaptor<Runnable> onStartRecordingClicked = ArgumentCaptor.forClass(Runnable.class); + verify(mController).createScreenRecordDialog(any(), onStartRecordingClicked.capture()); + + // When starting the recording, we collapse the shade and disable the dialog animation. + assertNotNull(onStartRecordingClicked.getValue()); + onStartRecordingClicked.getValue().run(); + verify(mDialogLaunchAnimator).disableAllCurrentDialogsExitAnimations(); + verify(mHost).collapsePanels(); } // Test that the tile is active and labeled correctly when the controller is starting diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index eb03b5ff2a6c..ca8903bfe009 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -49,8 +49,7 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController; +import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.toast.SystemUIToast; @@ -104,7 +103,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Mock private KeyguardStateController mKeyguardStateController; @Mock - private NetworkController.AccessPointController mAccessPointController; + private AccessPointController mAccessPointController; @Mock private WifiEntry mConnectedEntry; @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index 5e1fea512d55..b6e8979db189 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -99,7 +99,8 @@ public class InternetDialogTest extends SysuiTestCase { mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler, mBgExecutor); mInternetDialog.mAdapter = mInternetAdapter; - mInternetDialog.onAccessPointsChanged(mWifiEntries, mInternetWifiEntry); + mInternetDialog.mConnectedWifiEntry = mInternetWifiEntry; + mInternetDialog.mWifiEntriesCount = mWifiEntries.size(); mInternetDialog.show(); mDialogView = mInternetDialog.mDialogView; @@ -209,7 +210,7 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndNoConnectedWifi_hideConnectedWifi() { // The precondition WiFi ON is already in setUp() - mInternetDialog.onAccessPointsChanged(mWifiEntries, null /* connectedEntry*/); + mInternetDialog.mConnectedWifiEntry = null; doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); mInternetDialog.updateDialog(false); @@ -220,7 +221,7 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndNoWifiList_hideWifiListAndSeeAll() { // The precondition WiFi ON is already in setUp() - mInternetDialog.onAccessPointsChanged(null /* wifiEntries */, mInternetWifiEntry); + mInternetDialog.mWifiEntriesCount = 0; mInternetDialog.updateDialog(false); @@ -366,7 +367,8 @@ public class InternetDialogTest extends SysuiTestCase { public void showProgressBar_wifiEnabledWithoutWifiEntries_showProgressBarThenHideSearch() { Mockito.reset(mHandler); when(mWifiManager.isWifiEnabled()).thenReturn(true); - mInternetDialog.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry*/); + mInternetDialog.mConnectedWifiEntry = null; + mInternetDialog.mWifiEntriesCount = 0; mInternetDialog.showProgressBar(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index b7cc651dc24b..013e58ed99d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.settings.UserContextProvider; import org.junit.Before; import org.junit.Test; @@ -52,6 +53,8 @@ public class RecordingControllerTest extends SysuiTestCase { private RecordingController.RecordingStateChangeCallback mCallback; @Mock private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private UserContextProvider mUserContextProvider; private RecordingController mController; @@ -60,7 +63,7 @@ public class RecordingControllerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mController = new RecordingController(mBroadcastDispatcher); + mController = new RecordingController(mBroadcastDispatcher, mUserContextProvider); mController.addCallback(mCallback); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt index 7896a26badbe..c57b64db2c5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt @@ -56,9 +56,9 @@ class AccessPointControllerImplTest : SysuiTestCase() { @Mock private lateinit var wifiPickerTracker: WifiPickerTracker @Mock - private lateinit var callback: NetworkController.AccessPointController.AccessPointCallback + private lateinit var callback: AccessPointController.AccessPointCallback @Mock - private lateinit var otherCallback: NetworkController.AccessPointController.AccessPointCallback + private lateinit var otherCallback: AccessPointController.AccessPointCallback @Mock private lateinit var wifiEntryConnected: WifiEntry @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java index 11a53c55c024..2d29c80a15ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java @@ -29,10 +29,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.settingslib.mobile.TelephonyIcons; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener; -import com.android.systemui.statusbar.connectivity.NetworkController.IconState; -import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; -import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators; import com.android.systemui.tests.R; import org.junit.Before; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java index 92a32bce1799..7ddfde370afa 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileStateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java @@ -14,22 +14,26 @@ * limitations under the License. */ -package com.android.settingslib; +package com.android.systemui.statusbar.connectivity; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; + import com.android.settingslib.mobile.TelephonyIcons; +import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -@RunWith(RobolectricTestRunner.class) -public class MobileStateTest { +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class MobileStateTest extends SysuiTestCase { - private SignalIcon.MobileState mState = new SignalIcon.MobileState(); + private final MobileState mState = new MobileState(); @Before public void setUp() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index b23d07a314b4..47a11fcdcee4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -72,9 +72,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.statusbar.connectivity.NetworkController.IconState; -import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.telephony.TelephonyListenerManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java index 675d755ad3e3..f6f939ad2e12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java @@ -23,8 +23,6 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; -import com.android.systemui.statusbar.connectivity.NetworkController.IconState; - import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java index ffeaf207942b..a39971d27303 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java @@ -36,7 +36,6 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import com.android.settingslib.mobile.TelephonyIcons; -import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java index f23f14801484..c300021ac53a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java @@ -38,6 +38,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; +import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.LogcatEchoTracker; @@ -48,7 +49,10 @@ import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Ignore; @@ -259,9 +263,12 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mAnimationScheduler, mLocationPublisher, mMockNotificationAreaController, + new PanelExpansionStateManager(), mock(FeatureFlags.class), () -> Optional.of(mStatusBar), mStatusBarIconController, + new StatusBarHideIconsForBouncerManager( + mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()), mKeyguardStateController, mNetworkController, mStatusBarStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java index 1ce336e5f37d..34c43ef52a00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java @@ -28,6 +28,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import org.junit.Before; import org.junit.Test; @@ -44,12 +45,15 @@ public class DozeScrimControllerTest extends SysuiTestCase { private DozeParameters mDozeParameters; @Mock private DozeLog mDozeLog; + @Mock + private StatusBarStateController mStatusBarStateController; private DozeScrimController mDozeScrimController; @Before public void setup() { MockitoAnnotations.initMocks(this); - mDozeScrimController = new DozeScrimController(mDozeParameters, mDozeLog); + mDozeScrimController = new DozeScrimController(mDozeParameters, mDozeLog, + mStatusBarStateController); mDozeScrimController.setDozing(true); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index faf968b4ff44..8d05e6693e33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -85,6 +85,8 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { private BiometricUnlockController mBiometricUnlockController; @Mock private SysuiStatusBarStateController mStatusBarStateController; + @Mock + private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider; private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider; private KeyguardStatusBarView mKeyguardStatusBarView; @@ -118,7 +120,8 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mKeyguardBypassController, mKeyguardUpdateMonitor, mBiometricUnlockController, - mStatusBarStateController + mStatusBarStateController, + mStatusBarContentInsetsProvider ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 6e1f1487f2e7..01f5654f24ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -176,8 +176,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private HeadsUpTouchHelper.Callback mHeadsUpCallback; @Mock - private PanelBar mPanelBar; - @Mock private KeyguardUpdateMonitor mUpdateMonitor; @Mock private KeyguardBypassController mKeyguardBypassController; @@ -308,7 +306,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private NotificationsQSContainerController mNotificationsQSContainerController; @Mock private FeatureFlags mFeatureFlags; - private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent; + private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); private SysuiStatusBarStateController mStatusBarStateController; private NotificationPanelViewController mNotificationPanelViewController; private View.AccessibilityDelegate mAccessibiltyDelegate; @@ -460,7 +458,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { () -> {}, mNotificationShelfController); mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); - mNotificationPanelViewController.setBar(mPanelBar); mNotificationPanelViewController.setKeyguardIndicationController( mKeyguardIndicationController); ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor = @@ -526,46 +523,50 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void onTouchForwardedFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { + public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { when(mCommandQueue.panelsEnabled()).thenReturn(false); - boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); + boolean returnVal = mNotificationPanelViewController + .getStatusBarTouchEventHandler() + .handleTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); assertThat(returnVal).isFalse(); verify(mView, never()).dispatchTouchEvent(any()); } @Test - public void onTouchForwardedFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() { + public void handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() { when(mCommandQueue.panelsEnabled()).thenReturn(true); when(mView.isEnabled()).thenReturn(false); - boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); + boolean returnVal = mNotificationPanelViewController + .getStatusBarTouchEventHandler() + .handleTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); assertThat(returnVal).isTrue(); verify(mView, never()).dispatchTouchEvent(any()); } @Test - public void onTouchForwardedFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() { + public void handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() { when(mCommandQueue.panelsEnabled()).thenReturn(true); when(mView.isEnabled()).thenReturn(false); MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0); - mTouchHandler.onTouchForwardedFromStatusBar(event); + mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event); verify(mView).dispatchTouchEvent(event); } @Test - public void onTouchForwardedFromStatusBar_panelAndViewEnabled_viewReceivesEvent() { + public void handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() { when(mCommandQueue.panelsEnabled()).thenReturn(true); when(mView.isEnabled()).thenReturn(true); MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0); - mTouchHandler.onTouchForwardedFromStatusBar(event); + mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event); verify(mView).dispatchTouchEvent(event); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index eea8eb91675c..dc320076a668 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -52,8 +52,6 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Mock private lateinit var panelView: ViewGroup @Mock - private lateinit var scrimController: ScrimController - @Mock private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController @Mock private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent @@ -76,8 +74,6 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { val parent = FrameLayout(mContext) // add parent to keep layout params view = LayoutInflater.from(mContext) .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView - view.setScrimController(scrimController) - view.setBar(mock(StatusBar::class.java)) } controller = createController(view) @@ -85,10 +81,13 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Test fun constructor_setsTouchHandlerOnView() { + val interceptEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 10f, 10f, 0) val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + view.onInterceptTouchEvent(interceptEvent) view.onTouchEvent(event) + assertThat(touchEventHandler.lastInterceptEvent).isEqualTo(interceptEvent) assertThat(touchEventHandler.lastEvent).isEqualTo(event) } @@ -128,6 +127,11 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler { var lastEvent: MotionEvent? = null + var lastInterceptEvent: MotionEvent? = null + + override fun onInterceptTouchEvent(event: MotionEvent?) { + lastInterceptEvent = event + } override fun handleTouchEvent(event: MotionEvent?): Boolean { lastEvent = event return false diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt index 300860ca0a49..8d686ae94e79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt @@ -34,10 +34,6 @@ class PhoneStatusBarViewTest : SysuiTestCase() { private lateinit var panelViewController: PanelViewController @Mock private lateinit var panelView: ViewGroup - @Mock - private lateinit var scrimController: ScrimController - @Mock - private lateinit var statusBar: StatusBar private lateinit var view: PhoneStatusBarView @@ -49,53 +45,28 @@ class PhoneStatusBarViewTest : SysuiTestCase() { `when`(panelViewController.view).thenReturn(panelView) view = PhoneStatusBarView(mContext, null) - view.setScrimController(scrimController) - view.setBar(statusBar) - } - - @Test - fun panelStateChanged_toStateOpening_listenerNotified() { - val listener = TestStateChangedListener() - view.setPanelStateChangeListener(listener) - - view.panelExpansionChanged(0.5f, true) - - assertThat(listener.state).isEqualTo(PanelBar.STATE_OPENING) } @Test - fun panelStateChanged_toStateOpen_listenerNotified() { - val listener = TestStateChangedListener() - view.setPanelStateChangeListener(listener) - - view.panelExpansionChanged(1f, true) - - assertThat(listener.state).isEqualTo(PanelBar.STATE_OPEN) - } - - @Test - fun panelStateChanged_toStateClosed_listenerNotified() { - val listener = TestStateChangedListener() - view.setPanelStateChangeListener(listener) - - // First, open the panel - view.panelExpansionChanged(1f, true) + fun onTouchEvent_listenerNotified() { + val handler = TestTouchEventHandler() + view.setTouchEventHandler(handler) - // Then, close it again - view.panelExpansionChanged(0f, false) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + view.onTouchEvent(event) - assertThat(listener.state).isEqualTo(PanelBar.STATE_CLOSED) + assertThat(handler.lastEvent).isEqualTo(event) } @Test - fun onTouchEvent_listenerNotified() { + fun onInterceptTouchEvent_listenerNotified() { val handler = TestTouchEventHandler() view.setTouchEventHandler(handler) val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - view.onTouchEvent(event) + view.onInterceptTouchEvent(event) - assertThat(handler.lastEvent).isEqualTo(event) + assertThat(handler.lastInterceptEvent).isEqualTo(event) } @Test @@ -104,7 +75,7 @@ class PhoneStatusBarViewTest : SysuiTestCase() { view.setTouchEventHandler(handler) val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - handler.returnValue = true + handler.handleTouchReturnValue = true assertThat(view.onTouchEvent(event)).isTrue() } @@ -115,7 +86,7 @@ class PhoneStatusBarViewTest : SysuiTestCase() { view.setTouchEventHandler(handler) val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - handler.returnValue = false + handler.handleTouchReturnValue = false assertThat(view.onTouchEvent(event)).isFalse() } @@ -126,19 +97,18 @@ class PhoneStatusBarViewTest : SysuiTestCase() { // No assert needed, just testing no crash } - private class TestStateChangedListener : PanelBar.PanelStateChangeListener { - var state: Int = 0 - override fun onStateChanged(state: Int) { - this.state = state - } - } - private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler { + var lastInterceptEvent: MotionEvent? = null var lastEvent: MotionEvent? = null - var returnValue: Boolean = false + var handleTouchReturnValue: Boolean = false + + override fun onInterceptTouchEvent(event: MotionEvent?) { + lastInterceptEvent = event + } + override fun handleTouchEvent(event: MotionEvent?): Boolean { lastEvent = event - return returnValue + return handleTouchReturnValue } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java index 8555306bae04..0131293656e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java @@ -36,6 +36,7 @@ import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; +import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger; @@ -45,6 +46,8 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import org.junit.Before; @@ -109,6 +112,8 @@ public class StatusBarCommandQueueCallbacksTest extends SysuiTestCase { mStatusBarStateController, mNotificationShadeWindowView, mNotificationStackScrollLayoutController, + new StatusBarHideIconsForBouncerManager( + mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()), mPowerManager, mVibratorHelper, Optional.of(mVibrator), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt index e5158e74759c..e86676b81f8e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt @@ -467,7 +467,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { screenBounds = Rect(0, 0, 1080, 2160), displayUniqueId = "1" ) - val firstDisplayInsets = provider.getStatusBarContentInsetsForRotation(ROTATION_NONE) + val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE) givenDisplay( screenBounds = Rect(0, 0, 800, 600), displayUniqueId = "2" @@ -475,7 +475,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { configurationController.onConfigurationChanged(configuration) // WHEN: get insets on the second display - val secondDisplayInsets = provider.getStatusBarContentInsetsForRotation(ROTATION_NONE) + val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE) // THEN: insets are updated assertThat(firstDisplayInsets).isNotEqualTo(secondDisplayInsets) @@ -492,13 +492,13 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { displayUniqueId = "1" ) val firstDisplayInsetsFirstCall = provider - .getStatusBarContentInsetsForRotation(ROTATION_NONE) + .getStatusBarContentAreaForRotation(ROTATION_NONE) givenDisplay( screenBounds = Rect(0, 0, 800, 600), displayUniqueId = "2" ) configurationController.onConfigurationChanged(configuration) - provider.getStatusBarContentInsetsForRotation(ROTATION_NONE) + provider.getStatusBarContentAreaForRotation(ROTATION_NONE) givenDisplay( screenBounds = Rect(0, 0, 1080, 2160), displayUniqueId = "1" @@ -507,7 +507,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { // WHEN: get insets on the first display again val firstDisplayInsetsSecondCall = provider - .getStatusBarContentInsetsForRotation(ROTATION_NONE) + .getStatusBarContentAreaForRotation(ROTATION_NONE) // THEN: insets for the first and second calls for the first display are the same assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 7a93d03a5843..bd87a5021f8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -279,6 +279,7 @@ public class StatusBarTest extends SysuiTestCase { private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); private FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock); private InitController mInitController = new InitController(); + private final DumpManager mDumpManager = new DumpManager(); @Before public void setup() throws Exception { @@ -332,7 +333,7 @@ public class StatusBarTest extends SysuiTestCase { }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any()); WakefulnessLifecycle wakefulnessLifecycle = - new WakefulnessLifecycle(mContext, mIWallpaperManager, mock(DumpManager.class)); + new WakefulnessLifecycle(mContext, mIWallpaperManager, mDumpManager); wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); wakefulnessLifecycle.dispatchFinishedWakingUp(); @@ -390,7 +391,7 @@ public class StatusBarTest extends SysuiTestCase { mNetworkController, mBatteryController, mColorExtractor, - new ScreenLifecycle(mock(DumpManager.class)), + new ScreenLifecycle(mDumpManager), wakefulnessLifecycle, mStatusBarStateController, Optional.of(mBubblesManager), @@ -440,6 +441,7 @@ public class StatusBarTest extends SysuiTestCase { mAnimationScheduler, mLocationPublisher, mIconController, + new StatusBarHideIconsForBouncerManager(mCommandQueue, mMainExecutor, mDumpManager), mLockscreenTransitionController, mFeatureFlags, mKeyguardUnlockAnimationController, @@ -450,7 +452,7 @@ public class StatusBarTest extends SysuiTestCase { mUnlockedScreenOffAnimationController, Optional.of(mStartingSurface), mTunerService, - mock(DumpManager.class), + mDumpManager, mActivityLaunchAnimator); when(mKeyguardViewMediator.registerStatusBar( any(StatusBar.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt index e09cde917285..32bad5c084f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt @@ -33,9 +33,9 @@ class PanelExpansionStateManagerTest : SysuiTestCase() { } @Test - fun onPanelExpansionChanged_listenersNotified() { + fun onPanelExpansionChanged_listenerNotified() { val listener = TestPanelExpansionListener() - panelExpansionStateManager.addListener(listener) + panelExpansionStateManager.addExpansionListener(listener) val fraction = 0.6f val expanded = true val tracking = true @@ -48,20 +48,143 @@ class PanelExpansionStateManagerTest : SysuiTestCase() { } @Test - fun addPanelExpansionListener_listenerNotifiedOfCurrentValues() { + fun addExpansionListener_listenerNotifiedOfCurrentValues() { val fraction = 0.6f val expanded = true val tracking = true panelExpansionStateManager.onPanelExpansionChanged(fraction, expanded, tracking) val listener = TestPanelExpansionListener() - panelExpansionStateManager.addListener(listener) + panelExpansionStateManager.addExpansionListener(listener) assertThat(listener.fraction).isEqualTo(fraction) assertThat(listener.expanded).isEqualTo(expanded) assertThat(listener.tracking).isEqualTo(tracking) } + @Test + fun updateState_listenerNotified() { + val listener = TestPanelStateListener() + panelExpansionStateManager.addStateListener(listener) + + panelExpansionStateManager.updateState(STATE_OPEN) + + assertThat(listener.state).isEqualTo(STATE_OPEN) + } + + /* ***** [PanelExpansionStateManager.onPanelExpansionChanged] test cases *******/ + + /* Fraction < 1 test cases */ + + @Test + fun onPEC_fractionLessThanOne_expandedTrue_trackingFalse_becomesStateOpening() { + val listener = TestPanelStateListener() + panelExpansionStateManager.addStateListener(listener) + + panelExpansionStateManager.onPanelExpansionChanged( + fraction = 0.5f, expanded = true, tracking = false + ) + + assertThat(listener.state).isEqualTo(STATE_OPENING) + } + + @Test + fun onPEC_fractionLessThanOne_expandedTrue_trackingTrue_becomesStateOpening() { + val listener = TestPanelStateListener() + panelExpansionStateManager.addStateListener(listener) + + panelExpansionStateManager.onPanelExpansionChanged( + fraction = 0.5f, expanded = true, tracking = true + ) + + assertThat(listener.state).isEqualTo(STATE_OPENING) + } + + @Test + fun onPEC_fractionLessThanOne_expandedFalse_trackingFalse_becomesStateClosed() { + val listener = TestPanelStateListener() + panelExpansionStateManager.addStateListener(listener) + // Start out on a different state + panelExpansionStateManager.updateState(STATE_OPEN) + + panelExpansionStateManager.onPanelExpansionChanged( + fraction = 0.5f, expanded = false, tracking = false + ) + + assertThat(listener.state).isEqualTo(STATE_CLOSED) + } + + @Test + fun onPEC_fractionLessThanOne_expandedFalse_trackingTrue_doesNotBecomeStateClosed() { + val listener = TestPanelStateListener() + panelExpansionStateManager.addStateListener(listener) + // Start out on a different state + panelExpansionStateManager.updateState(STATE_OPEN) + + panelExpansionStateManager.onPanelExpansionChanged( + fraction = 0.5f, expanded = false, tracking = true + ) + + assertThat(listener.state).isEqualTo(STATE_OPEN) + } + + /* Fraction = 1 test cases */ + + @Test + fun onPEC_fractionOne_expandedTrue_trackingFalse_becomesStateOpeningThenStateOpen() { + val listener = TestPanelStateListener() + panelExpansionStateManager.addStateListener(listener) + + panelExpansionStateManager.onPanelExpansionChanged( + fraction = 1f, expanded = true, tracking = false + ) + + assertThat(listener.previousState).isEqualTo(STATE_OPENING) + assertThat(listener.state).isEqualTo(STATE_OPEN) + } + + @Test + fun onPEC_fractionOne_expandedTrue_trackingTrue_becomesStateOpening() { + val listener = TestPanelStateListener() + panelExpansionStateManager.addStateListener(listener) + + panelExpansionStateManager.onPanelExpansionChanged( + fraction = 1f, expanded = true, tracking = true + ) + + assertThat(listener.state).isEqualTo(STATE_OPENING) + } + + @Test + fun onPEC_fractionOne_expandedFalse_trackingFalse_becomesStateClosed() { + val listener = TestPanelStateListener() + panelExpansionStateManager.addStateListener(listener) + // Start out on a different state + panelExpansionStateManager.updateState(STATE_OPEN) + + panelExpansionStateManager.onPanelExpansionChanged( + fraction = 1f, expanded = false, tracking = false + ) + + assertThat(listener.state).isEqualTo(STATE_CLOSED) + } + + @Test + fun onPEC_fractionOne_expandedFalse_trackingTrue_doesNotBecomeStateClosed() { + val listener = TestPanelStateListener() + panelExpansionStateManager.addStateListener(listener) + // Start out on a different state + panelExpansionStateManager.updateState(STATE_OPEN) + + panelExpansionStateManager.onPanelExpansionChanged( + fraction = 1f, expanded = false, tracking = true + ) + + assertThat(listener.state).isEqualTo(STATE_OPEN) + } + + /* ***** end [PanelExpansionStateManager.onPanelExpansionChanged] test cases ******/ + class TestPanelExpansionListener : PanelExpansionListener { var fraction: Float = 0f var expanded: Boolean = false @@ -77,4 +200,14 @@ class PanelExpansionStateManagerTest : SysuiTestCase() { this.tracking = tracking } } + + class TestPanelStateListener : PanelStateListener { + @PanelState var previousState: Int = STATE_CLOSED + @PanelState var state: Int = STATE_CLOSED + + override fun onPanelStateChanged(state: Int) { + this.previousState = this.state + this.state = state + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java index 8ea9da6f4d0e..33ef9cf7a9c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java @@ -18,8 +18,9 @@ import android.os.Bundle; import android.testing.LeakCheck; import com.android.settingslib.net.DataUsageController; +import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback; +import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.policy.DataSaverController; public class FakeNetworkController extends BaseLeakChecker<SignalCallback> 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/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 63301ac49573..ab220b5e42e4 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -47,6 +47,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; import android.telecom.TelecomManager; +import android.telephony.AccessNetworkConstants; import android.telephony.Annotation; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SrvccState; @@ -1999,42 +2000,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { ApnSetting apnSetting = preciseState.getApnSetting(); - int apnTypes = apnSetting.getApnTypeBitmask(); - int state = preciseState.getState(); - int networkType = preciseState.getNetworkType(); - synchronized (mRecords) { if (validatePhoneId(phoneId)) { - // We only call the callback when the change is for default APN type. - if ((ApnSetting.TYPE_DEFAULT & apnTypes) != 0 - && (mDataConnectionState[phoneId] != state - || mDataConnectionNetworkType[phoneId] != networkType)) { - String str = "onDataConnectionStateChanged(" - + TelephonyUtils.dataStateToString(state) - + ", " + getNetworkTypeName(networkType) - + ") subId=" + subId + ", phoneId=" + phoneId; - log(str); - mLocalLog.log(str); - for (Record r : mRecords) { - if (r.matchTelephonyCallbackEvent( - TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED) - && idMatch(r, subId, phoneId)) { - try { - if (DBG) { - log("Notify data connection state changed on sub: " + subId); - } - r.callback.onDataConnectionStateChanged(state, networkType); - } catch (RemoteException ex) { - mRemoveList.add(r.binder); - } - } - } - handleRemoveListLocked(); - - mDataConnectionState[phoneId] = state; - mDataConnectionNetworkType[phoneId] = networkType; - } - Pair<Integer, ApnSetting> key = Pair.create(preciseState.getTransportType(), preciseState.getApnSetting()); PreciseDataConnectionState oldState = mPreciseDataConnectionStates.get(phoneId) @@ -2066,6 +2033,73 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (preciseState.getState() != TelephonyManager.DATA_DISCONNECTED) { mPreciseDataConnectionStates.get(phoneId).put(key, preciseState); } + + // Note that below is just the workaround for reporting the correct data connection + // state. The actual fix should be put in the new data stack in T. + // TODO: Remove the code below in T. + + // Collect all possible candidate data connection state for internet. Key is the + // data connection state, value is the precise data connection state. + Map<Integer, PreciseDataConnectionState> internetConnections = new ArrayMap<>(); + if (preciseState.getState() == TelephonyManager.DATA_DISCONNECTED + && preciseState.getApnSetting().getApnTypes() + .contains(ApnSetting.TYPE_DEFAULT)) { + internetConnections.put(TelephonyManager.DATA_DISCONNECTED, preciseState); + } + for (Map.Entry<Pair<Integer, ApnSetting>, PreciseDataConnectionState> entry : + mPreciseDataConnectionStates.get(phoneId).entrySet()) { + if (entry.getKey().first == AccessNetworkConstants.TRANSPORT_TYPE_WWAN + && entry.getKey().second.getApnTypes() + .contains(ApnSetting.TYPE_DEFAULT)) { + internetConnections.put(entry.getValue().getState(), entry.getValue()); + } + } + + // If any internet data is in connected state, then report connected, then check + // suspended, connecting, disconnecting, and disconnected. The order is very + // important. + int[] statesInPriority = new int[]{TelephonyManager.DATA_CONNECTED, + TelephonyManager.DATA_SUSPENDED, TelephonyManager.DATA_CONNECTING, + TelephonyManager.DATA_DISCONNECTING, + TelephonyManager.DATA_DISCONNECTED}; + int state = TelephonyManager.DATA_DISCONNECTED; + int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN; + for (int s : statesInPriority) { + if (internetConnections.containsKey(s)) { + state = s; + networkType = internetConnections.get(s).getNetworkType(); + break; + } + } + + if (mDataConnectionState[phoneId] != state + || mDataConnectionNetworkType[phoneId] != networkType) { + String str = "onDataConnectionStateChanged(" + + TelephonyUtils.dataStateToString(state) + + ", " + TelephonyManager.getNetworkTypeName(networkType) + + ") subId=" + subId + ", phoneId=" + phoneId; + log(str); + mLocalLog.log(str); + for (Record r : mRecords) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED) + && idMatch(r, subId, phoneId)) { + try { + if (DBG) { + log("Notify data connection state changed on sub: " + subId); + } + r.callback.onDataConnectionStateChanged(state, networkType); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + + mDataConnectionState[phoneId] = state; + mDataConnectionNetworkType[phoneId] = networkType; + + handleRemoveListLocked(); + } } } } 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/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 031f6eeeca5f..61b8ded60db7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -168,6 +168,10 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> return Utils.isKeyguard(getContext(), getOwnerString()); } + private boolean isSettings() { + return Utils.isSettings(getContext(), getOwnerString()); + } + @Override protected boolean isCryptoOperation() { return mOperationId != 0; @@ -499,6 +503,8 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> protected int getShowOverlayReason() { if (isKeyguard()) { return BiometricOverlayConstants.REASON_AUTH_KEYGUARD; + } else if (isSettings()) { + return BiometricOverlayConstants.REASON_AUTH_SETTINGS; } else if (isBiometricPrompt()) { return BiometricOverlayConstants.REASON_AUTH_BP; } else { 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/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 9d2cff9901e2..5ce72c2fd080 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -108,8 +108,9 @@ public class CameraServiceProxy extends SystemService /** * When enabled this change id forces the packages it is applied to override the default - * camera rotate & crop behavior. The default behavior along with all possible override - * combinations is discussed in the table below. + * camera rotate & crop behavior and always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE . + * The default behavior along with all possible override combinations is discussed in the table + * below. */ @ChangeId @Overridable @@ -121,9 +122,7 @@ public class CameraServiceProxy extends SystemService * When enabled this change id forces the packages it is applied to ignore the current value of * 'android:resizeableActivity' as well as target SDK equal to or below M and consider the * activity as non-resizeable. In this case, the value of camera rotate & crop will only depend - * on potential mismatches between the orientation of the camera and the fixed orientation of - * the activity. You can check the table below for further details on the possible override - * combinations. + * on the needed compensation considering the current display rotation. */ @ChangeId @Overridable @@ -132,67 +131,30 @@ public class CameraServiceProxy extends SystemService public static final long OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK = 191513214L; // buganizer id /** - * This change id forces the packages it is applied to override the default camera rotate & crop - * behavior. Enabling it will set the crop & rotate parameter to - * {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_90} and disabling it - * will reset the parameter to - * {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_NONE} as long as camera - * clients include {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_AUTO} - * in their capture requests. - * - * This treatment only takes effect if OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS is also enabled. - * The table below includes further information about the possible override combinations. - */ - @ChangeId - @Overridable - @Disabled - @TestApi - public static final long OVERRIDE_CAMERA_ROTATE_AND_CROP = 190069291L; //buganizer id - - /** * Possible override combinations * - * |OVERRIDE | |OVERRIDE_ - * |CAMERA_ |OVERRIDE |CAMERA_ - * |ROTATE_ |CAMERA_ |RESIZEABLE_ - * |AND_CROP_ |ROTATE_ |AND_SDK_ - * |DEFAULTS |AND_CROP |CHECK - * ______________________________________________ - * Default | | | - * Behavior | D |D |D - * ______________________________________________ - * Ignore | | | - * SDK&Resize | D |D |E - * ______________________________________________ - * Default | | | - * Behavior | D |E |D - * ______________________________________________ - * Ignore | | | - * SDK&Resize | D |E |E - * ______________________________________________ - * Rotate&Crop| | | - * disabled | E |D |D - * ______________________________________________ - * Rotate&Crop| | | - * disabled | E |D |E - * ______________________________________________ - * Rotate&Crop| | | - * enabled | E |E |D - * ______________________________________________ - * Rotate&Crop| | | - * enabled | E |E |E - * ______________________________________________ + * |OVERRIDE |OVERRIDE_ + * |CAMERA_ |CAMERA_ + * |ROTATE_ |RESIZEABLE_ + * |AND_CROP_ |AND_SDK_ + * |DEFAULTS |CHECK + * _________________________________________________ + * Default Behavior | D |D + * _________________________________________________ + * Ignore SDK&Resize | D |E + * _________________________________________________ + * SCALER_ROTATE_AND_CROP_NONE | E |D, E + * _________________________________________________ * Where: - * E -> Override enabled - * D -> Override disabled - * Default behavior -> Rotate&crop will be enabled only in cases - * where the fixed app orientation mismatches - * with the orientation of the camera. - * Additionally the app must either target M (or below) - * or is declared as non-resizeable. - * Ignore SDK&Resize -> Rotate&crop will be enabled only in cases - * where the fixed app orientation mismatches - * with the orientation of the camera. + * E -> Override enabled + * D -> Override disabled + * Default behavior -> Rotate&crop will be calculated depending on the required + * compensation necessary for the current display rotation. + * Additionally the app must either target M (or below) + * or is declared as non-resizeable. + * Ignore SDK&Resize -> The Rotate&crop value will depend on the required + * compensation for the current display rotation. + * SCALER_ROTATE_AND_CROP_NONE -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE */ // Flags arguments to NFC adapter to enable/disable NFC @@ -543,14 +505,8 @@ public class CameraServiceProxy extends SystemService if ((taskInfo != null) && (CompatChanges.isChangeEnabled( OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS, packageName, UserHandle.getUserHandleForUid(taskInfo.userId)))) { - if (CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_ROTATE_AND_CROP, packageName, - UserHandle.getUserHandleForUid(taskInfo.userId))) { - Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP enabled!"); + Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS enabled!"); return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; - } else { - Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP disabled!"); - return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; - } } boolean ignoreResizableAndSdkCheck = false; if ((taskInfo != null) && (CompatChanges.isChangeEnabled( diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 768587a6a2b8..2f3342f20fcb 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -378,6 +378,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private float mInitialAutoBrightness; // The controller for the automatic brightness level. + @Nullable private AutomaticBrightnessController mAutomaticBrightnessController; private Sensor mLightSensor; @@ -608,7 +609,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mPendingRbcOnOrChanged = strengthChanged || justActivated; // Reset model if strength changed OR rbc is turned off - if (strengthChanged || !justActivated && mAutomaticBrightnessController != null) { + if ((strengthChanged || !justActivated) && mAutomaticBrightnessController != null) { mAutomaticBrightnessController.resetShortTermModel(); } } @@ -1567,7 +1568,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call sendUpdatePowerStateLocked(); mHandler.post(mOnBrightnessChangeRunnable); // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. - mAutomaticBrightnessController.update(); + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.update(); + } }, mContext); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 73baf79ea4b1..82b34c35cfd2 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() - .createSystemUiContext(displayId); + .getSystemUiContext(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/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index b477ea353c25..29a5469367cd 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -2128,6 +2128,23 @@ public class MediaSessionService extends SystemService implements Monitor { // Enabled notification listener only works within the same user. return false; } + // Verify whether package name and controller UID. + // It will indirectly check whether the caller has obtained the package name and UID + // via ControllerInfo or with the valid package name visibility. + try { + int actualControllerUid = mContext.getPackageManager().getPackageUidAsUser( + controllerPackageName, + UserHandle.getUserId(controllerUid)); + if (controllerUid != actualControllerUid) { + Log.w(TAG, "Failed to check enabled notification listener. Package name and" + + " UID doesn't match"); + return false; + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Failed to check enabled notification listener. Package name doesn't" + + " exist"); + return false; + } if (mNotificationManager.hasEnabledNotificationListener(controllerPackageName, UserHandle.getUserHandleForUid(controllerUid))) { 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/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index ba64d25178e7..4f190093e0d0 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -896,7 +896,7 @@ public class DomainVerificationService extends SystemService oldPkgState.getUserStates(); int oldUserStatesSize = oldUserStates.size(); if (oldUserStatesSize > 0) { - ArraySet<String> newWebDomains = mCollector.collectValidAutoVerifyDomains(newPkg); + ArraySet<String> newWebDomains = mCollector.collectAllWebDomains(newPkg); for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize; oldUserStatesIndex++) { int userId = oldUserStates.keyAt(oldUserStatesIndex); 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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9133d8403ed6..95a286cf9047 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2724,7 +2724,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (windowingMode == WINDOWING_MODE_PINNED && info.supportsPictureInPicture()) { return false; } - if (WindowConfiguration.inMultiWindowMode(windowingMode) && supportsMultiWindow() + // Activity should be resizable if the task is. + final boolean supportsMultiWindow = task != null + ? task.supportsMultiWindow() || supportsMultiWindow() + : supportsMultiWindow(); + if (WindowConfiguration.inMultiWindowMode(windowingMode) && supportsMultiWindow && !mAtmService.mForceResizableActivities) { // The non resizable app will be letterboxed instead of being forced resizable. return false; @@ -7287,7 +7291,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return false; } } - return !isResizeable() && (info.isFixedOrientation() || hasFixedAspectRatio()) + // Activity should be resizable if the task is. + final boolean isResizeable = task != null + ? task.isResizeable() || isResizeable() + : isResizeable(); + return !isResizeable && (info.isFixedOrientation() || hasFixedAspectRatio()) // The configuration of non-standard type should be enforced by system. // {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} is set when this activity is // added to a task, but this function is called when resolving the launch params, at @@ -7657,7 +7665,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // orientation with insets applied. return; } - if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable()) { + // Activity should be resizable if the task is. + final boolean isResizeable = task != null + ? task.isResizeable() || isResizeable() + : isResizeable(); + if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable) { // Ignore orientation request for resizable apps in multi window. return; } 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 cb376520e000..9ef3ef2c0705 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -34,6 +34,7 @@ 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; @@ -62,6 +63,7 @@ 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; @@ -4996,6 +4998,12 @@ 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 */); } } @@ -6162,7 +6170,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Reparent the SurfaceControl of this DisplayContent to null, to prevent content // being added to it. This ensures that no app launched explicitly on the // VirtualDisplay will show up as part of the mirrored content. - .reparent(mWindowingLayer, null); + .reparent(mWindowingLayer, null) + .reparent(mOverlayLayer, null); // Retrieve the size of the DisplayArea to mirror. updateMirroredSurface(transaction, wc.getDisplayContent().getBounds(), surfaceSize); mTokenToMirror = tokenToMirror; @@ -6192,7 +6201,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Reparent the SurfaceControl of this DisplayContent back to mSurfaceControl, // to allow content to be added to it. This allows this DisplayContent to stop // mirroring and show content normally. - .reparent(mWindowingLayer, mSurfaceControl).apply(); + .reparent(mWindowingLayer, mSurfaceControl) + .reparent(mOverlayLayer, mSurfaceControl) + .apply(); // Stop mirroring by destroying the reference to the mirrored layer. mMirroredSurface = null; // Do not un-set the token, in case content is removed and mirroring should begin again. diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index eab1aaf0d9fb..1d3c56efaf24 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -54,6 +54,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; +import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; @@ -111,9 +112,6 @@ 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; @@ -450,7 +448,7 @@ public class DisplayPolicy { : service.mContext.createDisplayContext(displayContent.getDisplay()); mUiContext = displayContent.isDefaultDisplay ? service.mAtmService.mUiContext : service.mAtmService.mSystemThread - .createSystemUiContext(displayContent.getDisplayId()); + .getSystemUiContext(displayContent.getDisplayId()); mDisplayContent = displayContent; mLock = service.getWindowManagerLock(); @@ -880,6 +878,20 @@ public class DisplayPolicy { } /** + * Only trusted overlays are allowed to use FLAG_SLIPPERY. + */ + static int sanitizeFlagSlippery(int flags, int privateFlags, String name) { + if ((flags & FLAG_SLIPPERY) == 0) { + return flags; + } + if ((privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0) { + return flags; + } + Slog.w(TAG, "Removing FLAG_SLIPPERY for non-trusted overlay " + name); + return flags & ~FLAG_SLIPPERY; + } + + /** * Sanitize the layout parameters coming from a client. Allows the policy * to do things like ensure that windows of a specific type can't take * input focus. @@ -950,6 +962,8 @@ public class DisplayPolicy { if (mExtraNavBarAlt == win) { mExtraNavBarAltPosition = getAltBarPosition(attrs); } + + attrs.flags = sanitizeFlagSlippery(attrs.flags, attrs.privateFlags, win.getName()); } /** @@ -2240,19 +2254,8 @@ public class DisplayPolicy { // For non-system users, ensure that the resources are loaded from the current // user's package info (see ContextImpl.createDisplayContext) - 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 */); + mCurrentUserResources = uiContext.createContextAsUser(UserHandle.of(userId), 0 /* flags*/) + .getResources(); } @VisibleForTesting @@ -2665,10 +2668,15 @@ public class DisplayPolicy { } void updateSystemBarAttributes() { + WindowState winCandidate = mFocusedWindow; + if (winCandidate == null && mTopFullscreenOpaqueWindowState != null + && (mTopFullscreenOpaqueWindowState.mAttrs.flags + & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0) { + // Only focusable window can take system bar control. + winCandidate = mTopFullscreenOpaqueWindowState; + } // If there is no window focused, there will be nobody to handle the events // anyway, so just hang on in whatever state we're in until things settle down. - WindowState winCandidate = mFocusedWindow != null ? mFocusedWindow - : mTopFullscreenOpaqueWindowState; if (winCandidate == null) { return; } 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 b1eca9d5d4e4..3ffa62dbbe7e 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.mDisplayContent); + startSystemDecorations(display); } // 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..4190ff0b9bc7 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2377,6 +2377,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/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index bdcda825860f..e66d92e8ea07 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -31,6 +31,7 @@ import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; 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.Process.INVALID_UID; import static android.os.UserHandle.USER_NULL; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.TRANSIT_CLOSE; @@ -221,6 +222,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** Organizer that organizing this TaskFragment. */ @Nullable private ITaskFragmentOrganizer mTaskFragmentOrganizer; + private int mTaskFragmentOrganizerUid = INVALID_UID; + private @Nullable String mTaskFragmentOrganizerProcessName; /** Client assigned unique token for this TaskFragment if this is created by an organizer. */ @Nullable @@ -233,13 +236,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ private boolean mDelayLastActivityRemoval; - /** - * The PID of the organizer that created this TaskFragment. It should be the same as the PID - * of {@link android.window.TaskFragmentCreationParams#getOwnerToken()}. - * {@link ActivityRecord#INVALID_PID} if this is not an organizer-created TaskFragment. - */ - private int mTaskFragmentOrganizerPid = ActivityRecord.INVALID_PID; - final Point mLastSurfaceSize = new Point(); private final Rect mTmpInsets = new Rect(); @@ -338,9 +334,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { mDelayLastActivityRemoval = false; } - void setTaskFragmentOrganizer(TaskFragmentOrganizerToken organizer, int pid) { + void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid, + @NonNull String processName) { mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder()); - mTaskFragmentOrganizerPid = pid; + mTaskFragmentOrganizerUid = uid; + mTaskFragmentOrganizerProcessName = processName; } /** Whether this TaskFragment is organized by the given {@code organizer}. */ @@ -2180,9 +2178,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { List<IBinder> childActivities = new ArrayList<>(); for (int i = 0; i < getChildCount(); i++) { WindowContainer wc = getChildAt(i); - if (mTaskFragmentOrganizerPid != ActivityRecord.INVALID_PID + if (mTaskFragmentOrganizerUid != INVALID_UID && wc.asActivityRecord() != null - && wc.asActivityRecord().getPid() == mTaskFragmentOrganizerPid) { + && wc.asActivityRecord().info.processName.equals( + mTaskFragmentOrganizerProcessName) + && wc.asActivityRecord().getUid() == mTaskFragmentOrganizerUid) { // Only includes Activities that belong to the organizer process for security. childActivities.add(wc.asActivityRecord().appToken); } diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java index bc530416c8cd..86e356a876b5 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#registerWindowContextListener(IBinder, int, int, Bundle)} + * {@link WindowManagerService#attachWindowContextToDisplayArea(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#unregisterWindowContextListener(IBinder)}. + * {@link WindowManagerService#detachWindowContextFromWindowContainer(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.app.WindowContext} representation. If the + * a {@code clientToken}, which is a {@link android.window.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 d17c9dd8de3c..3edcd5cdf013 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -58,6 +58,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; +import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; @@ -2740,6 +2741,9 @@ 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(); @@ -2830,6 +2834,39 @@ 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) { @@ -8337,8 +8374,10 @@ public class WindowManagerService extends IWindowManager.Stub h.setWindowToken(window); h.name = name; + flags = DisplayPolicy.sanitizeFlagSlippery(flags, privateFlags, name); + final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE - | LayoutParams.FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE); + | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE); h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | sanitizedFlags; h.layoutParamsType = type; h.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS; diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 43a4f977e73a..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; } @@ -1205,8 +1231,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub creationParams.getFragmentToken(), true /* createdByOrganizer */); // Set task fragment organizer immediately, since it might have to be notified about further // actions. - taskFragment.setTaskFragmentOrganizer( - creationParams.getOrganizer(), ownerActivity.getPid()); + taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(), + ownerActivity.getUid(), ownerActivity.info.processName); ownerActivity.getTask().addChild(taskFragment, POSITION_TOP); taskFragment.setWindowingMode(creationParams.getWindowingMode()); taskFragment.setBounds(creationParams.getInitialBounds()); 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/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt index 6c2a8916617b..19eb456a9a95 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -618,6 +618,60 @@ class DomainVerificationPackageTest { } @Test + fun migratePackageSelected() { + val pkgName = PKG_ONE + val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE, + listOf(DOMAIN_1), listOf(DOMAIN_2)) + val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO, + listOf(DOMAIN_1), listOf(DOMAIN_2)) + + val map = mutableMapOf<String, PackageSetting>() + val service = makeService { map[it] } + service.addPackage(pkgBefore) + + // Only insert the package after addPackage call to ensure the service doesn't access + // a live package inside the addPackage logic. It should only use the provided input. + map[pkgName] = pkgBefore + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS)) + .isEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID)) + .isEqualTo(DomainVerificationManager.STATUS_OK) + + service.getInfo(pkgName).run { + assertThat(identifier).isEqualTo(UUID_ONE) + assertThat(hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + )) + } + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + + // Now remove the package because migrateState shouldn't use it either + map.remove(pkgName) + + service.migrateState(pkgBefore, pkgAfter) + + map[pkgName] = pkgAfter + + service.getInfo(pkgName).run { + assertThat(identifier).isEqualTo(UUID_TWO) + assertThat(hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + )) + } + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + } + + @Test fun backupAndRestore() { // This test acts as a proxy for true user restore through PackageManager, // as that's much harder to test for real. @@ -798,7 +852,8 @@ class DomainVerificationPackageTest { pkgName: String, domainSetId: UUID, signature: String, - domains: List<String> = listOf(DOMAIN_1, DOMAIN_2), + autoVerifyDomains: List<String> = listOf(DOMAIN_1, DOMAIN_2), + otherDomains: List<String> = listOf(), isSystemApp: Boolean = false ) = mockThrowOnUnmocked<PackageSetting> { val pkg = mockThrowOnUnmocked<AndroidPackage> { @@ -806,21 +861,23 @@ class DomainVerificationPackageTest { whenever(targetSdkVersion) { Build.VERSION_CODES.S } whenever(isEnabled) { true } + fun baseIntent(domain: String) = ParsedIntentInfo().apply { + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("http") + addDataScheme("https") + addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) + addDataAuthority(domain, null) + } + val activityList = listOf( ParsedActivity().apply { - domains.forEach { - addIntent( - ParsedIntentInfo().apply { - autoVerify = true - addAction(Intent.ACTION_VIEW) - addCategory(Intent.CATEGORY_BROWSABLE) - addCategory(Intent.CATEGORY_DEFAULT) - addDataScheme("http") - addDataScheme("https") - addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) - addDataAuthority(it, null) - } - ) + autoVerifyDomains.forEach { + addIntent(baseIntent(it).apply { autoVerify = true }) + } + otherDomains.forEach { + addIntent(baseIntent(it).apply { autoVerify = false }) } }, ) 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/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 128bfa8a28f3..fc2c162d058b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -16,9 +16,11 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; @@ -100,7 +102,6 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import android.app.ActivityOptions; -import android.app.WindowConfiguration; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.DestroyActivityItem; @@ -560,7 +561,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWith2LevelTask(); final Task task = activity.getTask(); final Task rootTask = activity.getRootTask(); - rootTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + rootTask.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); final Rect stableRect = new Rect(); rootTask.mDisplayContent.getStableRect(stableRect); @@ -602,19 +603,22 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void respectRequestedOrientationForNonResizableInSplitWindows() { - final Task task = new TaskBuilder(mSupervisor) - .setCreateParentTask(true).setCreateActivity(true).build(); - final Task rootTask = task.getRootTask(); + final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea(); + spyOn(tda); + doReturn(true).when(tda).supportsNonResizableMultiWindow(); + final Task rootTask = mDisplayContent.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + rootTask.setBounds(0, 0, 1000, 500); final ActivityRecord activity = new ActivityBuilder(mAtm) - .setParentTask(task) + .setParentTask(rootTask) + .setCreateTask(true) .setOnTop(true) .setResizeMode(RESIZE_MODE_UNRESIZEABLE) .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) .build(); + final Task task = activity.getTask(); // Task in landscape. - rootTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - task.setBounds(0, 0, 1000, 500); assertEquals(ORIENTATION_LANDSCAPE, task.getConfiguration().orientation); // Asserts fixed orientation request is respected, and the orientation is not changed. @@ -623,7 +627,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Clear size compat. activity.clearSizeCompatMode(); activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); - activity.mDisplayContent.sendNewConfiguration(); + mDisplayContent.sendNewConfiguration(); // Relaunching the app should still respect the orientation request. assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation); 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 a8ede13e5de6..d7daa57cc9da 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java @@ -29,7 +29,9 @@ 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; @@ -44,6 +46,7 @@ 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; @@ -62,6 +65,9 @@ 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. @@ -70,10 +76,12 @@ 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. - IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); - spyOn(wms); + mIWindowManager = WindowManagerGlobal.getWindowManagerService(); + spyOn(mIWindowManager); doAnswer(invocation -> { Object[] args = invocation.getArguments(); IBinder clientToken = (IBinder) args[0]; @@ -83,19 +91,24 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG, null /* options */); return dc.getImeContainer().getConfiguration(); - }).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. + }).when(mIWindowManager).attachWindowContextToDisplayArea(any(), + eq(TYPE_INPUT_METHOD_DIALOG), anyInt(), any()); + mDisplayManagerGlobal = DisplayManagerGlobal.getInstance(); + spyOn(mDisplayManagerGlobal); final int displayId = mSecondaryDisplay.getDisplayId(); final Display display = mSecondaryDisplay.getDisplay(); - DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance(); - spyOn(displayManagerGlobal); - doReturn(display).when(displayManagerGlobal).getCompatibleDisplay(eq(displayId), + doReturn(display).when(mDisplayManagerGlobal).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 feb656cc6498..9639aa78fd5b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -158,46 +158,6 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testKeepBoundsWhenChangingFromFreeformToFullscreen() { - removeGlobalMinSizeRestriction(); - // Create landscape freeform display and a freeform app. - DisplayContent display = new TestDisplayContent.Builder(mAtm, 2000, 1000) - .setCanRotate(false) - .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM).build(); - setUpApp(display); - - // Put app window into portrait freeform and then make it a compat app. - final Rect bounds = new Rect(100, 100, 400, 600); - mTask.setBounds(bounds); - prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); - assertEquals(bounds, mActivity.getBounds()); - // Activity is not yet in size compat mode; it is filling the freeform task window. - assertActivityMaxBoundsSandboxed(); - - // The activity should be able to accept negative x position [-150, 100 - 150, 600]. - final int dx = bounds.left + bounds.width() / 2; - final int dy = bounds.top + bounds.height() / 2; - mTask.setBounds(bounds.left - dx, bounds.top - dy, bounds.right - dx, bounds.bottom - dy); - // expected:<Rect(-150, 100 - 150, 600)> but was:<Rect(-150, 0 - 150, 500)> - assertEquals(mTask.getBounds(), mActivity.getBounds()); - - final int density = mActivity.getConfiguration().densityDpi; - - // Change display configuration to fullscreen. - Configuration c = new Configuration(display.getRequestedOverrideConfiguration()); - c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); - display.onRequestedOverrideConfigurationChanged(c); - - // Check if dimensions on screen stay the same by scaling. - assertScaled(); - assertEquals(bounds.width(), mActivity.getBounds().width()); - assertEquals(bounds.height(), mActivity.getBounds().height()); - assertEquals(density, mActivity.getConfiguration().densityDpi); - // Size compat mode is sandboxed at the activity level. - assertActivityMaxBoundsSandboxed(); - } - - @Test public void testFixedAspectRatioBoundsWithDecorInSquareDisplay() { final int notchHeight = 100; setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800).setNotch(notchHeight).build()); @@ -687,7 +647,7 @@ public class SizeCompatTests extends WindowTestsBase { .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE) .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) .build(); - assertTrue(activity.shouldCreateCompatDisplayInsets()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); // The non-resizable activity should not be size compat because it is on a resizable task // in multi-window mode. @@ -719,7 +679,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testShouldCreateCompatDisplayInsetsWhenUnresizeableAndSupportsSizeChangesFalse() { + public void testShouldNotCreateCompatDisplayInsetsWhenRootActivityIsResizeable() { setUpDisplaySizeWithApp(1000, 2500); // Make the task root resizable. @@ -728,7 +688,7 @@ public class SizeCompatTests extends WindowTestsBase { // Create an activity on the same task. final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false, RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - assertTrue(activity.shouldCreateCompatDisplayInsets()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); } @Test @@ -2318,6 +2278,12 @@ public class SizeCompatTests extends WindowTestsBase { activity.info.resizeMode = isUnresizable ? RESIZE_MODE_UNRESIZEABLE : RESIZE_MODE_RESIZEABLE; + final Task task = activity.getTask(); + if (task != null) { + // Update the Task resize value as activity will follow the task. + task.mResizeMode = activity.info.resizeMode; + task.getRootActivity().info.resizeMode = activity.info.resizeMode; + } activity.mVisibleRequested = true; if (maxAspect >= 0) { activity.info.setMaxAspectRatio(maxAspect); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index d475c46eed0c..786c3eae63d0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -254,7 +254,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { }); // Allow transaction to change a TaskFragment created by the organizer. - mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } @@ -276,7 +277,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { }); // Allow transaction to change a TaskFragment created by the organizer. - mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } @@ -301,7 +303,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { }); // Allow transaction to change a TaskFragment created by the organizer. - mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); @@ -337,8 +340,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { }); // Allow transaction to change a TaskFragment created by the organizer. - mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); - taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); + taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); @@ -391,7 +396,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { }); // Allow transaction to change a TaskFragment created by the organizer. - mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 42f4d583f5ff..6737b1ade785 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -20,12 +20,15 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.clearInvocations; import android.graphics.Rect; +import android.os.Binder; import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import android.window.ITaskFragmentOrganizer; +import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import androidx.test.filters.MediumTest; @@ -64,6 +67,7 @@ public class TaskFragmentTest extends WindowTestsBase { mTaskFragment = new TaskFragmentBuilder(mAtm) .setCreateParentTask() .setOrganizer(mOrganizer) + .setFragmentToken(new Binder()) .build(); mLeash = mTaskFragment.getSurfaceControl(); spyOn(mTaskFragment); @@ -103,4 +107,23 @@ public class TaskFragmentTest extends WindowTestsBase { verify(mTransaction).setPosition(mLeash, 500, 500); verify(mTransaction).setWindowCrop(mLeash, 500, 500); } + + /** + * Tests that when a {@link TaskFragmentInfo} is generated from a {@link TaskFragment}, an + * activity that has not yet been attached to a process because it is being initialized but + * belongs to the TaskFragmentOrganizer process is still reported in the TaskFragmentInfo. + */ + @Test + public void testActivityStillReported_NotYetAssignedToProcess() { + mTaskFragment.addChild(new ActivityBuilder(mAtm).setUid(DEFAULT_TASK_FRAGMENT_ORGANIZER_UID) + .setProcessName(DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME).build()); + final ActivityRecord activity = mTaskFragment.getTopMostActivity(); + // Remove the process to simulate an activity that has not yet been attached to a process + activity.app = null; + final TaskFragmentInfo info = activity.getTaskFragment().getTaskFragmentInfo(); + assertEquals(1, info.getRunningActivityCount()); + assertEquals(1, info.getActivities().size()); + assertEquals(false, info.isEmpty()); + assertEquals(activity.token, info.getActivities().get(0)); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 2a0aa96a00ee..88d8ba308b45 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -128,6 +128,9 @@ class WindowTestsBase extends SystemServiceTestsBase { // Default package name static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo"; + static final int DEFAULT_TASK_FRAGMENT_ORGANIZER_UID = 10000; + static final String DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME = "Test:TaskFragmentOrganizer"; + // Default base activity name private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity"; @@ -1243,7 +1246,8 @@ class WindowTestsBase extends SystemServiceTestsBase { } if (mOrganizer != null) { taskFragment.setTaskFragmentOrganizer( - mOrganizer.getOrganizerToken(), 10000 /* pid */); + mOrganizer.getOrganizerToken(), DEFAULT_TASK_FRAGMENT_ORGANIZER_UID, + DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME); } return taskFragment; } 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); } } |