diff options
114 files changed, 2181 insertions, 973 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index a4fb2c1f12ff..a8e06fc4c8a0 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3204,12 +3204,6 @@ package android.window { field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR; } - public final class TaskFragmentAppearedInfo implements android.os.Parcelable { - method @NonNull public android.view.SurfaceControl getLeash(); - method @NonNull public android.window.TaskFragmentInfo getTaskFragmentInfo(); - field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentAppearedInfo> CREATOR; - } - public final class TaskFragmentCreationParams implements android.os.Parcelable { method @NonNull public android.os.IBinder getFragmentToken(); method @NonNull public android.graphics.Rect getInitialBounds(); @@ -3246,7 +3240,7 @@ package android.window { ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor); method @NonNull public java.util.concurrent.Executor getExecutor(); method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken(); - method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentAppearedInfo); + method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo); method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable); method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo); method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 992f02ca538b..db45466d98d2 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4076,6 +4076,34 @@ public class ActivityManager { } /** + * Gets the message that is shown when a user is switched from. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USERS) + public @Nullable String getSwitchingFromUserMessage() { + try { + return getService().getSwitchingFromUserMessage(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Gets the message that is shown when a user is switched to. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USERS) + public @Nullable String getSwitchingToUserMessage() { + try { + return getService().getSwitchingToUserMessage(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Uses the value defined by the platform. * * @hide diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 17bb797bf8c5..19a1d7af5382 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; @@ -320,7 +321,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; @@ -2633,22 +2634,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) { @@ -3767,7 +3772,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..e76c1398b04a 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,18 @@ class ContextImpl extends Context { mContentCaptureOptions = options; } + @Override + protected void finalize() throws Throwable { + // If mToken is a WindowTokenClient, the Context is usually associated with a + // WindowContainer. We should detach from WindowContainer when the Context is finalized + // if this Context is not a WindowContext. WindowContext finalization is handled in + // WindowContext class. + if (mToken instanceof WindowTokenClient && mContextType != CONTEXT_TYPE_WINDOW_CONTEXT) { + ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded(); + } + super.finalize(); + } + @UnsupportedAppUsage static ContextImpl createSystemContext(ActivityThread mainThread) { LoadedApk packageInfo = new LoadedApk(mainThread); @@ -2983,22 +2997,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(); + final 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 +3211,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 9e9e28b8bd4c..4912703bc2ce 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -338,6 +338,8 @@ interface IActivityManager { void setPackageScreenCompatMode(in String packageName, int mode); @UnsupportedAppUsage boolean switchUser(int userid); + String getSwitchingFromUserMessage(); + String getSwitchingToUserMessage(); @UnsupportedAppUsage void setStopUserOnSwitch(int value); boolean removeTask(int taskId); 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/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl index 5eb432e785ee..cdfa206423c2 100644 --- a/core/java/android/window/ITaskFragmentOrganizer.aidl +++ b/core/java/android/window/ITaskFragmentOrganizer.aidl @@ -19,12 +19,11 @@ package android.window; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentInfo; /** @hide */ oneway interface ITaskFragmentOrganizer { - void onTaskFragmentAppeared(in TaskFragmentAppearedInfo taskFragmentAppearedInfo); + void onTaskFragmentAppeared(in TaskFragmentInfo taskFragmentInfo); void onTaskFragmentInfoChanged(in TaskFragmentInfo taskFragmentInfo); void onTaskFragmentVanished(in TaskFragmentInfo taskFragmentInfo); diff --git a/core/java/android/window/TaskFragmentAppearedInfo.aidl b/core/java/android/window/TaskFragmentAppearedInfo.aidl deleted file mode 100644 index 3729c09168a6..000000000000 --- a/core/java/android/window/TaskFragmentAppearedInfo.aidl +++ /dev/null @@ -1,23 +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 android.window; - -/** - * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer. - * @hide - */ -parcelable TaskFragmentAppearedInfo; diff --git a/core/java/android/window/TaskFragmentAppearedInfo.java b/core/java/android/window/TaskFragmentAppearedInfo.java deleted file mode 100644 index 89d9a9508a71..000000000000 --- a/core/java/android/window/TaskFragmentAppearedInfo.java +++ /dev/null @@ -1,93 +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 android.window; - -import android.annotation.NonNull; -import android.annotation.TestApi; -import android.os.Parcel; -import android.os.Parcelable; -import android.view.SurfaceControl; - -/** - * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer. - * @hide - */ -@TestApi -public final class TaskFragmentAppearedInfo implements Parcelable { - - @NonNull - private final TaskFragmentInfo mTaskFragmentInfo; - - @NonNull - private final SurfaceControl mLeash; - - /** @hide */ - public TaskFragmentAppearedInfo( - @NonNull TaskFragmentInfo taskFragmentInfo, @NonNull SurfaceControl leash) { - mTaskFragmentInfo = taskFragmentInfo; - mLeash = leash; - } - - @NonNull - public TaskFragmentInfo getTaskFragmentInfo() { - return mTaskFragmentInfo; - } - - @NonNull - public SurfaceControl getLeash() { - return mLeash; - } - - private TaskFragmentAppearedInfo(Parcel in) { - mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR); - mLeash = in.readTypedObject(SurfaceControl.CREATOR); - } - - /** @hide */ - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeTypedObject(mTaskFragmentInfo, flags); - dest.writeTypedObject(mLeash, flags); - } - - @NonNull - public static final Creator<TaskFragmentAppearedInfo> CREATOR = - new Creator<TaskFragmentAppearedInfo>() { - @Override - public TaskFragmentAppearedInfo createFromParcel(Parcel in) { - return new TaskFragmentAppearedInfo(in); - } - - @Override - public TaskFragmentAppearedInfo[] newArray(int size) { - return new TaskFragmentAppearedInfo[size]; - } - }; - - @Override - public String toString() { - return "TaskFragmentAppearedInfo{" - + " taskFragmentInfo=" + mTaskFragmentInfo - + "}"; - } - - /** @hide */ - @Override - public int describeContents() { - return 0; - } -} diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 337c5a14e9d3..7e7d37083b5b 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -120,8 +120,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } /** Called when a TaskFragment is created and organized by this organizer. */ - public void onTaskFragmentAppeared( - @NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {} + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {} /** Called when the status of an organized TaskFragment is changed. */ public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {} @@ -169,7 +168,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() { @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentInfo) { + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { mExecutor.execute( () -> TaskFragmentOrganizer.this.onTaskFragmentAppeared(taskFragmentInfo)); } 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 5aff0e0cf38a..4d851644903e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4879,9 +4879,10 @@ the app in the letterbox mode. --> <item name="config_fixedOrientationLetterboxAspectRatio" format="float" type="dimen">0.0</item> - <!-- Corners radius for activity presented the letterbox mode. Values < 0 will be ignored and - min between device bottom corner radii will be used instead. --> - <integer name="config_letterboxActivityCornersRadius">-1</integer> + <!-- Corners radius for activity presented the letterbox mode. Values < 0 enable rounded + corners with radius equal to min between device bottom corner radii. Default 0 value turns + off rounded corners logic in LetterboxUiController. --> + <integer name="config_letterboxActivityCornersRadius">0</integer> <!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are ignored and 0 is used. --> 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/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java index 5619585d9c3c..16f732f63bf7 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java @@ -22,6 +22,8 @@ import android.system.keystore2.Authorization; import android.system.keystore2.Domain; import android.system.keystore2.KeyDescriptor; +import com.android.internal.annotations.VisibleForTesting; + import java.security.Key; /** @@ -46,7 +48,11 @@ public class AndroidKeyStoreKey implements Key { // We do not include this member in comparisons. private final KeyStoreSecurityLevel mSecurityLevel; - AndroidKeyStoreKey(@NonNull KeyDescriptor descriptor, + /** + * @hide + */ + @VisibleForTesting + public AndroidKeyStoreKey(@NonNull KeyDescriptor descriptor, long keyId, @NonNull Authorization[] authorizations, @NonNull String algorithm, @@ -102,11 +108,9 @@ public class AndroidKeyStoreKey implements Key { final int prime = 31; int result = 1; - result = prime * result + ((mDescriptor == null) ? 0 : mDescriptor.hashCode()); + result = prime * result + getClass().hashCode(); result = prime * result + (int) (mKeyId >>> 32); result = prime * result + (int) (mKeyId & 0xffffffff); - result = prime * result + ((mAuthorizations == null) ? 0 : mAuthorizations.hashCode()); - result = prime * result + ((mAlgorithm == null) ? 0 : mAlgorithm.hashCode()); return result; } @@ -122,10 +126,6 @@ public class AndroidKeyStoreKey implements Key { return false; } AndroidKeyStoreKey other = (AndroidKeyStoreKey) obj; - if (mKeyId != other.mKeyId) { - return false; - } - - return true; + return mKeyId == other.mKeyId; } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java index db3e567cb6b4..0b3be327b521 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java @@ -23,6 +23,7 @@ import android.system.keystore2.KeyDescriptor; import android.system.keystore2.KeyMetadata; import java.security.PublicKey; +import java.util.Arrays; /** * {@link PublicKey} backed by Android Keystore. @@ -61,8 +62,8 @@ public abstract class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implem int result = 1; result = prime * result + super.hashCode(); - result = prime * result + ((mCertificate == null) ? 0 : mCertificate.hashCode()); - result = prime * result + ((mCertificateChain == null) ? 0 : mCertificateChain.hashCode()); + result = prime * result + Arrays.hashCode(mCertificate); + result = prime * result + Arrays.hashCode(mCertificateChain); return result; } @@ -75,9 +76,14 @@ public abstract class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implem if (!super.equals(obj)) { return false; } - if (getClass() != obj.getClass()) { - return false; - } - return true; + + /* + * getClass().equals(ojb.getClass()) is implied by the call to super.equals() above. This + * means we can cast obj to AndroidKeyStorePublicKey here. + */ + final AndroidKeyStorePublicKey other = (AndroidKeyStorePublicKey) obj; + + return Arrays.equals(mCertificate, other.mCertificate) && Arrays.equals(mCertificateChain, + other.mCertificateChain); } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index 67358c4f3255..33411e1ec5b9 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -601,8 +601,6 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } KeyProtection params = (KeyProtection) param; - @SecurityLevel int securityLevel = params.isStrongBoxBacked() ? SecurityLevel.STRONGBOX : - SecurityLevel.TRUSTED_ENVIRONMENT; @Domain int targetDomain = (getTargetDomain()); if (key instanceof AndroidKeyStoreSecretKey) { @@ -794,6 +792,9 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING; } + @SecurityLevel int securityLevel = params.isStrongBoxBacked() ? SecurityLevel.STRONGBOX : + SecurityLevel.TRUSTED_ENVIRONMENT; + try { KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel( securityLevel); diff --git a/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java b/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java index 1bd3069f483a..f96c39c879fd 100644 --- a/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java +++ b/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java @@ -24,7 +24,13 @@ import static org.mockito.Mockito.when; import android.security.KeyStore2; import android.security.KeyStoreException; +import android.security.KeyStoreSecurityLevel; +import android.system.keystore2.Authorization; +import android.system.keystore2.Domain; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -52,4 +58,112 @@ public class AndroidKeyStoreSpiTest { verify(mKeystore2).list(anyInt(), anyLong()); } + @Mock + private KeyStoreSecurityLevel mKeystoreSecurityLevel; + + private static KeyDescriptor descriptor() { + final KeyDescriptor keyDescriptor = new KeyDescriptor(); + keyDescriptor.alias = "key"; + keyDescriptor.blob = null; + keyDescriptor.domain = Domain.APP; + keyDescriptor.nspace = -1; + return keyDescriptor; + } + + private static KeyMetadata metadata(byte[] cert, byte[] certChain) { + KeyMetadata metadata = new KeyMetadata(); + metadata.authorizations = new Authorization[0]; + metadata.certificate = cert; + metadata.certificateChain = certChain; + metadata.key = descriptor(); + metadata.modificationTimeMs = 0; + metadata.keySecurityLevel = 1; + return metadata; + } + + private static byte[] bytes(String string) { + return string.getBytes(); + } + + class MyPublicKey extends AndroidKeyStorePublicKey { + MyPublicKey(String cert, String chain, KeyStoreSecurityLevel securityLevel) { + super(descriptor(), metadata(cert.getBytes(), chain.getBytes()), "N/A".getBytes(), + "RSA", securityLevel); + } + + @Override + AndroidKeyStorePrivateKey getPrivateKey() { + return null; + } + } + + private AndroidKeyStorePublicKey makePrivateKeyObject(String cert, String chain) { + return new MyPublicKey(cert, chain, mKeystoreSecurityLevel); + } + + @Test + public void testKeystoreKeysAdhereToContractBetweenEqualsAndHashCode() throws Exception { + AndroidKeyStoreKey key1 = new AndroidKeyStoreKey(descriptor(), 1, new Authorization[0], + "RSA", mKeystoreSecurityLevel); + AndroidKeyStoreKey key2 = new AndroidKeyStoreKey(descriptor(), 2, new Authorization[0], + "RSA", mKeystoreSecurityLevel); + AndroidKeyStoreKey key1_clone = new AndroidKeyStoreKey(descriptor(), 1, + new Authorization[0], "RSA", mKeystoreSecurityLevel); + + assertThat("Identity should yield true", key1.equals(key1)); + Assert.assertEquals("Identity should yield same hash codes", + key1.hashCode(), key1.hashCode()); + assertThat("Identity should yield true", key2.equals(key2)); + Assert.assertEquals("Identity should yield same hash codes", + key2.hashCode(), key2.hashCode()); + assertThat("Different keys should differ", !key1.equals(key2)); + Assert.assertNotEquals("Different keys should have different hash codes", + key1.hashCode(), key2.hashCode()); + + assertThat("Same keys should yield true", key1.equals(key1_clone)); + assertThat("Same keys should yield true", key1_clone.equals(key1)); + Assert.assertEquals("Same keys should yield same hash codes", + key1.hashCode(), key1_clone.hashCode()); + + assertThat("anything.equal(null) should yield false", !key1.equals(null)); + assertThat("anything.equal(null) should yield false", !key2.equals(null)); + assertThat("anything.equal(null) should yield false", !key1_clone.equals(null)); + } + + @Test + public void testKeystorePublicKeysAdhereToContractBetweenEqualsAndHashCode() throws Exception { + AndroidKeyStorePublicKey key1 = makePrivateKeyObject("myCert1", "myChain1"); + AndroidKeyStorePublicKey key2 = makePrivateKeyObject("myCert2", "myChain1"); + AndroidKeyStorePublicKey key3 = makePrivateKeyObject("myCert1", "myChain3"); + AndroidKeyStorePublicKey key1_clone = makePrivateKeyObject("myCert1", "myChain1"); + + assertThat("Identity should yield true", key1.equals(key1)); + Assert.assertEquals("Identity should yield same hash codes", + key1.hashCode(), key1.hashCode()); + assertThat("Identity should yield true", key2.equals(key2)); + Assert.assertEquals("Identity should yield same hash codes", + key2.hashCode(), key2.hashCode()); + assertThat("Identity should yield true", key3.equals(key3)); + Assert.assertEquals("Identity should yield same hash codes", + key3.hashCode(), key3.hashCode()); + assertThat("Different keys should differ", !key1.equals(key2)); + Assert.assertNotEquals("Different keys should have different hash codes", + key1.hashCode(), key2.hashCode()); + assertThat("Different keys should differ", !key1.equals(key3)); + Assert.assertNotEquals("Different keys should have different hash codes", + key1.hashCode(), key3.hashCode()); + assertThat("Different keys should differ", !key2.equals(key3)); + Assert.assertNotEquals("Different keys should have different hash codes", + key2.hashCode(), key3.hashCode()); + + assertThat("Same keys should yield true", key1.equals(key1_clone)); + assertThat("Same keys should yield true", key1_clone.equals(key1)); + Assert.assertEquals("Same keys should yield same hash codes", + key1.hashCode(), key1_clone.hashCode()); + + assertThat("anything.equal(null) should yield false", !key1.equals(null)); + assertThat("anything.equal(null) should yield false", !key2.equals(null)); + assertThat("anything.equal(null) should yield false", !key3.equals(null)); + assertThat("anything.equal(null) should yield false", !key1_clone.equals(null)); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 85ef270ac49d..df751fc9fa48 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -27,8 +27,6 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; -import android.view.SurfaceControl; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; @@ -51,9 +49,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */ private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); - /** Mapping from the client assigned unique token to the TaskFragment {@link SurfaceControl}. */ - private final Map<IBinder, SurfaceControl> mFragmentLeashes = new ArrayMap<>(); - /** * Mapping from the client assigned unique token to the TaskFragment parent * {@link Configuration}. @@ -67,7 +62,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * Callback that notifies the controller about changes to task fragments. */ interface TaskFragmentCallback { - void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo); + void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, @@ -259,15 +254,12 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { } @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) { - final TaskFragmentInfo info = taskFragmentAppearedInfo.getTaskFragmentInfo(); - final IBinder fragmentToken = info.getFragmentToken(); - final SurfaceControl leash = taskFragmentAppearedInfo.getLeash(); - mFragmentInfos.put(fragmentToken, info); - mFragmentLeashes.put(fragmentToken, leash); + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { + final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); + mFragmentInfos.put(fragmentToken, taskFragmentInfo); if (mCallback != null) { - mCallback.onTaskFragmentAppeared(taskFragmentAppearedInfo); + mCallback.onTaskFragmentAppeared(taskFragmentInfo); } } @@ -284,7 +276,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { @Override public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { mFragmentInfos.remove(taskFragmentInfo.getFragmentToken()); - mFragmentLeashes.remove(taskFragmentInfo.getFragmentToken()); mFragmentParentConfigs.remove(taskFragmentInfo.getFragmentToken()); if (mCallback != null) { 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 68c19041940c..fe6c7ba3b24c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -37,7 +37,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; @@ -110,14 +109,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) { - TaskFragmentContainer container = getContainer( - taskFragmentAppearedInfo.getTaskFragmentInfo().getFragmentToken()); + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { + TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container == null) { return; } - container.setInfo(taskFragmentAppearedInfo.getTaskFragmentInfo()); + container.setInfo(taskFragmentInfo); if (container.isFinished()) { mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); } diff --git a/packages/SystemUI/res/color/prv_text_color_on_accent.xml b/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml index 9f44acadb420..329e5b9b31a0 100644 --- a/packages/SystemUI/res/color/prv_text_color_on_accent.xml +++ b/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml @@ -14,7 +14,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<selector xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="?androidprv:attr/textColorOnAccent" /> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_500" android:lStar="35" /> </selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml b/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml index 94165a11eccb..22cd384e1be0 100644 --- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml +++ b/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml @@ -16,6 +16,6 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <solid android:color="@color/size_compat_hint_bubble"/> + <solid android:color="@color/size_compat_background"/> <corners android:radius="@dimen/size_compat_hint_corner_radius"/> </shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml b/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml index a8f0f76ef27f..af9063a94afb 100644 --- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml +++ b/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml @@ -20,6 +20,6 @@ android:viewportWidth="10" android:viewportHeight="8"> <path - android:fillColor="@color/size_compat_hint_bubble" + android:fillColor="@color/size_compat_background" android:pathData="M10,0 l-4.1875,6.6875 a1,1 0 0,1 -1.625,0 l-4.1875,-6.6875z"/> </vector> diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml index 3e486df71f91..18caa3582537 100644 --- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml +++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml @@ -20,16 +20,16 @@ android:viewportWidth="48" android:viewportHeight="48"> <path - android:fillColor="#53534D" + android:fillColor="@color/size_compat_background" android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0" /> <group android:translateX="12" android:translateY="12"> <path - android:fillColor="#E4E3DA" + android:fillColor="@color/size_compat_text" android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/> <path - android:fillColor="#E4E3DA" + android:fillColor="@color/size_compat_text" android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/> </group> </vector> diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml new file mode 100644 index 000000000000..95decff24ac4 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/size_compat_background_ripple"> + <item android:drawable="@drawable/size_compat_restart_button"/> +</ripple>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml index 17347f627049..d0e7c42dbf8b 100644 --- a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml +++ b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml @@ -40,7 +40,7 @@ android:padding="16dp" android:text="@string/restart_button_description" android:textAlignment="viewStart" - android:textColor="#E4E3DA" + android:textColor="@color/size_compat_text" android:textSize="14sp"/> <ImageView diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml index 47e76f061877..82ebee263a64 100644 --- a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml +++ b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml @@ -30,7 +30,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:src="@drawable/size_compat_restart_button" + android:src="@drawable/size_compat_restart_button_ripple" android:background="@android:color/transparent" android:contentDescription="@string/restart_button_description"/> diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml index b25a2189cd4d..23a21724e43d 100644 --- a/libs/WindowManager/Shell/res/values/colors.xml +++ b/libs/WindowManager/Shell/res/values/colors.xml @@ -29,7 +29,10 @@ <color name="bubbles_light">#FFFFFF</color> <color name="bubbles_dark">@color/GM2_grey_800</color> <color name="bubbles_icon_tint">@color/GM2_grey_700</color> - <color name="size_compat_hint_bubble">#30312B</color> + + <!-- Size Compat Restart Button --> + <color name="size_compat_background">@android:color/system_neutral1_800</color> + <color name="size_compat_text">@android:color/system_neutral1_50</color> <!-- GM2 colors --> <color name="GM2_grey_200">#E8EAED</color> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index d239e56bfd69..6b622044d25d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -337,7 +337,7 @@ public abstract class WMShellBaseModule { @Provides static Optional<OneHandedController> providesOneHandedController( @DynamicOverride Optional<OneHandedController> oneHandedController) { - if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) { + if (SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) { return oneHandedController; } return Optional.empty(); 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 7cf3bafe499a..a006f308d694 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 @@ -111,6 +111,11 @@ public class RecentTasksController implements TaskStackListenerCallback, if (taskId1 == taskId2) { return; } + if (mSplitTasks.get(taskId1, INVALID_TASK_ID) == taskId2 + && mTaskSplitBoundsMap.get(taskId1).equals(splitBounds)) { + // If the two tasks are already paired and the bounds are the same, then skip updating + return; + } // Remove any previous pairs removeSplitPair(taskId1); removeSplitPair(taskId2); @@ -121,6 +126,7 @@ public class RecentTasksController implements TaskStackListenerCallback, mSplitTasks.put(taskId2, taskId1); mTaskSplitBoundsMap.put(taskId1, splitBounds); mTaskSplitBoundsMap.put(taskId2, splitBounds); + notifyRecentTasksChanged(); } /** @@ -133,6 +139,7 @@ public class RecentTasksController implements TaskStackListenerCallback, mSplitTasks.delete(pairedTaskId); mTaskSplitBoundsMap.remove(taskId); mTaskSplitBoundsMap.remove(pairedTaskId); + notifyRecentTasksChanged(); } } @@ -217,7 +224,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } final int pairedTaskId = mSplitTasks.get(taskInfo.taskId); - if (pairedTaskId != INVALID_TASK_ID) { + if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) { final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); rawMapping.remove(pairedTaskId); recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo, 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 index aadf792c572f..a0c84cc33ebd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java @@ -19,6 +19,8 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * Container of various information needed to display split screen * tasks/leashes/etc in Launcher @@ -93,6 +95,24 @@ public class StagedSplitBounds implements Parcelable { } @Override + public boolean equals(Object obj) { + if (!(obj instanceof StagedSplitBounds)) { + return false; + } + // Only need to check the base fields (the other fields are derived from these) + final StagedSplitBounds other = (StagedSplitBounds) obj; + return Objects.equals(leftTopBounds, other.leftTopBounds) + && Objects.equals(rightBottomBounds, other.rightBottomBounds) + && leftTopTaskId == other.leftTopTaskId + && rightBottomTaskId == other.rightBottomTaskId; + } + + @Override + public int hashCode() { + return Objects.hash(leftTopBounds, rightBottomBounds, leftTopTaskId, rightBottomTaskId); + } + + @Override public String toString() { return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n" + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n" 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 19a5417aace6..50f6bd7b4927 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 @@ -28,6 +28,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static java.lang.Integer.MAX_VALUE; @@ -83,6 +84,36 @@ public class RecentTasksControllerTest extends ShellTestCase { } @Test + public void testAddRemoveSplitNotifyChange() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + setRawList(t1, t2); + + mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(StagedSplitBounds.class)); + verify(mRecentTasksController).notifyRecentTasksChanged(); + + reset(mRecentTasksController); + mRecentTasksController.removeSplitPair(t1.taskId); + verify(mRecentTasksController).notifyRecentTasksChanged(); + } + + @Test + public void testAddSameSplitBoundsInfoSkipNotifyChange() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + setRawList(t1, t2); + + // Verify only one update if the split info is the same + StagedSplitBounds bounds1 = new StagedSplitBounds(new Rect(0, 0, 50, 50), + new Rect(50, 50, 100, 100), t1.taskId, t2.taskId); + mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1); + StagedSplitBounds bounds2 = new StagedSplitBounds(new Rect(0, 0, 50, 50), + new Rect(50, 50, 100, 100), t1.taskId, t2.taskId); + mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2); + verify(mRecentTasksController, times(1)).notifyRecentTasksChanged(); + } + + @Test public void testGetRecentTasks() { ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java index a210e90a3cfc..8b17be1e8ec9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java @@ -19,7 +19,6 @@ package com.android.settingslib.notification; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.AlertDialog; -import android.app.Dialog; import android.app.NotificationManager; import android.content.Context; import android.content.DialogInterface; @@ -85,6 +84,7 @@ public class EnableZenModeDialog { @VisibleForTesting protected Context mContext; + private final int mThemeResId; @VisibleForTesting protected TextView mZenAlarmWarning; @VisibleForTesting @@ -97,10 +97,15 @@ public class EnableZenModeDialog { protected LayoutInflater mLayoutInflater; public EnableZenModeDialog(Context context) { + this(context, 0); + } + + public EnableZenModeDialog(Context context, int themeResId) { mContext = context; + mThemeResId = themeResId; } - public Dialog createDialog() { + public AlertDialog createDialog() { mNotificationManager = (NotificationManager) mContext. getSystemService(Context.NOTIFICATION_SERVICE); mForeverId = Condition.newId(mContext).appendPath("forever").build(); @@ -108,7 +113,7 @@ public class EnableZenModeDialog { mUserId = mContext.getUserId(); mAttached = false; - final AlertDialog.Builder builder = new AlertDialog.Builder(mContext) + final AlertDialog.Builder builder = new AlertDialog.Builder(mContext, mThemeResId) .setTitle(R.string.zen_mode_settings_turn_on_dialog_title) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.zen_mode_enable_dialog_turn_on, diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml index 1a128dfe8b10..14cb1de9fa2d 100644 --- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml @@ -16,8 +16,8 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:insetTop="@dimen/qs_dialog_button_vertical_inset" - android:insetBottom="@dimen/qs_dialog_button_vertical_inset"> + android:insetTop="@dimen/dialog_button_vertical_inset" + android:insetBottom="@dimen/dialog_button_vertical_inset"> <ripple android:color="?android:attr/colorControlHighlight"> <item android:id="@android:id/mask"> <shape android:shape="rectangle"> @@ -29,10 +29,10 @@ <shape android:shape="rectangle"> <corners android:radius="?android:attr/buttonCornerRadius"/> <solid android:color="?androidprv:attr/colorAccentPrimary"/> - <padding android:left="@dimen/qs_dialog_button_horizontal_padding" - android:top="@dimen/qs_dialog_button_vertical_padding" - android:right="@dimen/qs_dialog_button_horizontal_padding" - android:bottom="@dimen/qs_dialog_button_vertical_padding"/> + <padding android:left="@dimen/dialog_button_horizontal_padding" + android:top="@dimen/dialog_button_vertical_padding" + android:right="@dimen/dialog_button_horizontal_padding" + android:bottom="@dimen/dialog_button_vertical_padding"/> </shape> </item> </ripple> diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml index 467c20f3ffcd..665b4564ebf1 100644 --- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml @@ -16,8 +16,8 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:insetTop="@dimen/qs_dialog_button_vertical_inset" - android:insetBottom="@dimen/qs_dialog_button_vertical_inset"> + android:insetTop="@dimen/dialog_button_vertical_inset" + android:insetBottom="@dimen/dialog_button_vertical_inset"> <ripple android:color="?android:attr/colorControlHighlight"> <item android:id="@android:id/mask"> <shape android:shape="rectangle"> @@ -32,10 +32,10 @@ <stroke android:color="?androidprv:attr/colorAccentPrimary" android:width="1dp" /> - <padding android:left="@dimen/qs_dialog_button_horizontal_padding" - android:top="@dimen/qs_dialog_button_vertical_padding" - android:right="@dimen/qs_dialog_button_horizontal_padding" - android:bottom="@dimen/qs_dialog_button_vertical_padding"/> + <padding android:left="@dimen/dialog_button_horizontal_padding" + android:top="@dimen/dialog_button_vertical_padding" + android:right="@dimen/dialog_button_horizontal_padding" + android:bottom="@dimen/dialog_button_vertical_padding"/> </shape> </item> </ripple> diff --git a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml new file mode 100644 index 000000000000..a3e289a42d05 --- /dev/null +++ b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@*android:id/buttonPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scrollbarAlwaysDrawVerticalTrack="true" + android:scrollIndicators="top|bottom" + android:fillViewport="true" + android:paddingTop="@dimen/dialog_button_bar_top_padding" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" + android:paddingBottom="@dimen/dialog_bottom_padding" + style="?android:attr/buttonBarStyle"> + <com.android.internal.widget.ButtonBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layoutDirection="locale" + android:orientation="horizontal" + android:gravity="bottom"> + + <Button + android:id="@android:id/button3" + style="?android:attr/buttonBarNeutralButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <Space + android:id="@*android:id/spacer" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1" + android:visibility="invisible" /> + + <Button + android:id="@android:id/button2" + style="?android:attr/buttonBarNegativeButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <Button + android:id="@android:id/button1" + style="?android:attr/buttonBarPositiveButtonStyle" + android:layout_marginStart="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </com.android.internal.widget.ButtonBarLayout> +</ScrollView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml new file mode 100644 index 000000000000..f280cbd16a0f --- /dev/null +++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.internal.widget.AlertDialogLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@*android:id/parentPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal|top" + android:orientation="vertical" + android:paddingTop="@dimen/dialog_top_padding" + > + + <include layout="@layout/alert_dialog_title_systemui" /> + + <FrameLayout + android:id="@*android:id/contentPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="48dp" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" + > + + <ScrollView + android:id="@*android:id/scrollView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipToPadding="false"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <Space + android:id="@*android:id/textSpacerNoTitle" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="0dp" /> + + <TextView + android:id="@*android:id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/TextAppearance.Dialog.Body.Message" /> + + <Space + android:id="@*android:id/textSpacerNoButtons" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="6dp" /> + </LinearLayout> + </ScrollView> + </FrameLayout> + + <FrameLayout + android:id="@*android:id/customPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="48dp" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" + > + + <FrameLayout + android:id="@*android:id/custom" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </FrameLayout> + + <include + android:layout_width="match_parent" + android:layout_height="wrap_content" + layout="@layout/alert_dialog_button_bar_systemui" /> + +</com.android.internal.widget.AlertDialogLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml new file mode 100644 index 000000000000..480ba001fae1 --- /dev/null +++ b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:id="@*android:id/topPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" +> + + <!-- If the client uses a customTitle, it will be added here. --> + + <LinearLayout + android:id="@*android:id/title_template" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal|top"> + + <ImageView + android:id="@*android:id/icon" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_marginBottom="16dp" + android:scaleType="fitCenter" + android:src="@null" + android:tint="?androidprv:attr/colorAccentPrimaryVariant" + /> + + <com.android.internal.widget.DialogTitle + android:id="@*android:id/alertTitle" + android:singleLine="true" + android:ellipsize="end" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + style="@style/TextAppearance.Dialog.Title" /> + </LinearLayout> + + <Space + android:id="@*android:id/titleDividerNoCustom" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="0dp" /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index 86e2661f9534..98518c2bbf97 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -156,6 +156,14 @@ style="@style/InternetDialog.NetworkSummary"/> </LinearLayout> + <View + android:id="@+id/mobile_toggle_divider" + android:layout_width="1dp" + android:layout_height="28dp" + android:layout_marginEnd="16dp" + android:layout_gravity="center_vertical" + android:background="?android:attr/textColorSecondary"/> + <FrameLayout android:layout_width="@dimen/settingslib_switch_track_width" android:layout_height="48dp" @@ -367,8 +375,9 @@ android:id="@+id/done_layout" android:layout_width="67dp" android:layout_height="48dp" + android:layout_marginTop="8dp" android:layout_marginEnd="24dp" - android:layout_marginBottom="40dp" + android:layout_marginBottom="34dp" android:layout_gravity="end|center_vertical" android:clickable="true" android:focusable="true"> diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml index ee4530cb4377..9368a6a0b7da 100644 --- a/packages/SystemUI/res/layout/privacy_dialog.xml +++ b/packages/SystemUI/res/layout/privacy_dialog.xml @@ -22,10 +22,9 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/ongoing_appops_dialog_side_margins" android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins" - android:layout_marginTop="8dp" android:orientation="vertical" android:paddingBottom="8dp" - android:paddingTop="12dp" + android:paddingTop="8dp" android:paddingHorizontal="@dimen/ongoing_appops_dialog_side_padding" android:background="@drawable/qs_dialog_bg" /> diff --git a/packages/SystemUI/res/layout/qs_user_detail.xml b/packages/SystemUI/res/layout/qs_user_detail.xml index 91d3a53556a7..1aec296f4f5c 100644 --- a/packages/SystemUI/res/layout/qs_user_detail.xml +++ b/packages/SystemUI/res/layout/qs_user_detail.xml @@ -22,6 +22,6 @@ xmlns:sysui="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - sysui:verticalSpacing="4dp" + sysui:verticalSpacing="20dp" sysui:horizontalSpacing="4dp" style="@style/UserDetailView" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml index cc6c5d343502..91b11fcc3c26 100644 --- a/packages/SystemUI/res/layout/qs_user_detail_item.xml +++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml @@ -24,8 +24,6 @@ android:layout_height="wrap_content" android:orientation="vertical" android:gravity="top|center_horizontal" - android:paddingTop="16dp" - android:minHeight="112dp" android:clipChildren="false" android:clipToPadding="false" android:focusable="true" diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml index 9495ee6f3139..355df2c4448a 100644 --- a/packages/SystemUI/res/layout/qs_user_dialog_content.xml +++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml @@ -15,75 +15,19 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<androidx.constraintlayout.widget.ConstraintLayout +<FrameLayout 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" - 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" - /> - - <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" + android:id="@+id/grid" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + sysui:verticalSpacing="20dp" + sysui:horizontalSpacing="4dp" + sysui:fixedChildWidth="80dp" /> - - <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>
\ No newline at end of file +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 07e28b6d7f20..cb963e6ffc89 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -16,9 +16,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android"> - <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog"> - <item name="android:buttonCornerRadius">28dp</item> - </style> + <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Dialog"/> <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert" /> @@ -53,13 +51,17 @@ <style name="TextAppearance.InternetDialog.Active"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textSize">16sp</item> - <item name="android:textColor">@color/connected_network_primary_color</item> + <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> <item name="android:textDirection">locale</item> </style> <style name="TextAppearance.InternetDialog.Secondary.Active"> <item name="android:textSize">14sp</item> - <item name="android:textColor">@color/connected_network_secondary_color</item> + <item name="android:textColor">?android:attr/textColorSecondaryInverse</item> + </style> + + <style name="InternetDialog.Divider.Active"> + <item name="android:background">?android:attr/textColorSecondaryInverse</item> </style> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 2afbe8c648be..a2fd669c69fd 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1275,10 +1275,19 @@ <dimen name="drag_and_drop_icon_size">70dp</dimen> - <dimen name="qs_dialog_button_horizontal_padding">16dp</dimen> - <dimen name="qs_dialog_button_vertical_padding">8dp</dimen> + <!-- Dimensions for unified SystemUI dialogs styling. Used by Theme.SystemUI.Dialog and + alert_dialog_systemui.xml + --> + <dimen name="dialog_button_horizontal_padding">16dp</dimen> + <dimen name="dialog_button_vertical_padding">8dp</dimen> <!-- The button will be 48dp tall, but the background needs to be 36dp tall --> - <dimen name="qs_dialog_button_vertical_inset">6dp</dimen> + <dimen name="dialog_button_vertical_inset">6dp</dimen> + <dimen name="dialog_top_padding">24dp</dimen> + <dimen name="dialog_bottom_padding">18dp</dimen> + <dimen name="dialog_side_padding">24dp</dimen> + <dimen name="dialog_button_bar_top_padding">32dp</dimen> + + <!-- ************************************************************************* --> <dimen name="keyguard_unfold_translation_x">16dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 94d10decd269..3c7b53a8e6db 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -14,8 +14,8 @@ limitations under the License. --> -<resources xmlns:android="http://schemas.android.com/apk/res/android"> - <!-- NOTE: Adding the androidprv: namespace to this file will break the studio build. --> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <!-- HybridNotification themes and styles --> @@ -351,11 +351,19 @@ <item name="android:windowIsFloating">true</item> </style> - <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"> + <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/> + + <style name="Theme.SystemUI.Dialog" parent="@style/Theme.SystemUI.DayNightDialog"> <item name="android:buttonCornerRadius">28dp</item> - <item name="android:buttonBarPositiveButtonStyle">@style/Widget.QSDialog.Button</item> - <item name="android:buttonBarNegativeButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item> - <item name="android:buttonBarNeutralButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item> + <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button</item> + <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item> + <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item> + <item name="android:colorBackground">?androidprv:attr/colorSurface</item> + <item name="android:alertDialogStyle">@style/AlertDialogStyle</item> + </style> + + <style name="AlertDialogStyle" parent="@androidprv:style/AlertDialog.DeviceDefault"> + <item name="android:layout">@layout/alert_dialog_systemui</item> </style> <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" /> @@ -853,24 +861,37 @@ <item name="actionDividerHeight">32dp</item> </style> - <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog"> + <style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large"> <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textSize">24sp</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:lineHeight">32sp</item> + <item name="android:gravity">center</item> + <item name="android:textAlignment">center</item> + </style> + + <style name="TextAppearance.Dialog.Body" parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textSize">14sp</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:lineHeight">20sp</item> </style> - <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog"> + <style name="TextAppearance.Dialog.Body.Message"> + <item name="android:gravity">center</item> + <item name="android:textAlignment">center</item> + </style> + + <style name="Widget.Dialog.Button" parent = "Theme.SystemUI.Dialog"> <item name="android:background">@drawable/qs_dialog_btn_filled</item> - <item name="android:textColor">@color/prv_text_color_on_accent</item> + <item name="android:textColor">?androidprv:attr/textColorOnAccent</item> <item name="android:textSize">14sp</item> <item name="android:lineHeight">20sp</item> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> <item name="android:stateListAnimator">@null</item> - <item name="android:layout_marginHorizontal">4dp</item> </style> - <style name="Widget.QSDialog.Button.BorderButton"> + <style name="Widget.Dialog.Button.BorderButton"> <item name="android:background">@drawable/qs_dialog_btn_outline</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> @@ -947,4 +968,10 @@ <style name="TextAppearance.InternetDialog.Secondary.Active"/> + <style name="InternetDialog.Divider"> + <item name="android:background">?android:attr/textColorSecondary</item> + </style> + + <style name="InternetDialog.Divider.Active"/> + </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 8bd0f910dac3..01497516e0b1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -95,4 +95,9 @@ oneway interface IOverviewProxy { * Sent when screen turned on and ready to use (blocker scrim is hidden) */ void onScreenTurnedOn() = 21; + + /** + * Sent when the desired dark intensity of the nav buttons has changed + */ + void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 3128ffdbc67b..675dc9b533fb 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -28,6 +28,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.view.ViewDebug; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.io.PrintWriter; @@ -50,6 +51,7 @@ public class Task { @ViewDebug.ExportedProperty(category="recents") public int windowingMode; @ViewDebug.ExportedProperty(category="recents") + @NonNull public final Intent baseIntent; @ViewDebug.ExportedProperty(category="recents") public final int userId; @@ -83,7 +85,7 @@ public class Task { updateHashCode(); } - public TaskKey(int id, int windowingMode, Intent intent, + public TaskKey(int id, int windowingMode, @NonNull Intent intent, ComponentName sourceComponent, int userId, long lastActiveTime) { this.id = id; this.windowingMode = windowingMode; @@ -95,7 +97,7 @@ public class Task { updateHashCode(); } - public TaskKey(int id, int windowingMode, Intent intent, + public TaskKey(int id, int windowingMode, @NonNull Intent intent, ComponentName sourceComponent, int userId, long lastActiveTime, int displayId) { this.id = id; this.windowingMode = windowingMode; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 6154d84d5b37..8d98a7540a05 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -18,7 +18,6 @@ package com.android.systemui.shared.recents.utilities; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; import android.annotation.TargetApi; import android.content.Context; @@ -126,10 +125,9 @@ public class Utilities { final WindowManager windowManager = context.getSystemService(WindowManager.class); final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); - float originalSmallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()), + float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()), context.getResources().getConfiguration().densityDpi); - return dpiFromPx(Math.min(bounds.width(), bounds.height()), DENSITY_DEVICE_STABLE) - >= TABLET_MIN_DPS && originalSmallestWidth >= TABLET_MIN_DPS; + return smallestWidth >= TABLET_MIN_DPS; } public static float dpiFromPx(float size, int densityDpi) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index e24f07c21076..b56d189d3ab3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -801,13 +801,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT); } + boolean lockedOutStateChanged = false; if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { + lockedOutStateChanged |= !mFingerprintLockedOutPermanent; mFingerprintLockedOutPermanent = true; requireStrongAuthIfAllLockedOut(); } if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { + lockedOutStateChanged |= !mFingerprintLockedOut; mFingerprintLockedOut = true; if (isUdfpsEnrolled()) { updateFingerprintListeningState(); @@ -820,9 +823,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT); } } + + if (lockedOutStateChanged) { + notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT); + } } private void handleFingerprintLockoutReset() { + boolean changed = mFingerprintLockedOut || mFingerprintLockedOutPermanent; mFingerprintLockedOut = false; mFingerprintLockedOutPermanent = false; @@ -837,6 +845,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } else { updateFingerprintListeningState(); } + + if (changed) { + notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT); + } } private void setFingerprintRunningState(int fingerprintRunningState) { @@ -999,7 +1011,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + boolean lockedOutStateChanged = false; if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) { + lockedOutStateChanged = !mFaceLockedOutPermanent; mFaceLockedOutPermanent = true; requireStrongAuthIfAllLockedOut(); } @@ -1011,11 +1025,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab BiometricSourceType.FACE); } } + + if (lockedOutStateChanged) { + notifyLockedOutStateChanged(BiometricSourceType.FACE); + } } private void handleFaceLockoutReset() { + boolean changed = mFaceLockedOutPermanent; mFaceLockedOutPermanent = false; + updateFaceListeningState(); + + if (changed) { + notifyLockedOutStateChanged(BiometricSourceType.FACE); + } } private void setFaceRunningState(int faceRunningState) { @@ -1237,6 +1261,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + private void notifyLockedOutStateChanged(BiometricSourceType type) { + Assert.isMainThread(); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onLockedOutStateChanged(type); + } + } + } + public boolean isScreenOn() { return mScreenOn; } @@ -2454,6 +2488,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + public boolean isFingerprintLockedOut() { + return mFingerprintLockedOut || mFingerprintLockedOutPermanent; + } + /** * If biometrics hardware is available, not disabled, and user has enrolled templates. * This does NOT check if the device is encrypted or in lockdown. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 12431984c9b9..8170a81a09e6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -292,6 +292,11 @@ public class KeyguardUpdateMonitorCallback { public void onStrongAuthStateChanged(int userId) { } /** + * When the current user's locked out state changed. + */ + public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) { } + + /** * Called when the dream's window state is changed. * @param dreaming true if the dream's window has been created and is visible */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 223eb78044c4..8f4d6f6aa973 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -255,7 +255,6 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud private void maybeShowInputBouncer() { if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) { mKeyguardViewManager.showBouncer(true); - mKeyguardViewManager.resetAlternateAuth(false); } } diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index b45dc52585ad..10878dcc2474 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -42,7 +42,6 @@ import com.android.systemui.util.leak.LeakDetector; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; @@ -308,22 +307,22 @@ public class FragmentHostManager { return instantiateWithInjections(context, className, arguments); } - private Fragment instantiateWithInjections(Context context, String className, - Bundle args) { - Method method = mManager.getInjectionMap().get(className); - if (method != null) { + private Fragment instantiateWithInjections( + Context context, String className, Bundle args) { + FragmentService.FragmentInstantiationInfo fragmentInstantiationInfo = + mManager.getInjectionMap().get(className); + if (fragmentInstantiationInfo != null) { try { - Fragment f = (Fragment) method.invoke(mManager.getFragmentCreator()); + Fragment f = (Fragment) fragmentInstantiationInfo + .mMethod + .invoke(fragmentInstantiationInfo.mDaggerComponent); // Setup the args, taken from Fragment#instantiate. if (args != null) { args.setClassLoader(f.getClass().getClassLoader()); f.setArguments(args); } return f; - } catch (IllegalAccessException e) { - throw new Fragment.InstantiationException("Unable to instantiate " + className, - e); - } catch (InvocationTargetException e) { + } catch (IllegalAccessException | InvocationTargetException e) { throw new Fragment.InstantiationException("Unable to instantiate " + className, e); } diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java index 7f57fcc56117..2a5e653dd051 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java @@ -18,13 +18,13 @@ import android.app.Fragment; import android.content.res.Configuration; import android.os.Handler; import android.util.ArrayMap; +import android.util.Log; import android.view.View; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.qs.QSFragment; -import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.policy.ConfigurationController; import java.io.FileDescriptor; @@ -46,9 +46,14 @@ public class FragmentService implements Dumpable { private static final String TAG = "FragmentService"; private final ArrayMap<View, FragmentHostState> mHosts = new ArrayMap<>(); - private final ArrayMap<String, Method> mInjectionMap = new ArrayMap<>(); + /** + * A map with the means to create fragments via Dagger injection. + * + * key: the fragment class name. + * value: see {@link FragmentInstantiationInfo}. + */ + private final ArrayMap<String, FragmentInstantiationInfo> mInjectionMap = new ArrayMap<>(); private final Handler mHandler = new Handler(); - private final FragmentCreator mFragmentCreator; private ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @@ -65,26 +70,31 @@ public class FragmentService implements Dumpable { FragmentCreator.Factory fragmentCreatorFactory, ConfigurationController configurationController, DumpManager dumpManager) { - mFragmentCreator = fragmentCreatorFactory.build(); - initInjectionMap(); + addFragmentInstantiationProvider(fragmentCreatorFactory.build()); configurationController.addCallback(mConfigurationListener); dumpManager.registerDumpable(getClass().getSimpleName(), this); } - ArrayMap<String, Method> getInjectionMap() { + ArrayMap<String, FragmentInstantiationInfo> getInjectionMap() { return mInjectionMap; } - FragmentCreator getFragmentCreator() { - return mFragmentCreator; - } - - private void initInjectionMap() { - for (Method method : FragmentCreator.class.getDeclaredMethods()) { + /** + * Adds a new Dagger component object that provides method(s) to create fragments via injection. + */ + public void addFragmentInstantiationProvider(Object daggerComponent) { + for (Method method : daggerComponent.getClass().getDeclaredMethods()) { if (Fragment.class.isAssignableFrom(method.getReturnType()) && (method.getModifiers() & Modifier.PUBLIC) != 0) { - mInjectionMap.put(method.getReturnType().getName(), method); + String fragmentName = method.getReturnType().getName(); + if (mInjectionMap.containsKey(fragmentName)) { + Log.w(TAG, "Fragment " + fragmentName + " is already provided by different" + + " Dagger component; Not adding method"); + continue; + } + mInjectionMap.put( + fragmentName, new FragmentInstantiationInfo(method, daggerComponent)); } } } @@ -134,9 +144,6 @@ public class FragmentService implements Dumpable { * Inject a QSFragment. */ QSFragment createQSFragment(); - - /** Inject a CollapsedStatusBarFragment. */ - CollapsedStatusBarFragment createCollapsedStatusBarFragment(); } private class FragmentHostState { @@ -161,4 +168,16 @@ public class FragmentService implements Dumpable { mFragmentHostManager.onConfigurationChanged(newConfig); } } + + /** An object containing the information needed to instantiate a fragment. */ + static class FragmentInstantiationInfo { + /** The method that returns a newly-created fragment of the given class. */ + final Method mMethod; + /** The Dagger component that the method should be invoked on. */ + final Object mDaggerComponent; + FragmentInstantiationInfo(Method method, Object daggerComponent) { + this.mMethod = method; + this.mDaggerComponent = daggerComponent; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 7f9eae8bd55f..8d0733645117 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -793,7 +793,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; } else if (trust && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) { return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; - } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0) { + } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0 + || mUpdateMonitor.isFingerprintLockedOut())) { return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT; } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) { return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 47ef5e4c62fd..d54b1514abf7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -400,7 +400,7 @@ class MediaCarouselController @Inject constructor( } updatePageIndicator() mediaCarouselScrollHandler.onPlayersChanged() - mediaCarousel.requiresRemeasuring = true + mediaFrame.requiresRemeasuring = true // Check postcondition: mediaContent should have the same number of children as there are // elements in mediaPlayers. if (MediaPlayerData.players().size != mediaContent.childCount) { @@ -439,7 +439,7 @@ class MediaCarouselController @Inject constructor( updatePlayerToState(newRecs, noAnimation = true) reorderAllPlayers(curVisibleMediaKey) updatePageIndicator() - mediaCarousel.requiresRemeasuring = true + mediaFrame.requiresRemeasuring = true // Check postcondition: mediaContent should have the same number of children as there are // elements in mediaPlayers. if (MediaPlayerData.players().size != mediaContent.childCount) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java new file mode 100644 index 000000000000..52103d3bd739 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -0,0 +1,246 @@ +/* + * 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.navigationbar; + +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; + +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; +import android.view.accessibility.AccessibilityManager; + +import androidx.annotation.NonNull; + +import com.android.systemui.Dumpable; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; +import com.android.systemui.assist.AssistManager; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import dagger.Lazy; + +/** + * Extracts shared elements between navbar and taskbar delegate to de-dupe logic and help them + * experience the joys of friendship. + * The events are then passed through + * + * Currently consolidates + * * A11y + * * Assistant + */ +@SysUISingleton +public final class NavBarHelper implements + AccessibilityButtonModeObserver.ModeChangedListener, + OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener, + Dumpable { + private final AccessibilityManager mAccessibilityManager; + private final Lazy<AssistManager> mAssistManagerLazy; + private final UserTracker mUserTracker; + private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; + private final List<NavbarTaskbarStateUpdater> mA11yEventListeners = new ArrayList<>(); + private Context mContext; + private ContentResolver mContentResolver; + private boolean mAssistantAvailable; + private boolean mLongPressHomeEnabled; + private boolean mAssistantTouchGestureEnabled; + private int mNavBarMode; + + private final ContentObserver mAssistContentObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, Uri uri) { + updateAssitantAvailability(); + } + }; + + @Inject + public NavBarHelper(AccessibilityManager accessibilityManager, + AccessibilityManagerWrapper accessibilityManagerWrapper, + AccessibilityButtonModeObserver accessibilityButtonModeObserver, + OverviewProxyService overviewProxyService, + Lazy<AssistManager> assistManagerLazy, + NavigationModeController navigationModeController, + UserTracker userTracker, + DumpManager dumpManager) { + mAccessibilityManager = accessibilityManager; + mAssistManagerLazy = assistManagerLazy; + mUserTracker = userTracker; + accessibilityManagerWrapper.addCallback( + accessibilityManager1 -> NavBarHelper.this.dispatchA11yEventUpdate()); + mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; + + mAccessibilityButtonModeObserver.addListener(this); + mNavBarMode = navigationModeController.addListener(this); + overviewProxyService.addCallback(this); + dumpManager.registerDumpable(this); + } + + public void init(Context context) { + mContext = context; + mContentResolver = mContext.getContentResolver(); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), + false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED), + false, mAssistContentObserver, UserHandle.USER_ALL); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED), + false, mAssistContentObserver, UserHandle.USER_ALL); + updateAssitantAvailability(); + } + + public void destroy() { + mContentResolver.unregisterContentObserver(mAssistContentObserver); + } + + /** + * @param listener Will immediately get callbacks based on current state + */ + public void registerNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) { + mA11yEventListeners.add(listener); + listener.updateAccessibilityServicesState(); + listener.updateAssistantAvailable(mAssistantAvailable); + } + + public void removeNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) { + mA11yEventListeners.remove(listener); + } + + private void dispatchA11yEventUpdate() { + for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) { + listener.updateAccessibilityServicesState(); + } + } + + private void dispatchAssistantEventUpdate(boolean assistantAvailable) { + for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) { + listener.updateAssistantAvailable(assistantAvailable); + } + } + + @Override + public void onAccessibilityButtonModeChanged(int mode) { + dispatchA11yEventUpdate(); + } + + /** + * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and + * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE} + * + * @return the a11y button clickable and long_clickable states, or 0 if there is no + * a11y button in the navbar + */ + public int getA11yButtonState() { + // AccessibilityManagerService resolves services for the current user since the local + // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission + final List<String> a11yButtonTargets = + mAccessibilityManager.getAccessibilityShortcutTargets( + AccessibilityManager.ACCESSIBILITY_BUTTON); + final int requestingServices = a11yButtonTargets.size(); + + // If accessibility button is floating menu mode, click and long click state should be + // disabled. + if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode() + == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { + return 0; + } + + return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0) + | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0); + } + + @Override + public void onConnectionChanged(boolean isConnected) { + if (isConnected) { + updateAssitantAvailability(); + } + } + + private void updateAssitantAvailability() { + boolean assistantAvailableForUser = mAssistManagerLazy.get() + .getAssistInfoForUser(UserHandle.USER_CURRENT) != null; + boolean longPressDefault = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault); + mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver, + Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0, + mUserTracker.getUserId()) != 0; + boolean gestureDefault = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_assistTouchGestureEnabledDefault); + mAssistantTouchGestureEnabled = Settings.Secure.getIntForUser(mContentResolver, + Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0, + mUserTracker.getUserId()) != 0; + + mAssistantAvailable = assistantAvailableForUser + && mAssistantTouchGestureEnabled + && QuickStepContract.isGesturalMode(mNavBarMode); + dispatchAssistantEventUpdate(mAssistantAvailable); + } + + public boolean getLongPressHomeEnabled() { + return mLongPressHomeEnabled; + } + + @Override + public void startAssistant(Bundle bundle) { + mAssistManagerLazy.get().startAssist(bundle); + } + + @Override + public void onNavigationModeChanged(int mode) { + mNavBarMode = mode; + updateAssitantAvailability(); + } + + /** + * Callbacks will get fired once immediately after registering via + * {@link #registerNavTaskStateUpdater(NavbarTaskbarStateUpdater)} + */ + public interface NavbarTaskbarStateUpdater { + void updateAccessibilityServicesState(); + void updateAssistantAvailable(boolean available); + } + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("NavbarTaskbarFriendster"); + pw.println(" longPressHomeEnabled=" + mLongPressHomeEnabled); + pw.println(" mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled); + pw.println(" mAssistantAvailable=" + mAssistantAvailable); + pw.println(" mNavBarMode=" + mNavBarMode); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index bc023cc892a6..5141f25e4933 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -68,22 +68,18 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; -import android.database.ContentObserver; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.inputmethodservice.InputMethodService; -import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; -import android.provider.Settings; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.Log; @@ -200,8 +196,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private final Handler mHandler; private final NavigationBarOverlayController mNavbarOverlayController; private final UiEventLogger mUiEventLogger; - private final NavigationBarA11yHelper mNavigationBarA11yHelper; - private final UserTracker mUserTracker; + private final NavBarHelper mNavBarHelper; private final NotificationShadeDepthController mNotificationShadeDepthController; private Bundle mSavedState; @@ -213,9 +208,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private int mNavigationIconHints = 0; private @TransitionMode int mNavigationBarMode; private ContentResolver mContentResolver; - private boolean mAssistantAvailable; private boolean mLongPressHomeEnabled; - private boolean mAssistantTouchGestureEnabled; private int mDisabledFlags1; private int mDisabledFlags2; @@ -309,16 +302,31 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } }; + private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater = + new NavBarHelper.NavbarTaskbarStateUpdater() { + @Override + public void updateAccessibilityServicesState() { + updateAcessibilityStateFlags(); + } + + @Override + public void updateAssistantAvailable(boolean available) { + // TODO(b/198002034): Content observers currently can still be called back after + // being unregistered, and in this case we can ignore the change if the nav bar + // has been destroyed already + if (mNavigationBarView == null) { + return; + } + mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled(); + updateAssistantEntrypoints(available); + } + }; + private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() { @Override public void onConnectionChanged(boolean isConnected) { mNavigationBarView.updateStates(); updateScreenPinningGestures(); - - // Send the assistant availability upon connection - if (isConnected) { - updateAssistantEntrypoints(); - } } @Override @@ -421,20 +429,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } }; - private final ContentObserver mAssistContentObserver = new ContentObserver( - new Handler(Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange, Uri uri) { - // TODO(b/198002034): Content observers currently can still be called back after being - // unregistered, and in this case we can ignore the change if the nav bar has been - // destroyed already - if (mNavigationBarView == null) { - return; - } - updateAssistantEntrypoints(); - } - }; - private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -504,7 +498,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Main Handler mainHandler, NavigationBarOverlayController navbarOverlayController, UiEventLogger uiEventLogger, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, UserTracker userTracker, LightBarController mainLightBarController, LightBarController.Factory lightBarControllerFactory, @@ -535,8 +529,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mHandler = mainHandler; mNavbarOverlayController = navbarOverlayController; mUiEventLogger = uiEventLogger; - mNavigationBarA11yHelper = navigationBarA11yHelper; - mUserTracker = userTracker; + mNavBarHelper = navBarHelper; mNotificationShadeDepthController = notificationShadeDepthController; mMainLightBarController = mainLightBarController; mLightBarControllerFactory = lightBarControllerFactory; @@ -568,18 +561,9 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; mCommandQueue.addCallback(this); - mAssistantAvailable = mAssistManagerLazy.get() - .getAssistInfoForUser(UserHandle.USER_CURRENT) != null; + mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled(); mContentResolver = mContext.getContentResolver(); - mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), - false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL); - mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED), - false, mAssistContentObserver, UserHandle.USER_ALL); - mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED), - false, mAssistContentObserver, UserHandle.USER_ALL); + mNavBarHelper.init(mContext); mAllowForceNavBarHandleOpaque = mContext.getResources().getBoolean( R.bool.allow_force_nav_bar_handle_opaque); mForceNavBarHandleOpaque = DeviceConfig.getBoolean( @@ -593,7 +577,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, )).filter(duration -> duration != 0); DeviceConfig.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener); - updateAssistantEntrypoints(); if (savedState != null) { mDisabledFlags1 = savedState.getInt(EXTRA_DISABLE_STATE, 0); @@ -620,8 +603,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mWindowManager.removeViewImmediate(mNavigationBarView.getRootView()); mNavigationModeController.removeListener(this); - mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener); - mContentResolver.unregisterContentObserver(mAssistContentObserver); + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + mNavBarHelper.destroy(); mDeviceProvisionedController.removeCallback(mUserSetupListener); mNotificationShadeDepthController.removeListener(mDepthListener); @@ -643,7 +626,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mNavigationBarView.setWindowVisible(isNavBarWindowVisible()); mNavigationBarView.setBehavior(mBehavior); - mNavigationBarA11yHelper.registerA11yEventListener(mAccessibilityListener); + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener); mPipOptional.ifPresent(mNavigationBarView::registerPipExclusionBoundsChangeListener); @@ -716,7 +699,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mHandler.removeCallbacks(mAutoDim); mHandler.removeCallbacks(mOnVariableDurationHomeLongClick); mHandler.removeCallbacks(mEnableLayoutTransitions); - mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener); + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mFrame = null; mNavigationBarView = null; mOrientationHandle = null; @@ -885,7 +868,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, pw.println(" mCurrentRotation=" + mCurrentRotation); pw.println(" mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs); pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled); - pw.println(" mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled); pw.println(" mNavigationBarWindowState=" + windowStateToString(mNavigationBarWindowState)); pw.println(" mNavigationBarMode=" @@ -1165,7 +1147,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton(); accessibilityButton.setOnClickListener(this::onAccessibilityClick); accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick); - updateAccessibilityServicesState(); + updateAcessibilityStateFlags(); ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton(); imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick); @@ -1390,8 +1372,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, return true; } - void updateAccessibilityServicesState() { - int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState(); + void updateAcessibilityStateFlags() { + int a11yFlags = mNavBarHelper.getA11yButtonState(); if (mNavigationBarView != null) { boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; @@ -1403,7 +1385,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, public void updateSystemUiStateFlags(int a11yFlags) { if (a11yFlags < 0) { - a11yFlags = mNavigationBarA11yHelper.getA11yButtonState(); + a11yFlags = mNavBarHelper.getA11yButtonState(); } boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; @@ -1430,24 +1412,10 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } } - private void updateAssistantEntrypoints() { - mAssistantAvailable = mAssistManagerLazy.get() - .getAssistInfoForUser(UserHandle.USER_CURRENT) != null; - boolean longPressDefault = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault); - mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0, - mUserTracker.getUserId()) != 0; - boolean gestureDefault = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_assistTouchGestureEnabledDefault); - mAssistantTouchGestureEnabled = Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0, - mUserTracker.getUserId()) != 0; + private void updateAssistantEntrypoints(boolean assistantAvailable) { if (mOverviewProxyService.getProxy() != null) { try { - mOverviewProxyService.getProxy().onAssistantAvailable(mAssistantAvailable - && mAssistantTouchGestureEnabled - && QuickStepContract.isGesturalMode(mNavBarMode)); + mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable); } catch (RemoteException e) { Log.w(TAG, "Unable to send assistant availability data to launcher"); } @@ -1518,8 +1486,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Override public void onNavigationModeChanged(int mode) { mNavBarMode = mode; - // update assistant entry points on system navigation radio button click - updateAssistantEntrypoints(); if (!QuickStepContract.isGesturalMode(mode)) { // Reset the override alpha @@ -1558,9 +1524,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mNavigationBarView.getBarTransitions().finishAnimations(); } - private final NavigationBarA11yHelper.NavA11yEventListener mAccessibilityListener = - this::updateAccessibilityServicesState; - private WindowManager.LayoutParams getBarLayoutParams(int rotation) { WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation); lp.paramsForRotation = new WindowManager.LayoutParams[4]; @@ -1668,7 +1631,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } if (Intent.ACTION_USER_SWITCHED.equals(action)) { // The accessibility settings may be different for the new user - updateAccessibilityServicesState(); + updateAcessibilityStateFlags(); } } }; @@ -1704,7 +1667,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private final Handler mMainHandler; private final NavigationBarOverlayController mNavbarOverlayController; private final UiEventLogger mUiEventLogger; - private final NavigationBarA11yHelper mNavigationBarA11yHelper; + private final NavBarHelper mNavBarHelper; private final UserTracker mUserTracker; private final LightBarController mMainLightBarController; private final LightBarController.Factory mLightBarControllerFactory; @@ -1737,7 +1700,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Main Handler mainHandler, NavigationBarOverlayController navbarOverlayController, UiEventLogger uiEventLogger, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, UserTracker userTracker, LightBarController mainLightBarController, LightBarController.Factory lightBarControllerFactory, @@ -1767,7 +1730,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mMainHandler = mainHandler; mNavbarOverlayController = navbarOverlayController; mUiEventLogger = uiEventLogger; - mNavigationBarA11yHelper = navigationBarA11yHelper; + mNavBarHelper = navBarHelper; mUserTracker = userTracker; mMainLightBarController = mainLightBarController; mLightBarControllerFactory = lightBarControllerFactory; @@ -1788,7 +1751,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mSplitScreenOptional, mRecentsOptional, mStatusBarOptionalLazy, mShadeController, mNotificationRemoteInputManager, mNotificationShadeDepthController, mSystemActions, mMainHandler, - mNavbarOverlayController, mUiEventLogger, mNavigationBarA11yHelper, + mNavbarOverlayController, mUiEventLogger, mNavBarHelper, mUserTracker, mMainLightBarController, mLightBarControllerFactory, mMainAutoHideController, mAutoHideControllerFactory, mTelecomManagerOptional, mInputMethodManager); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java deleted file mode 100644 index 13e6d8b410d6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.android.systemui.navigationbar; - -import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; - -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; - -import android.view.accessibility.AccessibilityManager; - -import com.android.systemui.accessibility.AccessibilityButtonModeObserver; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -/** - * Extracts shared elements of a11y necessary between navbar and taskbar delegate - */ -@SysUISingleton -public final class NavigationBarA11yHelper implements - AccessibilityButtonModeObserver.ModeChangedListener { - private final AccessibilityManager mAccessibilityManager; - private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; - private final List<NavA11yEventListener> mA11yEventListeners = new ArrayList<>(); - - @Inject - public NavigationBarA11yHelper(AccessibilityManager accessibilityManager, - AccessibilityManagerWrapper accessibilityManagerWrapper, - AccessibilityButtonModeObserver accessibilityButtonModeObserver) { - mAccessibilityManager = accessibilityManager; - accessibilityManagerWrapper.addCallback( - accessibilityManager1 -> NavigationBarA11yHelper.this.dispatchEventUpdate()); - mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; - - mAccessibilityButtonModeObserver.addListener(this); - } - - public void registerA11yEventListener(NavA11yEventListener listener) { - mA11yEventListeners.add(listener); - } - - public void removeA11yEventListener(NavA11yEventListener listener) { - mA11yEventListeners.remove(listener); - } - - private void dispatchEventUpdate() { - for (NavA11yEventListener listener : mA11yEventListeners) { - listener.updateAccessibilityServicesState(); - } - } - - @Override - public void onAccessibilityButtonModeChanged(int mode) { - dispatchEventUpdate(); - } - - /** - * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and - * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE} - * - * @return the a11y button clickable and long_clickable states, or 0 if there is no - * a11y button in the navbar - */ - public int getA11yButtonState() { - // AccessibilityManagerService resolves services for the current user since the local - // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission - final List<String> a11yButtonTargets = - mAccessibilityManager.getAccessibilityShortcutTargets( - AccessibilityManager.ACCESSIBILITY_BUTTON); - final int requestingServices = a11yButtonTargets.size(); - - // If accessibility button is floating menu mode, click and long click state should be - // disabled. - if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode() - == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { - return 0; - } - - return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0) - | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0); - } - - public interface NavA11yEventListener { - void updateAccessibilityServicesState(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 3dc79c43d894..0429c022234d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -57,6 +57,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import java.io.FileDescriptor; @@ -100,11 +101,12 @@ public class NavigationBarController implements CommandQueue commandQueue, @Main Handler mainHandler, ConfigurationController configurationController, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, TaskbarDelegate taskbarDelegate, NavigationBar.Factory navigationBarFactory, DumpManager dumpManager, - AutoHideController autoHideController) { + AutoHideController autoHideController, + LightBarController lightBarController) { mContext = context; mHandler = mainHandler; mNavigationBarFactory = navigationBarFactory; @@ -115,8 +117,8 @@ public class NavigationBarController implements mNavMode = navigationModeController.addListener(this); mTaskbarDelegate = taskbarDelegate; mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService, - navigationBarA11yHelper, navigationModeController, sysUiFlagsContainer, - dumpManager, autoHideController); + navBarHelper, navigationModeController, sysUiFlagsContainer, + dumpManager, autoHideController, lightBarController); mIsTablet = isTablet(mContext); dumpManager.registerDumpable(this); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index d707dbdf5cba..3d58a5a81251 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -43,6 +43,8 @@ import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.inputmethodservice.InputMethodService; import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; import android.view.Display; import android.view.InsetsVisibilities; import android.view.View; @@ -62,6 +64,9 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; +import com.android.systemui.statusbar.phone.BarTransitions; +import com.android.systemui.statusbar.phone.LightBarController; +import com.android.systemui.statusbar.phone.LightBarTransitionsController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -73,19 +78,32 @@ import javax.inject.Singleton; public class TaskbarDelegate implements CommandQueue.Callbacks, OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener, ComponentCallbacks, Dumpable { + private static final String TAG = TaskbarDelegate.class.getSimpleName(); private final EdgeBackGestureHandler mEdgeBackGestureHandler; - + private boolean mInitialized; private CommandQueue mCommandQueue; private OverviewProxyService mOverviewProxyService; - private NavigationBarA11yHelper mNavigationBarA11yHelper; + private NavBarHelper mNavBarHelper; private NavigationModeController mNavigationModeController; private SysUiState mSysUiState; private AutoHideController mAutoHideController; + private LightBarController mLightBarController; + private LightBarTransitionsController mLightBarTransitionsController; private int mDisplayId; private int mNavigationIconHints; - private final NavigationBarA11yHelper.NavA11yEventListener mNavA11yEventListener = - this::updateSysuiFlags; + private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater = + new NavBarHelper.NavbarTaskbarStateUpdater() { + @Override + public void updateAccessibilityServicesState() { + updateSysuiFlags(); + } + + @Override + public void updateAssistantAvailable(boolean available) { + updateAssistantAvailability(available); + } + }; private int mDisabledFlags; private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING; private @Behavior int mBehavior; @@ -125,27 +143,56 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, public void setDependencies(CommandQueue commandQueue, OverviewProxyService overviewProxyService, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, NavigationModeController navigationModeController, SysUiState sysUiState, DumpManager dumpManager, - AutoHideController autoHideController) { + AutoHideController autoHideController, + LightBarController lightBarController) { // TODO: adding this in the ctor results in a dagger dependency cycle :( mCommandQueue = commandQueue; mOverviewProxyService = overviewProxyService; - mNavigationBarA11yHelper = navigationBarA11yHelper; + mNavBarHelper = navBarHelper; mNavigationModeController = navigationModeController; mSysUiState = sysUiState; dumpManager.registerDumpable(this); mAutoHideController = autoHideController; + mLightBarController = lightBarController; + mLightBarTransitionsController = createLightBarTransitionsController(); + } + + // Separated into a method to keep setDependencies() clean/readable. + private LightBarTransitionsController createLightBarTransitionsController() { + return new LightBarTransitionsController(mContext, + new LightBarTransitionsController.DarkIntensityApplier() { + @Override + public void applyDarkIntensity(float darkIntensity) { + mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity); + } + + @Override + public int getTintAnimationDuration() { + return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION; + } + }, mCommandQueue) { + @Override + public boolean supportsIconTintForNavMode(int navigationMode) { + // Always tint taskbar nav buttons (region sampling handles gesture bar separately). + return true; + } + }; } public void init(int displayId) { + if (mInitialized) { + return; + } mDisplayId = displayId; mCommandQueue.addCallback(this); mOverviewProxyService.addCallback(this); mEdgeBackGestureHandler.onNavigationModeChanged( mNavigationModeController.addListener(this)); - mNavigationBarA11yHelper.registerA11yEventListener(mNavA11yEventListener); + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + mNavBarHelper.init(mContext); mEdgeBackGestureHandler.onNavBarAttached(); // Initialize component callback Display display = mDisplayManager.getDisplay(displayId); @@ -154,23 +201,32 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, // Set initial state for any listeners updateSysuiFlags(); mAutoHideController.setNavigationBar(mAutoHideUiElement); + mLightBarController.setNavigationBar(mLightBarTransitionsController); + mInitialized = true; } public void destroy() { + if (!mInitialized) { + return; + } mCommandQueue.removeCallback(this); mOverviewProxyService.removeCallback(this); mNavigationModeController.removeListener(this); - mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener); + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + mNavBarHelper.destroy(); mEdgeBackGestureHandler.onNavBarDetached(); if (mWindowContext != null) { mWindowContext.unregisterComponentCallbacks(this); mWindowContext = null; } mAutoHideController.setNavigationBar(null); + mLightBarTransitionsController.destroy(mContext); + mLightBarController.setNavigationBar(null); + mInitialized = false; } private void updateSysuiFlags() { - int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState(); + int a11yFlags = mNavBarHelper.getA11yButtonState(); boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; @@ -194,6 +250,18 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, .commitUpdate(mDisplayId); } + private void updateAssistantAvailability(boolean assistantAvailable) { + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable); + } catch (RemoteException e) { + Log.e(TAG, "onAssistantAvailable() failed, available: " + assistantAvailable, e); + } + } + @Override public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { @@ -233,6 +301,10 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities, String packageName) { mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior); + if (mLightBarController != null && displayId == mDisplayId) { + mLightBarController.onNavigationBarAppearanceChanged(appearance, false/*nbModeChanged*/, + BarTransitions.MODE_TRANSPARENT /*navigationBarMode*/, navbarColorManagedByIme); + } if (mBehavior != behavior) { mBehavior = behavior; updateSysuiFlags(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java index 2f189beb7780..768598af6885 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java @@ -133,10 +133,7 @@ public class PseudoGridView extends ViewGroup { x += width + mHorizontalSpacing; } } - y += maxHeight; - if (row > 0) { - y += mVerticalSpacing; - } + y += maxHeight + mVerticalSpacing; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 71eb4a2e6cbb..d69deefc3477 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; @@ -103,6 +104,8 @@ public class QSPanel extends LinearLayout implements Tunable { protected LinearLayout mHorizontalContentContainer; protected QSTileLayout mTileLayout; + private float mSquishinessFraction = 1f; + private final ArrayMap<View, Integer> mChildrenLayoutTop = new ArrayMap<>(); public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -179,10 +182,26 @@ public class QSPanel extends LinearLayout implements Tunable { if (mTileLayout == null) { mTileLayout = (QSTileLayout) LayoutInflater.from(mContext) .inflate(R.layout.qs_paged_tile_layout, this, false); + mTileLayout.setSquishinessFraction(mSquishinessFraction); } return mTileLayout; } + public void setSquishinessFraction(float squishinessFraction) { + if (Float.compare(squishinessFraction, mSquishinessFraction) == 0) { + return; + } + mSquishinessFraction = squishinessFraction; + if (mTileLayout == null) { + return; + } + mTileLayout.setSquishinessFraction(squishinessFraction); + if (getMeasuredWidth() == 0) { + return; + } + updateViewPositions(); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mTileLayout instanceof PagedTileLayout) { @@ -228,6 +247,39 @@ public class QSPanel extends LinearLayout implements Tunable { setMeasuredDimension(getMeasuredWidth(), height); } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + mChildrenLayoutTop.put(child, child.getTop()); + } + updateViewPositions(); + } + + private void updateViewPositions() { + if (!(mTileLayout instanceof TileLayout)) { + return; + } + TileLayout layout = (TileLayout) mTileLayout; + + // Adjust view positions based on tile squishing + int tileHeightOffset = layout.getTilesHeight() - layout.getHeight(); + + boolean move = false; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (move) { + int top = mChildrenLayoutTop.get(child); + child.setLeftTopRightBottom(child.getLeft(), top + tileHeightOffset, + child.getRight(), top + tileHeightOffset + child.getHeight()); + } + if (child == mTileLayout) { + move = true; + } + } + } + protected String getDumpableTag() { return TAG; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index f7d1b1e2f5eb..eddc206db231 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -147,6 +147,10 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr return mMediaHost; } + public void setSquishinessFraction(float squishinessFraction) { + mView.setSquishinessFraction(squishinessFraction); + } + @Override protected void onViewAttached() { mQsTileRevealController = createTileRevealController(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt index c1c146d40e38..c680cb5d18a4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt @@ -1,14 +1,10 @@ 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 javax.inject.Inject -import javax.inject.Named @QSScope class QSSquishinessController @Inject constructor( - @Named(QQS_FOOTER) private val qqsFooterActionsView: FooterActionsView, private val qsAnimator: QSAnimator, private val qsPanelController: QSPanelController, private val quickQSPanelController: QuickQSPanelController @@ -33,23 +29,7 @@ class QSSquishinessController @Inject constructor( * Change the height of all tiles and repositions their siblings. */ private fun updateSquishiness() { - (qsPanelController.tileLayout as QSPanel.QSTileLayout).setSquishinessFraction(squishiness) - val tileLayout = quickQSPanelController.tileLayout as TileLayout - tileLayout.setSquishinessFraction(squishiness) - - // Calculate how much we should move the footer - val tileHeightOffset = tileLayout.height - tileLayout.tilesHeight - val footerTopMargin = (qqsFooterActionsView.layoutParams as ViewGroup.MarginLayoutParams) - .topMargin - val nextTop = tileLayout.bottom - tileHeightOffset + footerTopMargin - val amountMoved = nextTop - qqsFooterActionsView.top - - // Move the footer and other siblings (MediaPlayer) - (qqsFooterActionsView.parent as ViewGroup?)?.let { parent -> - val index = parent.indexOfChild(qqsFooterActionsView) - for (i in index until parent.childCount) { - parent.getChildAt(i).top += amountMoved - } - } + qsPanelController.setSquishinessFraction(squishiness) + quickQSPanelController.setSquishinessFraction(squishiness) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 2bd5c8f0fc20..9d60e63032c3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -247,7 +247,10 @@ open class QSTileViewImpl @JvmOverloads constructor( } else { measuredHeight } - bottom = top + (actualHeight * squishinessFraction).toInt() + // Limit how much we affect the height, so we don't have rounding artifacts when the tile + // is too short. + val constrainedSquishiness = 0.1f + squishinessFraction * 0.9f + bottom = top + (actualHeight * constrainedSquishiness).toInt() scrollY = (actualHeight - height) / 2 } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index b1cd68e5185c..3163c5f5a3c9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_OFF; +import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.Intent; @@ -41,7 +42,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; -import android.view.WindowManager; import android.widget.Switch; import android.widget.Toast; @@ -53,6 +53,7 @@ import com.android.settingslib.notification.EnableZenModeDialog; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.SysUIToast; +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; @@ -84,6 +85,7 @@ public class DndTile extends QSTileImpl<BooleanState> { private final DndDetailAdapter mDetailAdapter; private final SharedPreferences mSharedPreferences; private final SecureSetting mSettingZenDuration; + private final DialogLaunchAnimator mDialogLaunchAnimator; private boolean mListening; private boolean mShowingDetail; @@ -100,7 +102,8 @@ public class DndTile extends QSTileImpl<BooleanState> { QSLogger qsLogger, ZenModeController zenModeController, @Main SharedPreferences sharedPreferences, - SecureSettings secureSettings + SecureSettings secureSettings, + DialogLaunchAnimator dialogLaunchAnimator ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); @@ -108,6 +111,7 @@ public class DndTile extends QSTileImpl<BooleanState> { mSharedPreferences = sharedPreferences; mDetailAdapter = new DndDetailAdapter(); mController.observe(getLifecycle(), mZenCallback); + mDialogLaunchAnimator = dialogLaunchAnimator; mSettingZenDuration = new SecureSetting(secureSettings, mUiHandler, Settings.Secure.ZEN_DURATION, getHost().getUserId()) { @Override @@ -117,8 +121,6 @@ public class DndTile extends QSTileImpl<BooleanState> { }; } - - public static void setVisible(Context context, boolean visible) { Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible); } @@ -187,14 +189,17 @@ public class DndTile extends QSTileImpl<BooleanState> { switch (zenDuration) { case Settings.Secure.ZEN_DURATION_PROMPT: mUiHandler.post(() -> { - Dialog mDialog = new EnableZenModeDialog(mContext).createDialog(); - mDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - SystemUIDialog.setShowForAllUsers(mDialog, true); - SystemUIDialog.registerDismissListener(mDialog); - SystemUIDialog.setWindowOnTop(mDialog); - mUiHandler.post(() -> mDialog.show()); - mHost.collapsePanels(); + Dialog dialog = makeZenModeDialog(); + if (view != null) { + final Dialog hostDialog = + mDialogLaunchAnimator.showFromView(dialog, view, false); + setDialogListeners(dialog, hostDialog); + } else { + // If we are not launching with animator, register default + // dismiss listener + SystemUIDialog.registerDismissListener(dialog); + dialog.show(); + } }); break; case Settings.Secure.ZEN_DURATION_FOREVER: @@ -209,6 +214,20 @@ public class DndTile extends QSTileImpl<BooleanState> { } } + private Dialog makeZenModeDialog() { + AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog) + .createDialog(); + SystemUIDialog.applyFlags(dialog); + SystemUIDialog.setShowForAllUsers(dialog, true); + return dialog; + } + + private void setDialogListeners(Dialog zenModeDialog, Dialog hostDialog) { + // Zen mode dialog is never hidden. + SystemUIDialog.registerDismissListener(zenModeDialog, hostDialog::dismiss); + zenModeDialog.setOnCancelListener(dialog -> hostDialog.cancel()); + } + @Override protected void handleSecondaryClick(@Nullable View view) { if (mController.isVolumeRestricted()) { 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 77b9cc14fa6d..883552a1f7c3 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 @@ -56,6 +56,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; +import com.android.settingslib.Utils; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; @@ -120,6 +121,7 @@ public class InternetDialog extends SystemUIDialog implements private TextView mMobileTitleText; private TextView mMobileSummaryText; private Switch mMobileDataToggle; + private View mMobileToggleDivider; private Switch mWiFiToggle; private FrameLayout mDoneLayout; private Drawable mBackgroundOn; @@ -207,6 +209,7 @@ public class InternetDialog extends SystemUIDialog implements mSignalIcon = mDialogView.requireViewById(R.id.signal_icon); mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title); mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary); + mMobileToggleDivider = mDialogView.requireViewById(R.id.mobile_toggle_divider); mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle); mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle); mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on); @@ -378,6 +381,14 @@ public class InternetDialog extends SystemUIDialog implements mMobileNetworkLayout.setBackground( isCarrierNetworkConnected ? mBackgroundOn : mBackgroundOff); + TypedArray array = mContext.obtainStyledAttributes( + R.style.InternetDialog_Divider_Active, new int[]{android.R.attr.background}); + int dividerColor = Utils.getColorAttrDefaultColor(mContext, + android.R.attr.textColorSecondary); + mMobileToggleDivider.setBackgroundColor(isCarrierNetworkConnected + ? array.getColor(0, dividerColor) : dividerColor); + array.recycle(); + mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt deleted file mode 100644 index 26d1bbde2a54..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt +++ /dev/null @@ -1,82 +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.qs.user - -import android.content.Context -import android.os.Bundle -import android.view.Gravity -import android.view.View -import android.view.ViewGroup -import android.view.WindowInsets -import android.view.WindowManager -import com.android.systemui.qs.PseudoGridView -import com.android.systemui.statusbar.phone.SystemUIDialog -import com.android.systemui.R - -/** - * Dialog for switching users or creating new ones. - */ -class UserDialog( - context: Context -) : SystemUIDialog(context) { - - // create() is no-op after creation - private lateinit var _doneButton: View - /** - * Button with text "Done" in dialog. - */ - val doneButton: View - get() { - create() - return _doneButton - } - - private lateinit var _settingsButton: View - /** - * Button with text "User Settings" in dialog. - */ - val settingsButton: View - get() { - create() - return _settingsButton - } - - private lateinit var _grid: PseudoGridView - /** - * Grid to populate with user avatar from adapter - */ - val grid: ViewGroup - get() { - create() - return _grid - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - window?.apply { - setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) - attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars() - attributes.receiveInsetsIgnoringZOrder = true - setGravity(Gravity.CENTER) - } - setContentView(R.layout.qs_user_dialog_content) - - _doneButton = requireViewById(R.id.done) - _settingsButton = requireViewById(R.id.settings) - _grid = requireViewById(R.id.grid) - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt index d74a50e24ed3..00e04540fd94 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt @@ -21,13 +21,16 @@ import android.content.Context import android.content.DialogInterface import android.content.Intent import android.provider.Settings +import android.view.LayoutInflater import android.view.View import androidx.annotation.VisibleForTesting +import com.android.systemui.R import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.tiles.UserDetailView +import com.android.systemui.statusbar.phone.SystemUIDialog import javax.inject.Inject import javax.inject.Provider @@ -40,7 +43,7 @@ class UserSwitchDialogController @VisibleForTesting constructor( private val activityStarter: ActivityStarter, private val falsingManager: FalsingManager, private val dialogLaunchAnimator: DialogLaunchAnimator, - private val dialogFactory: (Context) -> UserDialog + private val dialogFactory: (Context) -> SystemUIDialog ) { @Inject @@ -54,7 +57,7 @@ class UserSwitchDialogController @VisibleForTesting constructor( activityStarter, falsingManager, dialogLaunchAnimator, - { UserDialog(it) } + { SystemUIDialog(it) } ) companion object { @@ -71,9 +74,10 @@ class UserSwitchDialogController @VisibleForTesting constructor( with(dialogFactory(view.context)) { setShowForAllUsers(true) setCanceledOnTouchOutside(true) - create() // Needs to be called before we can retrieve views - settingsButton.setOnClickListener { + setTitle(R.string.qs_user_switch_dialog_title) + setPositiveButton(R.string.quick_settings_done, null) + setNeutralButton(R.string.quick_settings_more_user_settings) { _, _ -> if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() activityStarter.postStartActivityDismissingKeyguard( @@ -81,12 +85,14 @@ class UserSwitchDialogController @VisibleForTesting constructor( 0 ) } - dismiss() } - doneButton.setOnClickListener { dismiss() } + val gridFrame = LayoutInflater.from(this.context) + .inflate(R.layout.qs_user_dialog_content, null) + setView(gridFrame) val adapter = userDetailViewAdapterProvider.get() - adapter.linkToViewGroup(grid) + + adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid)) val hostDialog = dialogLaunchAnimator.showFromView(this, view) adapter.injectDialogShower(DialogShowerImpl(hostDialog, dialogLaunchAnimator)) diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index fa874b19c2cc..3ed7e84af020 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -986,6 +986,18 @@ public class OverviewProxyService extends CurrentUserTracker implements } } + public void onNavButtonsDarkIntensityChanged(float darkIntensity) { + try { + if (mOverviewProxy != null) { + mOverviewProxy.onNavButtonsDarkIntensityChanged(darkIntensity); + } else { + Log.e(TAG_OPS, "Failed to get overview proxy to update nav buttons dark intensity"); + } + } catch (RemoteException e) { + Log.e(TAG_OPS, "Failed to call onNavButtonsDarkIntensityChanged()", e); + } + } + private void updateEnabledState() { final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId(); mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 6676901997bf..5876703a800e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -513,9 +513,13 @@ public class CommandQueue extends IStatusBar.Stub implements * @param animate {@code true} to show animations. */ public void recomputeDisableFlags(int displayId, boolean animate) { - int disabled1 = getDisabled1(displayId); - int disabled2 = getDisabled2(displayId); - disable(displayId, disabled1, disabled2, animate); + // This must update holding the lock otherwise it can clobber the disabled flags set on the + // binder thread from the disable() call + synchronized (mLock) { + int disabled1 = getDisabled1(displayId); + int disabled2 = getDisabled2(displayId); + disable(displayId, disabled1, disabled2, animate); + } } private void setDisabled(int displayId, int disabled1, int disabled2) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index 664776975b24..a12fd4a644e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -444,8 +444,8 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc final int height = (view instanceof ExpandableView) ? ((ExpandableView) view).getActualHeight() : view.getHeight(); - final int rx = (int) ev.getX(); - final int ry = (int) ev.getY(); + final int rx = (int) ev.getRawX(); + final int ry = (int) ev.getRawY(); int[] temp = new int[2]; view.getLocationOnScreen(temp); final int x = temp[0]; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 67f51cb9ab43..aa3b3e12b8f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.content.Context; import android.content.res.Resources; import android.hardware.biometrics.BiometricSourceType; +import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.os.Handler; import android.os.PowerManager; @@ -46,9 +47,11 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.KeyguardStateController; import java.io.FileDescriptor; @@ -71,6 +74,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000; private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock"; private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); + private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3; @IntDef(prefix = { "MODE_" }, value = { MODE_NONE, @@ -167,6 +171,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final MetricsLogger mMetricsLogger; private final AuthController mAuthController; + private final StatusBarStateController mStatusBarStateController; + + private long mLastFpFailureUptimeMillis; + private int mNumConsecutiveFpFailures; private static final class PendingAuthenticated { public final int userId; @@ -209,7 +217,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp BIOMETRIC_IRIS_FAILURE(403), @UiEvent(doc = "A biometric event of type iris errored.") - BIOMETRIC_IRIS_ERROR(404); + BIOMETRIC_IRIS_ERROR(404), + + @UiEvent(doc = "Bouncer was shown as a result of consecutive failed UDFPS attempts.") + BIOMETRIC_BOUNCER_SHOWN(916); private final int mId; @@ -257,7 +268,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp NotificationMediaManager notificationMediaManager, WakefulnessLifecycle wakefulnessLifecycle, ScreenLifecycle screenLifecycle, - AuthController authController) { + AuthController authController, + StatusBarStateController statusBarStateController) { mContext = context; mPowerManager = powerManager; mShadeController = shadeController; @@ -279,6 +291,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mKeyguardBypassController.setUnlockController(this); mMetricsLogger = metricsLogger; mAuthController = authController; + mStatusBarStateController = statusBarStateController; dumpManager.registerDumpable(getClass().getName(), this); } @@ -620,6 +633,22 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp .setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType))); Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType)) .ifPresent(UI_EVENT_LOGGER::log); + + long currUptimeMillis = SystemClock.uptimeMillis(); + if (currUptimeMillis - mLastFpFailureUptimeMillis < 2000) { // attempt within 2 seconds + mNumConsecutiveFpFailures += 1; + } else { + mNumConsecutiveFpFailures = 1; + } + mLastFpFailureUptimeMillis = currUptimeMillis; + + if (biometricSourceType.equals(BiometricSourceType.FINGERPRINT) + && mUpdateMonitor.isUdfpsSupported() + && mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) { + mKeyguardViewController.showBouncer(true); + UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN); + mNumConsecutiveFpFailures = 0; + } cleanup(); } @@ -631,6 +660,16 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp .addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId)); Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType)) .ifPresent(UI_EVENT_LOGGER::log); + + // if we're on the shade and we're locked out, immediately show the bouncer + if (biometricSourceType == BiometricSourceType.FINGERPRINT + && (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT + || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) + && mUpdateMonitor.isUdfpsSupported() + && (mStatusBarStateController.getState() == StatusBarState.SHADE + || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) { + mKeyguardViewController.showBouncer(true); + } cleanup(); } @@ -664,6 +703,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mBiometricModeListener.onResetMode(); mBiometricModeListener.notifyBiometricAuthModeChanged(); } + mNumConsecutiveFpFailures = 0; + mLastFpFailureUptimeMillis = 0; } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 353868ba969f..9647486be992 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -21,6 +21,7 @@ import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import android.content.Context; import android.content.res.ColorStateList; +import android.hardware.biometrics.BiometricSourceType; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; @@ -81,6 +82,13 @@ public class KeyguardBouncer { public void onStrongAuthStateChanged(int userId) { mBouncerPromptReason = mCallback.getBouncerPromptReason(); } + + @Override + public void onLockedOutStateChanged(BiometricSourceType type) { + if (type == BiometricSourceType.FINGERPRINT) { + mBouncerPromptReason = mCallback.getBouncerPromptReason(); + } + } }; private final Runnable mRemoveViewRunnable = this::removeView; private final KeyguardBypassController mKeyguardBypassController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index 570b0ca3564c..88ae0db5bad0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -37,7 +37,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.policy.BatteryController; import java.io.FileDescriptor; @@ -251,7 +250,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private void updateNavigation() { if (mNavigationBarController != null - && !QuickStepContract.isGesturalMode(mNavigationMode)) { + && mNavigationBarController.supportsIconTintForNavMode(mNavigationMode)) { mNavigationBarController.setIconsDark(mNavigationLight, animateChange()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java index 9021b74d1518..415fb92e37ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java @@ -28,6 +28,7 @@ import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.statusbar.StatusBarStateController; +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.policy.KeyguardStateController; @@ -230,6 +231,14 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, } /** + * Return whether to use the tint calculated in this class for nav icons. + */ + public boolean supportsIconTintForNavMode(int navigationMode) { + // In gesture mode, we already do region sampling to update tint based on content beneath. + return !QuickStepContract.isGesturalMode(navigationMode); + } + + /** * Interface to apply a specific dark intensity. */ public interface DarkIntensityApplier { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt index c814622ff074..a4feeab48e6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt @@ -25,6 +25,7 @@ import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.flags.FeatureFlags import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope +import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.SPLIT_SHADE_BATTERY_CONTROLLER import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.SPLIT_SHADE_HEADER import javax.inject.Inject import javax.inject.Named @@ -35,7 +36,7 @@ class SplitShadeHeaderController @Inject constructor( private val statusBarIconController: StatusBarIconController, qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder, featureFlags: FeatureFlags, - batteryMeterViewController: BatteryMeterViewController + @Named(SPLIT_SHADE_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController ) { companion object { 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 ea9287bf44bc..0f55f289ff27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -150,6 +150,7 @@ import com.android.systemui.emergency.EmergencyGesture; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.ExtensionFragmentListener; import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -489,7 +490,6 @@ public class StatusBar extends SystemUI implements private final DozeParameters mDozeParameters; private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; private final StatusBarComponent.Factory mStatusBarComponentFactory; - private final StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory; private final PluginManager mPluginManager; private final Optional<LegacySplitScreen> mSplitScreenOptional; private final StatusBarNotificationActivityStarter.Builder @@ -538,7 +538,7 @@ public class StatusBar extends SystemUI implements protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final BrightnessSliderController.Factory mBrightnessSliderFactory; private final FeatureFlags mFeatureFlags; - + private final FragmentService mFragmentService; private final WallpaperController mWallpaperController; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final MessageRouter mMessageRouter; @@ -546,6 +546,8 @@ public class StatusBar extends SystemUI implements private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private final TunerService mTunerService; + private StatusBarComponent mStatusBarComponent; + // Flags for disabling the status bar // Two variables becaseu the first one evidently ran out of room for new flags. private int mDisabled1 = 0; @@ -690,6 +692,7 @@ public class StatusBar extends SystemUI implements public StatusBar( Context context, NotificationsController notificationsController, + FragmentService fragmentService, LightBarController lightBarController, AutoHideController autoHideController, StatusBarWindowController statusBarWindowController, @@ -747,7 +750,6 @@ public class StatusBar extends SystemUI implements CommandQueue commandQueue, CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, StatusBarComponent.Factory statusBarComponentFactory, - StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory, PluginManager pluginManager, Optional<LegacySplitScreen> splitScreenOptional, LightsOutNotifController lightsOutNotifController, @@ -791,6 +793,7 @@ public class StatusBar extends SystemUI implements ActivityLaunchAnimator activityLaunchAnimator) { super(context); mNotificationsController = notificationsController; + mFragmentService = fragmentService; mLightBarController = lightBarController; mAutoHideController = autoHideController; mStatusBarWindowController = statusBarWindowController; @@ -852,7 +855,6 @@ public class StatusBar extends SystemUI implements mCommandQueue = commandQueue; mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger; mStatusBarComponentFactory = statusBarComponentFactory; - mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory; mPluginManager = pluginManager; mSplitScreenOptional = splitScreenOptional; mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder; @@ -1184,24 +1186,7 @@ public class StatusBar extends SystemUI implements }).getFragmentManager() .beginTransaction() .replace(R.id.status_bar_container, - new CollapsedStatusBarFragment( - mStatusBarFragmentComponentFactory, - mOngoingCallController, - mAnimationScheduler, - mStatusBarLocationPublisher, - mNotificationIconAreaController, - mPanelExpansionStateManager, - mFeatureFlags, - () -> Optional.of(this), - mStatusBarIconController, - mStatusBarHideIconsForBouncerManager, - mKeyguardStateController, - mNetworkController, - mStatusBarStateController, - mCommandQueue, - mCollapsedStatusBarFragmentLogger, - mOperatorNameViewControllerFactory - ), + mStatusBarComponent.createCollapsedStatusBarFragment(), CollapsedStatusBarFragment.TAG) .commit(); @@ -1557,32 +1542,34 @@ public class StatusBar extends SystemUI implements } private void inflateStatusBarWindow() { - StatusBarComponent statusBarComponent = mStatusBarComponentFactory.create(); - mNotificationShadeWindowView = statusBarComponent.getNotificationShadeWindowView(); - mNotificationShadeWindowViewController = statusBarComponent + mStatusBarComponent = mStatusBarComponentFactory.create(); + mFragmentService.addFragmentInstantiationProvider(mStatusBarComponent); + + mNotificationShadeWindowView = mStatusBarComponent.getNotificationShadeWindowView(); + mNotificationShadeWindowViewController = mStatusBarComponent .getNotificationShadeWindowViewController(); mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowViewController.setupExpandedStatusBar(); - mNotificationPanelViewController = statusBarComponent.getNotificationPanelViewController(); - statusBarComponent.getLockIconViewController().init(); - mStackScrollerController = statusBarComponent.getNotificationStackScrollLayoutController(); + mNotificationPanelViewController = mStatusBarComponent.getNotificationPanelViewController(); + mStatusBarComponent.getLockIconViewController().init(); + mStackScrollerController = mStatusBarComponent.getNotificationStackScrollLayoutController(); mStackScroller = mStackScrollerController.getView(); - mNotificationShelfController = statusBarComponent.getNotificationShelfController(); - mAuthRippleController = statusBarComponent.getAuthRippleController(); + mNotificationShelfController = mStatusBarComponent.getNotificationShelfController(); + mAuthRippleController = mStatusBarComponent.getAuthRippleController(); mAuthRippleController.init(); - mHeadsUpManager.addListener(statusBarComponent.getStatusBarHeadsUpChangeListener()); + mHeadsUpManager.addListener(mStatusBarComponent.getStatusBarHeadsUpChangeListener()); - mHeadsUpManager.addListener(statusBarComponent.getStatusBarHeadsUpChangeListener()); + mHeadsUpManager.addListener(mStatusBarComponent.getStatusBarHeadsUpChangeListener()); // Listen for demo mode changes - mDemoModeController.addCallback(statusBarComponent.getStatusBarDemoMode()); + mDemoModeController.addCallback(mStatusBarComponent.getStatusBarDemoMode()); if (mCommandQueueCallbacks != null) { mCommandQueue.removeCallback(mCommandQueueCallbacks); } - mCommandQueueCallbacks = statusBarComponent.getStatusBarCommandQueueCallbacks(); + mCommandQueueCallbacks = mStatusBarComponent.getStatusBarCommandQueueCallbacks(); // Connect in to the status bar manager service mCommandQueue.addCallback(mCommandQueueCallbacks); } 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 7ab4a1ec237e..0d23d663c51c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -441,6 +441,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)} */ public void showBouncer(boolean scrimmed) { + resetAlternateAuth(false); + if (mShowing && !mBouncer.isShowing()) { mBouncer.show(false /* resetSecuritySelection */, scrimmed); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 1130ec24108a..ed52a81751dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -36,6 +36,8 @@ import android.view.WindowInsets.Type; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import androidx.annotation.Nullable; + import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.animation.DialogListener; @@ -303,13 +305,32 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog, * the screen off / close system dialogs broadcast. * <p> * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after - * calling this because it causes a leak of BroadcastReceiver. + * calling this because it causes a leak of BroadcastReceiver. Instead, call the version that + * takes an extra Runnable as a parameter. * * @param dialog The dialog to be associated with the listener. */ public static void registerDismissListener(Dialog dialog) { + registerDismissListener(dialog, null); + } + + + /** + * Registers a listener that dismisses the given dialog when it receives + * the screen off / close system dialogs broadcast. + * <p> + * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after + * calling this because it causes a leak of BroadcastReceiver. + * + * @param dialog The dialog to be associated with the listener. + * @param dismissAction An action to run when the dialog is dismissed. + */ + public static void registerDismissListener(Dialog dialog, @Nullable Runnable dismissAction) { DismissReceiver dismissReceiver = new DismissReceiver(dialog); - dialog.setOnDismissListener(d -> dismissReceiver.unregister()); + dialog.setOnDismissListener(d -> { + dismissReceiver.unregister(); + if (dismissAction != null) dismissAction.run(); + }); dismissReceiver.register(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java index e06605eac46f..375641fdb69d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java @@ -29,6 +29,7 @@ import com.android.systemui.statusbar.phone.SplitShadeHeaderController; import com.android.systemui.statusbar.phone.StatusBarCommandQueueCallbacks; import com.android.systemui.statusbar.phone.StatusBarDemoMode; import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener; +import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -38,7 +39,13 @@ import javax.inject.Scope; import dagger.Subcomponent; /** - * Dagger subcomponent tied to the lifecycle of StatusBar views. + * Dagger subcomponent for classes (semi-)related to the status bar. The component is created once + * inside {@link com.android.systemui.statusbar.phone.StatusBar} and never re-created. + * + * TODO(b/197137564): This should likely be re-factored a bit. It includes classes that aren't + * directly related to status bar functionality, like multiple notification classes. And, the fact + * that it has many getter methods indicates that we need to access many of these classes from + * outside the component. Should more items be moved *into* this component to avoid so many getters? */ @Subcomponent(modules = {StatusBarViewModule.class}) @StatusBarComponent.StatusBarScope @@ -121,4 +128,10 @@ public interface StatusBarComponent { */ @StatusBarScope SplitShadeHeaderController getSplitShadeHeaderController(); + + /** + * Creates a new {@link CollapsedStatusBarFragment} each time it's called. See + * {@link StatusBarViewModule#createCollapsedStatusBarFragment}. + */ + CollapsedStatusBarFragment createCollapsedStatusBarFragment(); } 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 27ece84859d8..50e6151bd3a3 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 @@ -39,6 +39,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; @@ -100,7 +101,6 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; -import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.BatteryController; @@ -143,6 +143,7 @@ public interface StatusBarPhoneModule { static StatusBar provideStatusBar( Context context, NotificationsController notificationsController, + FragmentService fragmentService, LightBarController lightBarController, AutoHideController autoHideController, StatusBarWindowController statusBarWindowController, @@ -200,7 +201,6 @@ public interface StatusBarPhoneModule { CommandQueue commandQueue, CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, StatusBarComponent.Factory statusBarComponentFactory, - StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory, PluginManager pluginManager, Optional<LegacySplitScreen> splitScreenOptional, LightsOutNotifController lightsOutNotifController, @@ -245,6 +245,7 @@ public interface StatusBarPhoneModule { return new StatusBar( context, notificationsController, + fragmentService, lightBarController, autoHideController, statusBarWindowController, @@ -302,7 +303,6 @@ public interface StatusBarPhoneModule { commandQueue, collapsedStatusBarFragmentLogger, statusBarComponentFactory, - statusBarFragmentComponentFactory, pluginManager, splitScreenOptional, lightsOutNotifController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 2765fe37f846..76176df136b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.phone.dagger; import android.annotation.Nullable; +import android.content.ContentResolver; +import android.os.Handler; import android.view.LayoutInflater; import android.view.View; import android.view.ViewStub; @@ -24,19 +26,45 @@ import android.view.ViewStub; import com.android.keyguard.LockIconView; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; +import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.biometrics.AuthRippleView; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; +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.notification.row.dagger.NotificationShelfComponent; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; +import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationPanelView; +import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; +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.StatusBarLocationPublisher; import com.android.systemui.statusbar.phone.TapAgainView; +import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; +import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; +import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; +import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.tuner.TunerService; + +import java.util.Optional; import javax.inject.Named; +import dagger.Lazy; import dagger.Module; import dagger.Provides; @@ -44,6 +72,8 @@ import dagger.Provides; public abstract class StatusBarViewModule { public static final String SPLIT_SHADE_HEADER = "split_shade_header"; + private static final String SPLIT_SHADE_BATTERY_VIEW = "split_shade_battery_view"; + public static final String SPLIT_SHADE_BATTERY_CONTROLLER = "split_shade_battery_controller"; /** */ @Provides @@ -143,10 +173,34 @@ public abstract class StatusBarViewModule { /** */ @Provides @StatusBarComponent.StatusBarScope + @Named(SPLIT_SHADE_BATTERY_VIEW) static BatteryMeterView getBatteryMeterView(@Named(SPLIT_SHADE_HEADER) View view) { return view.findViewById(R.id.batteryRemainingIcon); } + @Provides + @StatusBarComponent.StatusBarScope + @Named(SPLIT_SHADE_BATTERY_CONTROLLER) + static BatteryMeterViewController getBatteryMeterViewController( + @Named(SPLIT_SHADE_BATTERY_VIEW) BatteryMeterView batteryMeterView, + ConfigurationController configurationController, + TunerService tunerService, + BroadcastDispatcher broadcastDispatcher, + @Main Handler mainHandler, + ContentResolver contentResolver, + BatteryController batteryController + ) { + return new BatteryMeterViewController( + batteryMeterView, + configurationController, + tunerService, + broadcastDispatcher, + mainHandler, + contentResolver, + batteryController); + + } + /** */ @Provides @StatusBarComponent.StatusBarScope @@ -161,4 +215,54 @@ public abstract class StatusBarViewModule { NotificationShadeWindowView notificationShadeWindowView) { return notificationShadeWindowView.findViewById(R.id.notification_container_parent); } + + /** + * Creates a new {@link CollapsedStatusBarFragment}. + * + * **IMPORTANT**: This method intentionally does not have + * {@link StatusBarComponent.StatusBarScope}, which means a new fragment *will* be created each + * time this method is called. This is intentional because we need fragments to re-created in + * certain lifecycle scenarios. + * + * **IMPORTANT**: This method also intentionally does not have a {@link Provides} annotation. If + * you need to get access to a {@link CollapsedStatusBarFragment}, go through + * {@link StatusBarFragmentComponent} instead. + */ + public static CollapsedStatusBarFragment createCollapsedStatusBarFragment( + StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory, + OngoingCallController ongoingCallController, + SystemStatusAnimationScheduler animationScheduler, + StatusBarLocationPublisher locationPublisher, + NotificationIconAreaController notificationIconAreaController, + PanelExpansionStateManager panelExpansionStateManager, + FeatureFlags featureFlags, + Lazy<Optional<StatusBar>> statusBarOptionalLazy, + StatusBarIconController statusBarIconController, + StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, + KeyguardStateController keyguardStateController, + NotificationPanelViewController notificationPanelViewController, + NetworkController networkController, + StatusBarStateController statusBarStateController, + CommandQueue commandQueue, + CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, + OperatorNameViewController.Factory operatorNameViewControllerFactory + ) { + return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory, + ongoingCallController, + animationScheduler, + locationPublisher, + notificationIconAreaController, + panelExpansionStateManager, + featureFlags, + statusBarOptionalLazy, + statusBarIconController, + statusBarHideIconsForBouncerManager, + keyguardStateController, + notificationPanelViewController, + networkController, + statusBarStateController, + commandQueue, + collapsedStatusBarFragmentLogger, + operatorNameViewControllerFactory); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 9bcf4a58ce0b..b32acce51e84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -53,6 +53,7 @@ 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.NotificationIconAreaController; +import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; @@ -94,6 +95,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private PhoneStatusBarView mStatusBar; private final StatusBarStateController mStatusBarStateController; private final KeyguardStateController mKeyguardStateController; + private final NotificationPanelViewController mNotificationPanelViewController; private final NetworkController mNetworkController; private LinearLayout mSystemIconArea; private View mClockView; @@ -147,6 +149,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue StatusBarIconController statusBarIconController, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, + NotificationPanelViewController notificationPanelViewController, NetworkController networkController, StatusBarStateController statusBarStateController, CommandQueue commandQueue, @@ -164,6 +167,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBarIconController = statusBarIconController; mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mKeyguardStateController = keyguardStateController; + mNotificationPanelViewController = notificationPanelViewController; mNetworkController = networkController; mStatusBarStateController = statusBarStateController; mCommandQueue = commandQueue; @@ -354,8 +358,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue // The shelf will be hidden when dozing with a custom clock, we must show notification // icons in this occasion. if (mStatusBarStateController.isDozing() - && mStatusBarOptionalLazy.get().map( - sb -> sb.getPanelController().hasCustomClock()).orElse(false)) { + && mNotificationPanelViewController.hasCustomClock()) { state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO; } 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 0e86964147d7..1cf21ac40e31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -351,9 +351,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mSystemClock.advanceTime(205); mController.onTouchOutsideView(); - // THEN show the bouncer and reset alt auth + // THEN show the bouncer verify(mStatusBarKeyguardViewManager).showBouncer(eq(true)); - verify(mStatusBarKeyguardViewManager).resetAlternateAuth(anyBoolean()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt new file mode 100644 index 000000000000..77c837b803af --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt @@ -0,0 +1,82 @@ +package com.android.systemui.fragments + +import android.app.Fragment +import android.os.Looper +import android.test.suitebuilder.annotation.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.qs.QSFragment +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test + +@SmallTest +class FragmentServiceTest : SysuiTestCase() { + private val fragmentCreator = TestFragmentCreator() + private val fragmentCreatorFactory = FragmentService.FragmentCreator.Factory { fragmentCreator } + + private lateinit var fragmentService: FragmentService + + @Before + fun setUp() { + if (Looper.myLooper() == null) { + Looper.prepare() + } + + fragmentService = FragmentService(fragmentCreatorFactory, mock(), DumpManager()) + } + + @Test + fun constructor_addsFragmentCreatorMethodsToMap() { + val map = fragmentService.injectionMap + assertThat(map).hasSize(2) + assertThat(map.keys).contains(QSFragment::class.java.name) + assertThat(map.keys).contains(TestFragmentInCreator::class.java.name) + } + + @Test + fun addFragmentInstantiationProvider_objectHasNoFragmentMethods_nothingAdded() { + fragmentService.addFragmentInstantiationProvider(Object()) + + assertThat(fragmentService.injectionMap).hasSize(2) + } + + @Test + fun addFragmentInstantiationProvider_objectHasFragmentMethods_methodsAdded() { + fragmentService.addFragmentInstantiationProvider( + @Suppress("unused") + object : Any() { + fun createTestFragment2() = TestFragment2() + fun createTestFragment3() = TestFragment3() + } + ) + + val map = fragmentService.injectionMap + assertThat(map).hasSize(4) + assertThat(map.keys).contains(TestFragment2::class.java.name) + assertThat(map.keys).contains(TestFragment3::class.java.name) + } + + @Test + fun addFragmentInstantiationProvider_objectFragmentMethodsAlreadyProvided_nothingAdded() { + fragmentService.addFragmentInstantiationProvider( + @Suppress("unused") + object : Any() { + fun createTestFragment() = TestFragmentInCreator() + } + ) + + assertThat(fragmentService.injectionMap).hasSize(2) + } + + class TestFragmentCreator : FragmentService.FragmentCreator { + override fun createQSFragment(): QSFragment = mock() + @Suppress("unused") + fun createTestFragment(): TestFragmentInCreator = TestFragmentInCreator() + } + + class TestFragmentInCreator : Fragment() + class TestFragment2 : Fragment() + class TestFragment3 : Fragment() +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java new file mode 100644 index 000000000000..734faec4ec74 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java @@ -0,0 +1,178 @@ +/* + * 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.navigationbar; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.view.accessibility.AccessibilityManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; +import com.android.systemui.assist.AssistManager; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import dagger.Lazy; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NavBarHelperTest extends SysuiTestCase { + + @Mock + AccessibilityManager mAccessibilityManager; + @Mock + AccessibilityManagerWrapper mAccessibilityManagerWrapper; + @Mock + AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; + @Mock + OverviewProxyService mOverviewProxyService; + @Mock + Lazy<AssistManager> mAssistManagerLazy; + @Mock + AssistManager mAssistManager; + @Mock + NavigationModeController mNavigationModeController; + @Mock + UserTracker mUserTracker; + @Mock + ComponentName mAssistantComponent; + @Mock + DumpManager mDumpManager; + @Mock + NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater; + + private NavBarHelper mNavBarHelper; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mAssistManagerLazy.get()).thenReturn(mAssistManager); + when(mAssistManager.getAssistInfoForUser(anyInt())).thenReturn(mAssistantComponent); + when(mUserTracker.getUserId()).thenReturn(1); + + mNavBarHelper = new NavBarHelper(mAccessibilityManager, + mAccessibilityManagerWrapper, mAccessibilityButtonModeObserver, + mOverviewProxyService, mAssistManagerLazy, mNavigationModeController, + mUserTracker, mDumpManager); + + } + + @Test + public void registerListenersInCtor() { + verify(mAccessibilityButtonModeObserver, times(1)).addListener(mNavBarHelper); + verify(mNavigationModeController, times(1)).addListener(mNavBarHelper); + verify(mOverviewProxyService, times(1)).addCallback(mNavBarHelper); + } + + @Test + public void registerAssistantContentObserver() { + mNavBarHelper.init(mContext); + verify(mAssistManager, times(1)).getAssistInfoForUser(anyInt()); + } + + @Test + public void callbacksFiredWhenRegistering() { + mNavBarHelper.init(mContext); + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void assistantCallbacksFiredAfterConnecting() { + mNavBarHelper.init(mContext); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + mNavBarHelper.onConnectionChanged(false); + // assert no more callbacks fired + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + + mNavBarHelper.onConnectionChanged(true); + // assert no more callbacks fired + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(2)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void a11yCallbacksFiredAfterModeChange() { + mNavBarHelper.init(mContext); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + mNavBarHelper.onAccessibilityButtonModeChanged(0); + verify(mNavbarTaskbarStateUpdater, times(2)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void assistantCallbacksFiredAfterNavModeChange() { + mNavBarHelper.init(mContext); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + mNavBarHelper.onNavigationModeChanged(0); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(2)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void removeListenerNoCallbacksFired() { + mNavBarHelper.init(mContext); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + // Remove listener + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + // Would have fired 2nd callback if not removed + mNavBarHelper.onAccessibilityButtonModeChanged(0); + + // assert no more callbacks fired + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index 4fc329ffc7af..9d2541c0150f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -45,6 +45,7 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import org.junit.After; @@ -82,11 +83,12 @@ public class NavigationBarControllerTest extends SysuiTestCase { mCommandQueue, Dependency.get(Dependency.MAIN_HANDLER), mock(ConfigurationController.class), - mock(NavigationBarA11yHelper.class), + mock(NavBarHelper.class), mock(TaskbarDelegate.class), mNavigationBarFactory, mock(DumpManager.class), - mock(AutoHideController.class))); + mock(AutoHideController.class), + mock(LightBarController.class))); initializeNavigationBars(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 223ffbd7bba5..776b6aa2ef38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -131,7 +131,7 @@ public class NavigationBarTest extends SysuiTestCase { @Mock EdgeBackGestureHandler mEdgeBackGestureHandler; @Mock - NavigationBarA11yHelper mNavigationBarA11yHelper; + NavBarHelper mNavBarHelper; @Mock private LightBarController mLightBarController; @Mock @@ -220,6 +220,7 @@ public class NavigationBarTest extends SysuiTestCase { new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_SYSTEMUI) .setLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 100) .build()); + when(mNavBarHelper.getLongPressHomeEnabled()).thenReturn(true); mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null)); mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain( @@ -284,14 +285,14 @@ public class NavigationBarTest extends SysuiTestCase { public void testA11yEventAfterDetach() { View v = mNavigationBar.createView(null); mNavigationBar.onViewAttachedToWindow(v); - verify(mNavigationBarA11yHelper).registerA11yEventListener(any( - NavigationBarA11yHelper.NavA11yEventListener.class)); + verify(mNavBarHelper).registerNavTaskStateUpdater(any( + NavBarHelper.NavbarTaskbarStateUpdater.class)); mNavigationBar.onViewDetachedFromWindow(v); - verify(mNavigationBarA11yHelper).removeA11yEventListener(any( - NavigationBarA11yHelper.NavA11yEventListener.class)); + verify(mNavBarHelper).removeNavTaskStateUpdater(any( + NavBarHelper.NavbarTaskbarStateUpdater.class)); // Should be safe even though the internal view is now null. - mNavigationBar.updateAccessibilityServicesState(); + mNavigationBar.updateAcessibilityStateFlags(); } private NavigationBar createNavBar(Context context) { @@ -321,7 +322,7 @@ public class NavigationBarTest extends SysuiTestCase { mHandler, mock(NavigationBarOverlayController.class), mUiEventLogger, - mNavigationBarA11yHelper, + mNavBarHelper, mock(UserTracker.class), mLightBarController, mLightBarcontrollerFactory, 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 f41d7b127a9e..e2a0626d9849 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt @@ -1,7 +1,6 @@ package com.android.systemui.qs import android.testing.AndroidTestingRunner -import android.view.ViewGroup import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Before @@ -9,7 +8,6 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @@ -18,13 +16,9 @@ import org.mockito.junit.MockitoJUnit @SmallTest class QSSquishinessControllerTest : SysuiTestCase() { - @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 tileLayout: TileLayout - @Mock private lateinit var pagedTileLayout: PagedTileLayout @JvmField @Rule val mockitoRule = MockitoJUnit.rule() @@ -32,11 +26,8 @@ class QSSquishinessControllerTest : SysuiTestCase() { @Before fun setup() { - qsSquishinessController = QSSquishinessController(qqsFooterActionsView, qsAnimator, + qsSquishinessController = QSSquishinessController(qsAnimator, qsPanelController, quickQsPanelController) - `when`(quickQsPanelController.tileLayout).thenReturn(tileLayout) - `when`(qsPanelController.tileLayout).thenReturn(pagedTileLayout) - `when`(qqsFooterActionsView.layoutParams).thenReturn(qqsFooterActionsViewLP) } @Test @@ -51,7 +42,7 @@ class QSSquishinessControllerTest : SysuiTestCase() { @Test fun setSquishiness_updatesTiles() { qsSquishinessController.squishiness = 0.5f - verify(tileLayout).setSquishinessFraction(0.5f) - verify(pagedTileLayout).setSquishinessFraction(0.5f) + verify(qsPanelController).setSquishinessFraction(0.5f) + verify(quickQsPanelController).setSquishinessFraction(0.5f) } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt index f99703e2415d..13df04ccef6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt @@ -16,22 +16,28 @@ package com.android.systemui.qs.tiles +import android.app.Dialog import android.content.ContextWrapper import android.content.SharedPreferences import android.os.Handler import android.provider.Settings +import android.provider.Settings.Global.ZEN_MODE_OFF import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger 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 import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger import com.android.systemui.statusbar.policy.ZenModeController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat @@ -40,9 +46,12 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.never +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.io.File +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @@ -70,6 +79,10 @@ class DndTileTest : SysuiTestCase() { private lateinit var zenModeController: ZenModeController @Mock private lateinit var sharedPreferences: SharedPreferences + @Mock + private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + @Mock + private lateinit var hostDialog: Dialog private lateinit var secureSettings: SecureSettings private lateinit var testableLooper: TestableLooper @@ -81,15 +94,17 @@ class DndTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) secureSettings = FakeSettings() - Mockito.`when`(qsHost.userId).thenReturn(DEFAULT_USER) - Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) + whenever(qsHost.userId).thenReturn(DEFAULT_USER) + whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger) + whenever(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean())) + .thenReturn(hostDialog) val wrappedContext = object : ContextWrapper(context) { override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences { return sharedPreferences } } - Mockito.`when`(qsHost.context).thenReturn(wrappedContext) + whenever(qsHost.context).thenReturn(wrappedContext) tile = DndTile( qsHost, @@ -102,7 +117,8 @@ class DndTileTest : SysuiTestCase() { qsLogger, zenModeController, sharedPreferences, - secureSettings + secureSettings, + dialogLaunchAnimator ) } @@ -147,4 +163,32 @@ class DndTileTest : SysuiTestCase() { assertThat(tile.state.forceExpandIcon).isTrue() } + + @Test + fun testLaunchDialogFromViewWhenPrompt() { + whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) + + secureSettings.putIntForUser(KEY, Settings.Secure.ZEN_DURATION_PROMPT, DEFAULT_USER) + testableLooper.processAllMessages() + + val view = View(context) + tile.handleClick(view) + testableLooper.processAllMessages() + + verify(dialogLaunchAnimator).showFromView(any(), eq(view), anyBoolean()) + } + + @Test + fun testNoLaunchDialogWhenNotPrompt() { + whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) + + secureSettings.putIntForUser(KEY, 60, DEFAULT_USER) + testableLooper.processAllMessages() + + val view = View(context) + tile.handleClick(view) + testableLooper.processAllMessages() + + verify(dialogLaunchAnimator, never()).showFromView(any(), any(), anyBoolean()) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt deleted file mode 100644 index d5fe588b2115..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt +++ /dev/null @@ -1,62 +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.qs.user - -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.view.View -import android.view.ViewGroup -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.google.common.truth.Truth.assertThat -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper -class UserDialogTest : SysuiTestCase() { - - private lateinit var dialog: UserDialog - - @Before - fun setUp() { - dialog = UserDialog(mContext) - } - - @After - fun tearDown() { - dialog.dismiss() - } - - @Test - fun doneButtonExists() { - assertThat(dialog.doneButton).isInstanceOf(View::class.java) - } - - @Test - fun settingsButtonExists() { - assertThat(dialog.settingsButton).isInstanceOf(View::class.java) - } - - @Test - fun gridExistsAndIsViewGroup() { - assertThat(dialog.grid).isInstanceOf(ViewGroup::class.java) - } -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt index ea3a42ce501c..3c4a557eac10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.user import android.app.Dialog +import android.content.DialogInterface import android.content.Intent import android.provider.Settings import android.testing.AndroidTestingRunner @@ -28,6 +29,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PseudoGridView import com.android.systemui.qs.tiles.UserDetailView +import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture @@ -43,7 +45,6 @@ import org.mockito.Mockito.`when` import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt import org.mockito.Mockito.argThat -import org.mockito.Mockito.inOrder import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -53,27 +54,21 @@ import org.mockito.MockitoAnnotations class UserSwitchDialogControllerTest : SysuiTestCase() { @Mock - private lateinit var dialog: UserDialog + private lateinit var dialog: SystemUIDialog @Mock private lateinit var falsingManager: FalsingManager @Mock - private lateinit var settingsView: View - @Mock - private lateinit var doneView: View - @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var userDetailViewAdapter: UserDetailView.Adapter @Mock private lateinit var launchView: View @Mock - private lateinit var gridView: PseudoGridView - @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator @Mock private lateinit var hostDialog: Dialog @Captor - private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener> + private lateinit var clickCaptor: ArgumentCaptor<DialogInterface.OnClickListener> private lateinit var controller: UserSwitchDialogController @@ -81,11 +76,8 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - `when`(dialog.settingsButton).thenReturn(settingsView) - `when`(dialog.doneButton).thenReturn(doneView) - `when`(dialog.grid).thenReturn(gridView) - `when`(launchView.context).thenReturn(mContext) + `when`(dialog.context).thenReturn(mContext) `when`(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean())) .thenReturn(hostDialog) @@ -105,30 +97,6 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { } @Test - fun createCalledBeforeDoneButton() { - controller.showDialog(launchView) - val inOrder = inOrder(dialog) - inOrder.verify(dialog).create() - inOrder.verify(dialog).doneButton - } - - @Test - fun createCalledBeforeSettingsButton() { - controller.showDialog(launchView) - val inOrder = inOrder(dialog) - inOrder.verify(dialog).create() - inOrder.verify(dialog).settingsButton - } - - @Test - fun createCalledBeforeGrid() { - controller.showDialog(launchView) - val inOrder = inOrder(dialog) - inOrder.verify(dialog).create() - inOrder.verify(dialog).grid - } - - @Test fun dialog_showForAllUsers() { controller.showDialog(launchView) verify(dialog).setShowForAllUsers(true) @@ -143,51 +111,44 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { @Test fun adapterAndGridLinked() { controller.showDialog(launchView) - verify(userDetailViewAdapter).linkToViewGroup(gridView) + verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>()) } @Test - fun clickDoneButton_dismiss() { + fun doneButtonSetWithNullHandler() { controller.showDialog(launchView) - verify(doneView).setOnClickListener(capture(clickCaptor)) - - clickCaptor.value.onClick(doneView) - - verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt()) - verify(dialog).dismiss() + verify(dialog).setPositiveButton(anyInt(), eq(null)) } @Test - fun clickSettingsButton_noFalsing_opensSettingsAndDismisses() { + fun clickSettingsButton_noFalsing_opensSettings() { `when`(falsingManager.isFalseTap(anyInt())).thenReturn(false) controller.showDialog(launchView) - verify(settingsView).setOnClickListener(capture(clickCaptor)) + verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor)) - clickCaptor.value.onClick(settingsView) + clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL) verify(activityStarter) .postStartActivityDismissingKeyguard( argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)), eq(0) ) - verify(dialog).dismiss() } @Test - fun clickSettingsButton_Falsing_notOpensSettingsAndDismisses() { + fun clickSettingsButton_Falsing_notOpensSettings() { `when`(falsingManager.isFalseTap(anyInt())).thenReturn(true) controller.showDialog(launchView) - verify(settingsView).setOnClickListener(capture(clickCaptor)) + verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor)) - clickCaptor.value.onClick(settingsView) + clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL) verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt()) - verify(dialog).dismiss() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 0faf5d478116..a0e91fc77148 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -438,8 +438,8 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { assertEquals("returns false when view is null", false, NotificationSwipeHelper.isTouchInView(mEvent, null)); - doReturn(5f).when(mEvent).getX(); - doReturn(10f).when(mEvent).getY(); + doReturn(5f).when(mEvent).getRawX(); + doReturn(10f).when(mEvent).getRawY(); doReturn(20).when(mView).getWidth(); doReturn(20).when(mView).getHeight(); @@ -455,7 +455,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { assertTrue("Touch is within the view", mSwipeHelper.isTouchInView(mEvent, mView)); - doReturn(50f).when(mEvent).getX(); + doReturn(50f).when(mEvent).getRawX(); assertFalse("Touch is not within the view", mSwipeHelper.isTouchInView(mEvent, mView)); @@ -466,8 +466,8 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { assertEquals("returns false when view is null", false, NotificationSwipeHelper.isTouchInView(mEvent, null)); - doReturn(5f).when(mEvent).getX(); - doReturn(10f).when(mEvent).getY(); + doReturn(5f).when(mEvent).getRawX(); + doReturn(10f).when(mEvent).getRawY(); doReturn(20).when(mNotificationRow).getWidth(); doReturn(20).when(mNotificationRow).getActualHeight(); @@ -483,7 +483,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { assertTrue("Touch is within the view", mSwipeHelper.isTouchInView(mEvent, mNotificationRow)); - doReturn(50f).when(mEvent).getX(); + doReturn(50f).when(mEvent).getRawX(); assertFalse("Touch is not within the view", mSwipeHelper.isTouchInView(mEvent, mNotificationRow)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 25fd80133897..07debe68e224 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -45,6 +45,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -101,6 +102,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private WakefulnessLifecycle mWakefulnessLifecycle; @Mock private ScreenLifecycle mScreenLifecycle; + @Mock + private StatusBarStateController mStatusBarStateController; private BiometricUnlockController mBiometricUnlockController; @Before @@ -123,7 +126,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters, mMetricsLogger, mDumpManager, mPowerManager, mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle, - mAuthController); + mAuthController, mStatusBarStateController); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener); } @@ -378,6 +381,23 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test + public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() { + // GIVEN UDFPS is supported + when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true); + + // WHEN udfps fails twice - then don't show the bouncer + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean()); + + // WHEN udfps fails the third time + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + + // THEN show the bouncer + verify(mStatusBarKeyguardViewManager).showBouncer(true); + } + + @Test public void onFinishedGoingToSleep_authenticatesWhenPending() { when(mUpdateMonitor.isGoingToSleep()).thenReturn(true); mBiometricUnlockController.onFinishedGoingToSleep(-1); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 6f174cbe0021..c5bdfed6082b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -374,6 +374,21 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + public void testHideAltAuth_onShowBouncer() { + // GIVEN alt auth is showing + mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor); + when(mBouncer.isShowing()).thenReturn(false); + when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true); + reset(mAlternateAuthInterceptor); + + // WHEN showBouncer is called + mStatusBarKeyguardViewManager.showBouncer(true); + + // THEN alt bouncer should be hidden + verify(mAlternateAuthInterceptor).hideAlternateAuthBouncer(); + } + + @Test public void testUpdateResources_delegatesToBouncer() { mStatusBarKeyguardViewManager.updateResources(); 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 88d36b0e3d44..d58e13cd8a64 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 @@ -86,6 +86,7 @@ import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; @@ -132,7 +133,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; -import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.BatteryController; @@ -244,8 +244,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger; @Mock private StatusBarComponent.Factory mStatusBarComponentFactory; @Mock private StatusBarComponent mStatusBarComponent; - @Mock private StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory; - @Mock private StatusBarFragmentComponent mStatusBarFragmentComponent; @Mock private PluginManager mPluginManager; @Mock private LegacySplitScreen mLegacySplitScreen; @Mock private LightsOutNotifController mLightsOutNotifController; @@ -350,8 +348,6 @@ public class StatusBarTest extends SysuiTestCase { when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController); when(mStatusBarComponentFactory.create()).thenReturn(mStatusBarComponent); - when(mStatusBarFragmentComponentFactory.create(any())) - .thenReturn(mStatusBarFragmentComponent); when(mStatusBarComponent.getNotificationShadeWindowViewController()).thenReturn( mNotificationShadeWindowViewController); @@ -366,6 +362,7 @@ public class StatusBarTest extends SysuiTestCase { mStatusBar = new StatusBar( mContext, mNotificationsController, + mock(FragmentService.class), mLightBarController, mAutoHideController, mStatusBarWindowController, @@ -422,7 +419,6 @@ public class StatusBarTest extends SysuiTestCase { mCommandQueue, mCollapsedStatusBarFragmentLogger, mStatusBarComponentFactory, - mStatusBarFragmentComponentFactory, mPluginManager, Optional.of(mLegacySplitScreen), mLightsOutNotifController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index ec35edfdfab2..8b5989ff61a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -295,6 +295,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { new StatusBarHideIconsForBouncerManager( mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()), mKeyguardStateController, + mock(NotificationPanelViewController.class), mNetworkController, mStatusBarStateController, mCommandQueue, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5cd66aabbf2e..dde1ed9cdca9 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -15091,6 +15091,16 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public String getSwitchingFromUserMessage() { + return mUserController.getSwitchingFromSystemUserMessage(); + } + + @Override + public String getSwitchingToUserMessage() { + return mUserController.getSwitchingToSystemUserMessage(); + } + + @Override public void setStopUserOnSwitch(@StopUserOnSwitch int value) { mUserController.setStopUserOnSwitch(value); } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 429696fd88a2..b28b1a66cd97 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1792,7 +1792,8 @@ class UserController implements Handler.Callback { private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) { // The dialog will show and then initiate the user switch by calling startUserInForeground mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second, - getSwitchingFromSystemUserMessage(), getSwitchingToSystemUserMessage()); + getSwitchingFromSystemUserMessageUnchecked(), + getSwitchingToSystemUserMessageUnchecked()); } private void dispatchForegroundProfileChanged(@UserIdInt int userId) { @@ -2564,18 +2565,40 @@ class UserController implements Handler.Callback { } } - private String getSwitchingFromSystemUserMessage() { + // Called by AMS, must check permission + String getSwitchingFromSystemUserMessage() { + checkHasManageUsersPermission("getSwitchingFromSystemUserMessage()"); + + return getSwitchingFromSystemUserMessageUnchecked(); + } + + // Called by AMS, must check permission + String getSwitchingToSystemUserMessage() { + checkHasManageUsersPermission("getSwitchingToSystemUserMessage()"); + + return getSwitchingToSystemUserMessageUnchecked(); + } + + private String getSwitchingFromSystemUserMessageUnchecked() { synchronized (mLock) { return mSwitchingFromSystemUserMessage; } } - private String getSwitchingToSystemUserMessage() { + private String getSwitchingToSystemUserMessageUnchecked() { synchronized (mLock) { return mSwitchingToSystemUserMessage; } } + private void checkHasManageUsersPermission(String operation) { + if (mInjector.checkCallingPermission( + android.Manifest.permission.MANAGE_USERS) == PackageManager.PERMISSION_DENIED) { + throw new SecurityException( + "You need MANAGE_USERS permission to call " + operation); + } + } + void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (mLock) { long token = proto.start(fieldId); @@ -2648,6 +2671,12 @@ class UserController implements Handler.Callback { pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled); pw.println(" mInitialized:" + mInitialized); + if (mSwitchingFromSystemUserMessage != null) { + pw.println(" mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage); + } + if (mSwitchingToSystemUserMessage != null) { + pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage); + } } } 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/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 6ad2f7cad3c2..bb5d962760e7 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -496,11 +496,13 @@ public class ActivityStartController { * @param activityIntent intent to start the activity. * @param activityOptions ActivityOptions to start the activity with. * @param resultTo the caller activity + * @param callingUid the caller uid + * @param callingPid the caller pid * @return the start result. */ int startActivityInTaskFragment(@NonNull TaskFragment taskFragment, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, - @Nullable IBinder resultTo) { + @Nullable IBinder resultTo, int callingUid, int callingPid) { final ActivityRecord caller = resultTo != null ? ActivityRecord.forTokenLocked(resultTo) : null; return obtainStarter(activityIntent, "startActivityInTaskFragment") @@ -508,8 +510,8 @@ public class ActivityStartController { .setInTaskFragment(taskFragment) .setResultTo(resultTo) .setRequestCode(-1) - .setCallingUid(Binder.getCallingUid()) - .setCallingPid(Binder.getCallingPid()) + .setCallingUid(callingUid) + .setCallingPid(callingPid) .setUserId(caller != null ? caller.mUserId : mService.getCurrentUserId()) .execute(); } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index df9a6d21113e..878822522d08 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -590,7 +590,7 @@ public class AppTransitionController { } // We don't want the organizer to handle transition of non-embedded activity of other // app. - if (r.getUid() != rootActivity.getUid() && !r.isEmbedded()) { + if (r.getUid() != task.effectiveUid && !r.isEmbedded()) { leafTask = null; break; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 07ab57145050..36579d96d6c5 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; @@ -4997,6 +4999,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 */); } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index eb829573e45d..b2657e82eb6c 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -452,7 +452,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(); @@ -2260,7 +2260,8 @@ public class DisplayPolicy { // user's package info (see ContextImpl.createDisplayContext) final LoadedApk pi = ActivityThread.currentActivityThread().getPackageInfo( uiContext.getPackageName(), null, 0, userId); - mCurrentUserResources = ResourcesManager.getInstance().getResources(null, + mCurrentUserResources = ResourcesManager.getInstance().getResources( + uiContext.getWindowContextToken(), pi.getResDir(), null /* splitResDirs */, pi.getOverlayDirs(), 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/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 6d83fb6d12c9..29c27f9f3af6 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -31,10 +31,8 @@ import android.os.RemoteException; import android.util.ArrayMap; import android.util.Slog; import android.view.RemoteAnimationDefinition; -import android.view.SurfaceControl; import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentInfo; import com.android.internal.protolog.common.ProtoLog; @@ -135,11 +133,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment tf) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName()); final TaskFragmentInfo info = tf.getTaskFragmentInfo(); - final SurfaceControl outSurfaceControl = new SurfaceControl(tf.getSurfaceControl(), - "TaskFragmentOrganizerController.onTaskFragmentInfoAppeared"); try { - organizer.onTaskFragmentAppeared( - new TaskFragmentAppearedInfo(info, outSurfaceControl)); + organizer.onTaskFragmentAppeared(info); mLastSentTaskFragmentInfos.put(tf, info); tf.mTaskFragmentAppearedSent = true; } catch (RemoteException e) { 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 943a3c12e5ed..cc7485c317c8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2741,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(); @@ -2831,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) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index bd8d1164ebef..877965e5bfde 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -588,7 +588,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: { final TaskFragmentCreationParams taskFragmentCreationOptions = hop.getTaskFragmentCreationOptions(); - createTaskFragment(taskFragmentCreationOptions, errorCallbackToken); + createTaskFragment(taskFragmentCreationOptions, errorCallbackToken, caller); break; } case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: { @@ -631,7 +631,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken); final int result = mService.getActivityStartController() .startActivityInTaskFragment(tf, activityIntent, activityOptions, - hop.getCallingActivity()); + hop.getCallingActivity(), caller.mUid, caller.mPid); if (!isStartResultSuccessful(result)) { sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(), errorCallbackToken, @@ -1200,7 +1200,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, - @Nullable IBinder errorCallbackToken) { + @Nullable IBinder errorCallbackToken, @NonNull CallerInfo caller) { final ActivityRecord ownerActivity = ActivityRecord.forTokenLocked(creationParams.getOwnerToken()); final ITaskFragmentOrganizer organizer = ITaskFragmentOrganizer.Stub.asInterface( @@ -1218,9 +1218,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; } - // The ownerActivity has to belong to the same app as the root Activity of the target Task. - final ActivityRecord rootActivity = ownerActivity.getTask().getRootActivity(); - if (rootActivity.getUid() != ownerActivity.getUid()) { + // The ownerActivity has to belong to the same app as the target Task. + if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid() + || ownerActivity.getTask().effectiveUid != caller.mUid) { final Throwable exception = new IllegalArgumentException("Not allowed to operate with the ownerToken while " + "the root activity of the target task belong to the different app"); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 5d0e34a80f3f..6fa306b004a2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -803,6 +803,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord openingActivity = taskFragment.getTopMostActivity(); openingActivity.allDrawn = true; + task.effectiveUid = openingActivity.getUid(); spyOn(mDisplayContent.mAppTransition); // Prepare a transition. @@ -879,6 +880,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { final ActivityRecord closingActivity = taskFragment.getTopMostActivity(); closingActivity.allDrawn = true; closingActivity.info.applicationInfo.uid = 12345; + task.effectiveUid = closingActivity.getUid(); // Opening non-embedded activity with different UID. final ActivityRecord openingActivity = createActivityRecord(task); openingActivity.info.applicationInfo.uid = 54321; 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/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index b8e12d15c5d2..7014851ee210 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -21,6 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.testing.Assert.assertThrows; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -352,28 +353,66 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test - public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() { + public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() + throws RemoteException { + mController.registerOrganizer(mIOrganizer); + final ActivityRecord activity = createActivityRecord(mDisplayContent); + final int uid = Binder.getCallingUid(); + activity.info.applicationInfo.uid = uid; + activity.getTask().effectiveUid = uid; + final IBinder fragmentToken = new Binder(); + final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder( + mOrganizerToken, fragmentToken, activity.token).build(); mOrganizer.applyTransaction(mTransaction); // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment. - final TaskFragmentCreationParams mockParams = mock(TaskFragmentCreationParams.class); - doReturn(mOrganizerToken).when(mockParams).getOrganizer(); - mTransaction.createTaskFragment(mockParams); + mTransaction.createTaskFragment(params); mTransaction.startActivityInTaskFragment( mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */); mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class)); mTransaction.setAdjacentTaskFragments(mFragmentToken, mock(IBinder.class), null /* options */); + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); - // It is expected to fail for the mock TaskFragmentCreationParams. It is ok as we are - // testing the security check here. - assertThrows(IllegalArgumentException.class, () -> { - try { - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); - } catch (RemoteException e) { - fail(); - } - }); + // Successfully created a TaskFragment. + final TaskFragment taskFragment = mAtm.mWindowOrganizerController + .getTaskFragment(fragmentToken); + assertNotNull(taskFragment); + assertEquals(activity.getTask(), taskFragment.getTask()); + } + + @Test + public void testApplyTransaction_createTaskFragment_failForDifferentUid() + throws RemoteException { + mController.registerOrganizer(mIOrganizer); + final ActivityRecord activity = createActivityRecord(mDisplayContent); + final int uid = Binder.getCallingUid(); + final IBinder fragmentToken = new Binder(); + final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder( + mOrganizerToken, fragmentToken, activity.token).build(); + mOrganizer.applyTransaction(mTransaction); + mTransaction.createTaskFragment(params); + + // Fail to create TaskFragment when the task uid is different from caller. + activity.info.applicationInfo.uid = uid; + activity.getTask().effectiveUid = uid + 1; + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); + + // Fail to create TaskFragment when the task uid is different from owner activity. + activity.info.applicationInfo.uid = uid + 1; + activity.getTask().effectiveUid = uid; + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); + + // Successfully created a TaskFragment for same uid. + activity.info.applicationInfo.uid = uid; + activity.getTask().effectiveUid = uid; + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); } @Test |