diff options
51 files changed, 667 insertions, 214 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e3a6dd07b526..a4fb2c1f12ff 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -113,7 +113,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors(); method public static void resumeAppSwitches() throws android.os.RemoteException; method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setStopUserOnSwitch(int); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean); method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle(); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index f0deecac96c5..992f02ca538b 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4117,7 +4117,7 @@ public class ActivityManager { */ @TestApi @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) + android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(@StopUserOnSwitch int value) { try { getService().setStopUserOnSwitch(value); diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 11c01e61911c..22b1c03fba50 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -240,6 +240,14 @@ public class WallpaperManager { */ public static final String EXTRA_NEW_WALLPAPER_ID = "android.service.wallpaper.extra.ID"; + /** + * Extra passed on {@link Intent.ACTION_WALLPAPER_CHANGED} indicating if wallpaper was set from + * a foreground app. + * @hide + */ + public static final String EXTRA_FROM_FOREGROUND_APP = + "android.service.wallpaper.extra.FROM_FOREGROUND_APP"; + // flags for which kind of wallpaper to act on /** @hide */ diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index c0cb44286479..b5298fc9e1c7 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1015,7 +1015,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { */ @ChangeId @Overridable - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S_V2) @TestApi public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // buganizer id diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 2d263a54b874..c77399f692f0 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1575,7 +1575,7 @@ public abstract class WallpaperService extends Service { void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages, float xOffsetStep) { // to save creating a runnable, check twice - long current = SystemClock.elapsedRealtime(); + long current = System.currentTimeMillis(); long lapsed = current - currentPage.getLastUpdateTime(); // Always update the page when the last update time is <= 0 // This is important especially when the device first boots @@ -1768,6 +1768,7 @@ public abstract class WallpaperService extends Service { return; } } + processLocalColors(mPendingXOffset, mPendingYOffset); } /** diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 2357d13c8d41..f724285df9dc 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -5735,24 +5735,44 @@ public class RemoteViews implements Parcelable, Filter { return previousLayoutId == getLayoutId() && mViewId == overrideId; } - // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls - // should set it to false. - private void reapply(Context context, View v, InteractionHandler handler, SizeF size, - ColorResources colorResources, boolean topLevel) { - + /** + * Returns the RemoteViews that should be used in the reapply operation. + * + * If the current RemoteViews has multiple layout, this will select the correct one. + * + * @throws RuntimeException If the current RemoteViews should not be reapplied onto the provided + * View. + */ + private RemoteViews getRemoteViewsToReapply(Context context, View v, @Nullable SizeF size) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); // In the case that a view has this RemoteViews applied in one orientation or size, is // persisted across change, and has the RemoteViews re-applied in a different situation // (orientation or size), we throw an exception, since the layouts may be completely // unrelated. - if (hasMultipleLayouts()) { + // If the ViewID has been changed on the view, or is changed by the RemoteViews, we also + // may throw an exception, as the RemoteViews will probably not apply properly. + // However, we need to let potentially unrelated RemoteViews apply, as this lack of testing + // is already used in production code in some apps. + if (hasMultipleLayouts() + || rvToApply.mViewId != View.NO_ID + || v.getTag(R.id.remote_views_override_id) != null) { if (!rvToApply.canRecycleView(v)) { throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + " that does not share the same root layout id."); } } + return rvToApply; + } + + // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls + // should set it to false. + private void reapply(Context context, View v, InteractionHandler handler, SizeF size, + ColorResources colorResources, boolean topLevel) { + + RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); + rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources); // If the parent of the view is has is a root, resolve the recycling. @@ -5789,17 +5809,7 @@ public class RemoteViews implements Parcelable, Filter { public CancellationSignal reapplyAsync(Context context, View v, Executor executor, OnViewAppliedListener listener, InteractionHandler handler, SizeF size, ColorResources colorResources) { - RemoteViews rvToApply = getRemoteViewsToApply(context, size); - - // In the case that a view has this RemoteViews applied in one orientation, is persisted - // across orientation change, and has the RemoteViews re-applied in the new orientation, - // we throw an exception, since the layouts may be completely unrelated. - if (hasMultipleLayouts()) { - if (!rvToApply.canRecycleView(v)) { - throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + - " that does not share the same root layout id."); - } - } + RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), context, listener, handler, colorResources, v, true /* topLevel */) diff --git a/libs/WindowManager/Shell/res/drawable/pip_split.xml b/libs/WindowManager/Shell/res/drawable/pip_split.xml new file mode 100644 index 000000000000..2cfdf6ed259b --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/pip_split.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="@dimen/pip_expand_action_inner_size" + android:height="@dimen/pip_expand_action_inner_size" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="#FFFFFF" + android:pathData="M20,18h-5V6h5V18z M22,18V6c0-1.1-0.9-2-2-2h-5c-1.1,0-2,0.9-2,2v12c0,1.1,0.9,2,2,2h5C21.1,20,22,19.1,22,18z M9,18H4V6h5 + V18z M11,18V6c0-1.1-0.9-2-2-2H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h5C10.1,20,11,19.1,11,18z" /> +</vector> diff --git a/libs/WindowManager/Shell/res/layout/pip_menu.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml index 9fe024748610..1dd17bad155b 100644 --- a/libs/WindowManager/Shell/res/layout/pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/pip_menu.xml @@ -65,25 +65,29 @@ <LinearLayout android:id="@+id/top_end_container" android:layout_gravity="top|end" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> + <ImageButton android:id="@+id/settings" android:layout_width="@dimen/pip_action_size" android:layout_height="@dimen/pip_action_size" android:contentDescription="@string/pip_phone_settings" + android:layout_gravity="top|start" android:gravity="center" android:src="@drawable/pip_ic_settings" android:background="?android:selectableItemBackgroundBorderless" /> <ImageButton - android:id="@+id/dismiss" - android:layout_width="@dimen/pip_action_size" - android:layout_height="@dimen/pip_action_size" - android:contentDescription="@string/pip_phone_close" + android:id="@+id/enter_split" + android:layout_width="@dimen/pip_split_icon_size" + android:layout_height="@dimen/pip_split_icon_size" + android:layout_gravity="top|start" + android:layout_margin="@dimen/pip_split_icon_margin" android:gravity="center" - android:src="@drawable/pip_ic_close_white" + android:contentDescription="@string/pip_phone_enter_split" + android:src="@drawable/pip_split" android:background="?android:selectableItemBackgroundBorderless" /> </LinearLayout> @@ -97,4 +101,14 @@ android:padding="@dimen/pip_resize_handle_padding" android:src="@drawable/pip_resize_handle" android:background="?android:selectableItemBackgroundBorderless" /> + + <ImageButton + android:id="@+id/dismiss" + android:layout_width="@dimen/pip_action_size" + android:layout_height="@dimen/pip_action_size" + android:contentDescription="@string/pip_phone_close" + android:layout_gravity="top|end" + android:gravity="center" + android:src="@drawable/pip_ic_close_white" + android:background="?android:selectableItemBackgroundBorderless" /> </FrameLayout> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index e9b9ec3f7d89..9e77578eafd8 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -67,6 +67,10 @@ <dimen name="pip_resize_handle_margin">4dp</dimen> <dimen name="pip_resize_handle_padding">0dp</dimen> + <!-- PIP Split icon size and margin. --> + <dimen name="pip_split_icon_size">24dp</dimen> + <dimen name="pip_split_icon_margin">12dp</dimen> + <!-- PIP stash offset size, which is the width of visible PIP region when stashed. --> <dimen name="pip_stash_offset">32dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 764854af3b3f..c88fc16e218e 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -24,6 +24,9 @@ <!-- Label for PIP settings button [CHAR LIMIT=NONE]--> <string name="pip_phone_settings">Settings</string> + <!-- Label for the PIP enter split button [CHAR LIMIT=NONE] --> + <string name="pip_phone_enter_split">Enter split screen</string> + <!-- Title of menu shown over picture-in-picture. Used for accessibility. --> <string name="pip_menu_title">Menu</string> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index b80dcd063589..711a0ac76702 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -43,6 +43,7 @@ import com.android.wm.shell.pip.tv.TvPipController; import com.android.wm.shell.pip.tv.TvPipMenuController; import com.android.wm.shell.pip.tv.TvPipNotificationController; import com.android.wm.shell.pip.tv.TvPipTransition; +import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; import java.util.Optional; @@ -160,13 +161,14 @@ public abstract class TvPipModule { PipTransitionController pipTransitionController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<LegacySplitScreenController> splitScreenOptional, + Optional<SplitScreenController> newSplitScreenOptional, DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { return new PipTaskOrganizer(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm, tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper, - pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger, - shellTaskOrganizer, mainExecutor); + pipTransitionController, splitScreenOptional, newSplitScreenOptional, + displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 944dfed57cb6..ec701470354c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -55,6 +55,7 @@ import com.android.wm.shell.pip.phone.PipAppOpsListener; import com.android.wm.shell.pip.phone.PipController; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.pip.phone.PipTouchHandler; +import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm; import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm; import com.android.wm.shell.transition.Transitions; @@ -215,14 +216,15 @@ public class WMShellModule { PipSurfaceTransactionHelper pipSurfaceTransactionHelper, PipTransitionController pipTransitionController, Optional<LegacySplitScreenController> splitScreenOptional, + Optional<SplitScreenController> newSplitScreenOptional, DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { return new PipTaskOrganizer(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm, menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper, - pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger, - shellTaskOrganizer, mainExecutor); + pipTransitionController, splitScreenOptional, newSplitScreenOptional, + displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index b6e5804a64dd..6cc5f09827af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -77,6 +77,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PipMotionHelper; +import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; @@ -126,7 +127,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private final int mExitAnimationDuration; private final int mCrossFadeAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; - private final Optional<LegacySplitScreenController> mSplitScreenOptional; + private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional; + private final Optional<SplitScreenController> mSplitScreenOptional; protected final ShellTaskOrganizer mTaskOrganizer; protected final ShellExecutor mMainExecutor; @@ -252,7 +254,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @NonNull PipAnimationController pipAnimationController, @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper, @NonNull PipTransitionController pipTransitionController, - Optional<LegacySplitScreenController> splitScreenOptional, + Optional<LegacySplitScreenController> legacySplitScreenOptional, + Optional<SplitScreenController> splitScreenOptional, @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, @NonNull ShellTaskOrganizer shellTaskOrganizer, @@ -274,6 +277,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipAnimationController = pipAnimationController; mPipUiEventLoggerLogger = pipUiEventLogger; mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; + mLegacySplitScreenOptional = legacySplitScreenOptional; mSplitScreenOptional = splitScreenOptional; mTaskOrganizer = shellTaskOrganizer; mMainExecutor = mainExecutor; @@ -373,8 +377,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, * activity render it's final configuration while the Task is still in PiP. * - setWindowingMode to undefined at the end of transition * @param animationDurationMs duration in millisecond for the exiting PiP transition + * @param requestEnterSplit whether the enterSplit button is pressed on PiP or not. + * Indicate the user wishes to directly put PiP into split screen + * mode. */ - public void exitPip(int animationDurationMs) { + public void exitPip(int animationDurationMs, boolean requestEnterSplit) { if (!mPipTransitionState.isInPip() || mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP || mToken == null) { @@ -387,7 +394,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN); final WindowContainerTransaction wct = new WindowContainerTransaction(); final Rect destinationBounds = mPipBoundsState.getDisplayBounds(); - final int direction = syncWithSplitScreenBounds(destinationBounds) + final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit) ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN : TRANSITION_DIRECTION_LEAVE_PIP; final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); @@ -396,7 +403,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // We set to fullscreen here for now, but later it will be set to UNDEFINED for // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit. wct.setActivityWindowingMode(mToken, - direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN + direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && !requestEnterSplit ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_FULLSCREEN); wct.setBounds(mToken, destinationBounds); @@ -435,7 +442,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, wct.setWindowingMode(mToken, getOutPipWindowingMode()); // Simply reset the activity mode set prior to the animation running. wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); - mSplitScreenOptional.ifPresent(splitScreen -> { + mLegacySplitScreenOptional.ifPresent(splitScreen -> { if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { wct.reparent(mToken, splitScreen.getSecondaryRoot(), true /* onTop */); } @@ -1165,6 +1172,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @PipAnimationController.TransitionDirection int direction, @PipAnimationController.AnimationType int type) { final Rect preResizeBounds = new Rect(mPipBoundsState.getBounds()); + final boolean isPipTopLeft = isPipTopLeft(); mPipBoundsState.setBounds(destinationBounds); if (direction == TRANSITION_DIRECTION_REMOVE_STACK) { removePipImmediately(); @@ -1210,10 +1218,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, null /* callback */, false /* withStartDelay */); }); } else { - applyFinishBoundsResize(wct, direction); + applyFinishBoundsResize(wct, direction, isPipTopLeft); } } else { - applyFinishBoundsResize(wct, direction); + applyFinishBoundsResize(wct, direction, isPipTopLeft); } finishResizeForMenu(destinationBounds); @@ -1241,7 +1249,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } else if (isOutPipDirection(direction)) { // If we are animating to fullscreen or split screen, then we need to reset the // override bounds on the task to ensure that the task "matches" the parent's bounds. - taskBounds = null; + if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { + taskBounds = destinationBounds; + } else { + taskBounds = null; + } applyWindowingModeChangeOnExit(wct, direction); } else { // Just a resize in PIP @@ -1261,8 +1273,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, * applying it. */ public void applyFinishBoundsResize(@NonNull WindowContainerTransaction wct, - @PipAnimationController.TransitionDirection int direction) { - mTaskOrganizer.applyTransaction(wct); + @PipAnimationController.TransitionDirection int direction, boolean wasPipTopLeft) { + if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { + mSplitScreenOptional.get().enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct); + } else { + mTaskOrganizer.applyTransaction(wct); + } + } + + private boolean isPipTopLeft() { + final Rect topLeft = new Rect(); + final Rect bottomRight = new Rect(); + mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight); + + return topLeft.contains(mPipBoundsState.getBounds()); } /** @@ -1347,18 +1371,27 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** - * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split - * screen. + * Sync with {@link LegacySplitScreenController} or {@link SplitScreenController} on destination + * bounds if PiP is going to split screen. * * @param destinationBoundsOut contain the updated destination bounds if applicable * @return {@code true} if destinationBounds is altered for split screen */ - private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut) { - if (!mSplitScreenOptional.isPresent()) { + private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit) { + if (enterSplit && mSplitScreenOptional.isPresent()) { + final Rect topLeft = new Rect(); + final Rect bottomRight = new Rect(); + mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight); + final boolean isPipTopLeft = isPipTopLeft(); + destinationBoundsOut.set(isPipTopLeft ? topLeft : bottomRight); + return true; + } + + if (!mLegacySplitScreenOptional.isPresent()) { return false; } - LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.get(); + LegacySplitScreenController legacySplitScreen = mLegacySplitScreenOptional.get(); if (!legacySplitScreen.isDividerVisible()) { // fail early if system is not in split screen mode return false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index ae8c1b6f8c1a..5687f4d62444 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -95,6 +95,11 @@ public class PhonePipMenuController implements PipMenuController { * Called when the PIP requested to show the menu. */ void onPipShowMenu(); + + /** + * Called when the PIP requested to enter Split. + */ + void onEnterSplit(); } private final Matrix mMoveTransform = new Matrix(); @@ -458,6 +463,10 @@ public class PhonePipMenuController implements PipMenuController { mListeners.forEach(Listener::onPipDismiss); } + void onEnterSplit() { + mListeners.forEach(Listener::onEnterSplit); + } + /** * @return the best set of actions to show in the PiP menu. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java index 47a8c67a22e6..69ae45d12795 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java @@ -151,7 +151,7 @@ public class PipAccessibilityInteractionConnection { result = true; break; case AccessibilityNodeInfo.ACTION_EXPAND: - mMotionHelper.expandLeavePip(); + mMotionHelper.expandLeavePip(false /* skipAnimation */); result = true; break; default: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 8c431f08a385..10bc7e250cc8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -482,7 +482,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb false /* fromShelfAdjustment */, wct /* windowContainerTransaction */); if (wct != null) { - mPipTaskOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_SAME); + mPipTaskOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_SAME, + false /* wasPipTopLeft */); } }; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java index 3eeba6eb5366..06446573840c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java @@ -18,8 +18,6 @@ package com.android.wm.shell.pip.phone; import android.content.Context; import android.graphics.Rect; -import android.util.Log; -import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -34,6 +32,7 @@ public class PipMenuIconsAlgorithm { protected ViewGroup mViewRoot; protected ViewGroup mTopEndContainer; protected View mDragHandle; + protected View mEnterSplitButton; protected View mSettingsButton; protected View mDismissButton; @@ -44,14 +43,13 @@ public class PipMenuIconsAlgorithm { * Bind the necessary views. */ public void bindViews(ViewGroup viewRoot, ViewGroup topEndContainer, View dragHandle, - View settingsButton, View dismissButton) { + View enterSplitButton, View settingsButton, View dismissButton) { mViewRoot = viewRoot; mTopEndContainer = topEndContainer; mDragHandle = dragHandle; + mEnterSplitButton = enterSplitButton; mSettingsButton = settingsButton; mDismissButton = dismissButton; - - bindInitialViewState(); } /** @@ -72,22 +70,4 @@ public class PipMenuIconsAlgorithm { v.setLayoutParams(params); } } - - /** Calculate the initial state of the menu icons. Called when the menu is first created. */ - private void bindInitialViewState() { - if (mViewRoot == null || mTopEndContainer == null || mDragHandle == null - || mSettingsButton == null || mDismissButton == null) { - Log.e(TAG, "One of the required views is null."); - return; - } - // The menu view layout starts out with the settings button aligned at the top|end of the - // view group next to the dismiss button. On phones, the settings button should be aligned - // to the top|start of the view, so move it to parent view group to then align it to the - // top|start of the menu. - mTopEndContainer.removeView(mSettingsButton); - mViewRoot.addView(mSettingsButton); - - setLayoutGravity(mDragHandle, Gravity.START | Gravity.TOP); - setLayoutGravity(mSettingsButton, Gravity.START | Gravity.TOP); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 8ef2b6b12030..b209699c1a19 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -99,7 +99,7 @@ public class PipMenuView extends FrameLayout { private static final float MENU_BACKGROUND_ALPHA = 0.3f; private static final float DISABLED_ACTION_ALPHA = 0.54f; - private static final boolean ENABLE_RESIZE_HANDLE = false; + private static final boolean ENABLE_ENTER_SPLIT = true; private int mMenuState; private boolean mAllowMenuTimeout = true; @@ -139,7 +139,7 @@ public class PipMenuView extends FrameLayout { protected View mViewRoot; protected View mSettingsButton; protected View mDismissButton; - protected View mResizeHandle; + protected View mEnterSplitButton; protected View mTopEndContainer; protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm; @@ -177,14 +177,23 @@ public class PipMenuView extends FrameLayout { } }); - mResizeHandle = findViewById(R.id.resize_handle); - mResizeHandle.setAlpha(0); + mEnterSplitButton = findViewById(R.id.enter_split); + mEnterSplitButton.setAlpha(0); + mEnterSplitButton.setOnClickListener(v -> { + if (mMenuContainer.getAlpha() != 0) { + enterSplit(); + } + }); + + findViewById(R.id.resize_handle).setAlpha(0); + mActionsGroup = findViewById(R.id.actions_group); mBetweenActionPaddingLand = getResources().getDimensionPixelSize( R.dimen.pip_between_action_padding_land); mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext); mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer, - mResizeHandle, mSettingsButton, mDismissButton); + findViewById(R.id.resize_handle), mEnterSplitButton, mSettingsButton, + mDismissButton); mDismissFadeOutDurationMs = context.getResources() .getInteger(R.integer.config_pipExitAnimationDuration); @@ -268,14 +277,13 @@ public class PipMenuView extends FrameLayout { mSettingsButton.getAlpha(), 1f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 1f); - ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA, - mResizeHandle.getAlpha(), - ENABLE_RESIZE_HANDLE && showResizeHandle ? 1f : 0f); + ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, + mEnterSplitButton.getAlpha(), ENABLE_ENTER_SPLIT ? 1f : 0f); if (menuState == MENU_STATE_FULL) { mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, - resizeAnim); + enterSplitAnim); } else { - mMenuContainerAnimator.playTogether(resizeAnim); + mMenuContainerAnimator.playTogether(enterSplitAnim); } mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS); @@ -328,7 +336,7 @@ public class PipMenuView extends FrameLayout { mMenuContainer.setAlpha(0f); mSettingsButton.setAlpha(0f); mDismissButton.setAlpha(0f); - mResizeHandle.setAlpha(0f); + mEnterSplitButton.setAlpha(0f); } void pokeMenu() { @@ -368,9 +376,10 @@ public class PipMenuView extends FrameLayout { mSettingsButton.getAlpha(), 0f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 0f); - ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA, - mResizeHandle.getAlpha(), 0f); - mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim); + ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, + mEnterSplitButton.getAlpha(), 0f); + mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, + enterSplitAnim); mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT); mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType)); mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { @@ -522,6 +531,14 @@ public class PipMenuView extends FrameLayout { } } + private void enterSplit() { + // Do not notify menu visibility when hiding the menu, the controller will do this when it + // handles the message + hideMenu(mController::onEnterSplit, false /* notifyMenuVisibility */, true /* resize */, + ANIM_TYPE_HIDE); + } + + private void showSettings() { final Pair<ComponentName, Integer> topPipActivityInfo = PipUtils.getTopPipActivity(mContext); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index dbd09fd7b265..c634b7f220b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -338,22 +338,29 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Resizes the pinned stack back to unknown windowing mode, which could be freeform or * * fullscreen depending on the display area's windowing mode. */ - void expandLeavePip() { - expandLeavePip(false /* skipAnimation */); + void expandLeavePip(boolean skipAnimation) { + expandLeavePip(skipAnimation, false /* enterSplit */); + } + + /** + * Resizes the pinned task to split-screen mode. + */ + void expandIntoSplit() { + expandLeavePip(false, true /* enterSplit */); } /** * Resizes the pinned stack back to unknown windowing mode, which could be freeform or * fullscreen depending on the display area's windowing mode. */ - void expandLeavePip(boolean skipAnimation) { + private void expandLeavePip(boolean skipAnimation, boolean enterSplit) { if (DEBUG) { Log.d(TAG, "exitPip: skipAnimation=" + skipAnimation + " callers=\n" + Debug.getCallers(5, " ")); } cancelPhysicsAnimation(); mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */); - mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION); + mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION, enterSplit); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 9f2f6a575aca..570fd5eab9f6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -139,7 +139,12 @@ public class PipTouchHandler { @Override public void onPipExpand() { - mMotionHelper.expandLeavePip(); + mMotionHelper.expandLeavePip(false /* skipAnimation */); + } + + @Override + public void onEnterSplit() { + mMotionHelper.expandIntoSplit(); } @Override @@ -899,7 +904,7 @@ public class PipTouchHandler { // Expand to fullscreen if this is a double tap // the PiP should be frozen until the transition ends setTouchEnabled(false); - mMotionHelper.expandLeavePip(); + mMotionHelper.expandLeavePip(false /* skipAnimation */); } } else if (mMenuState != MENU_STATE_FULL) { if (mPipBoundsState.isStashed()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index a2e9b64046fd..00083d986dbe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -219,7 +219,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public void movePipToFullscreen() { if (DEBUG) Log.d(TAG, "movePipToFullscreen(), state=" + stateToName(mState)); - mPipTaskOrganizer.exitPip(mResizeAnimationDuration); + mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */); onPipDisappeared(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 04058ed6388c..7457be2d0871 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -202,11 +202,25 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return moveToSideStage(task, sideStagePosition); } + public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition, + WindowContainerTransaction wct) { + final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); + if (task == null) { + throw new IllegalArgumentException("Unknown taskId" + taskId); + } + return moveToSideStage(task, sideStagePosition, wct); + } + public boolean moveToSideStage(ActivityManager.RunningTaskInfo task, @SplitPosition int sideStagePosition) { return mStageCoordinator.moveToSideStage(task, sideStagePosition); } + public boolean moveToSideStage(ActivityManager.RunningTaskInfo task, + @SplitPosition int sideStagePosition, WindowContainerTransaction wct) { + return mStageCoordinator.moveToSideStage(task, sideStagePosition, wct); + } + public boolean removeFromSideStage(int taskId) { return mStageCoordinator.removeFromSideStage(taskId); } @@ -224,6 +238,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT); } + public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) { + moveToSideStage(taskId, + leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); + } + public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) { mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 3589f7c14cd3..95886c8f3deb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -281,6 +281,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, boolean moveToSideStage(ActivityManager.RunningTaskInfo task, @SplitPosition int sideStagePosition) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + return moveToSideStage(task, sideStagePosition, wct); + } + + boolean moveToSideStage(ActivityManager.RunningTaskInfo task, + @SplitPosition int sideStagePosition, WindowContainerTransaction wct) { final WindowContainerTransaction evictWct = new WindowContainerTransaction(); setSideStagePosition(sideStagePosition, wct); mSideStage.evictAllChildren(evictWct); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 0270093da938..0172cf324eea 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -50,6 +50,7 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PhonePipMenuController; +import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; import org.junit.Test; @@ -75,7 +76,8 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Mock private PipTransitionController mMockPipTransitionController; @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper; @Mock private PipUiEventLogger mMockPipUiEventLogger; - @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen; + @Mock private Optional<LegacySplitScreenController> mMockOptionalLegacySplitScreen; + @Mock private Optional<SplitScreenController> mMockOptionalSplitScreen; @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; private TestShellExecutor mMainExecutor; private PipBoundsState mPipBoundsState; @@ -99,8 +101,9 @@ public class PipTaskOrganizerTest extends ShellTestCase { mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState, mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController, mMockPipSurfaceTransactionHelper, - mMockPipTransitionController, mMockOptionalSplitScreen, mMockDisplayController, - mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor)); + mMockPipTransitionController, mMockOptionalLegacySplitScreen, + mMockOptionalSplitScreen, mMockDisplayController, mMockPipUiEventLogger, + mMockShellTaskOrganizer, mMainExecutor)); mMainExecutor.flushAll(); preparePipTaskOrg(); } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index faa7554525c5..413612ff9a76 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -54,8 +54,12 @@ class DialogLaunchAnimator( private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running } + /** + * The set of dialogs that were animated using this animator and that are still opened (not + * dismissed, but can be hidden). + */ // TODO(b/201264644): Remove this set. - private val currentAnimations = hashSetOf<DialogLaunchAnimation>() + private val openedDialogs = hashSetOf<AnimatedDialog>() /** * Show [dialog] by expanding it from [view]. If [animateBackgroundBoundsChange] is true, then @@ -79,21 +83,29 @@ class DialogLaunchAnimator( "the main thread") } + // If the parent of the view we are launching from is the background of some other animated + // dialog, then this means the caller intent is to launch a dialog from another dialog. In + // this case, we also animate the parent (which is the dialog background). + val dialogContentParent = openedDialogs + .firstOrNull { it.dialogContentParent == view.parent } + ?.dialogContentParent + val animateFrom = dialogContentParent ?: view + // Make sure we don't run the launch animation from the same view twice at the same time. - if (view.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) { + if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) { Log.e(TAG, "Not running dialog launch animation as there is already one running") dialog.show() return dialog } - view.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true) + animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true) - val launchAnimation = DialogLaunchAnimation( - context, launchAnimator, hostDialogProvider, view, - onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog, + val launchAnimation = AnimatedDialog( + context, launchAnimator, hostDialogProvider, animateFrom, + onDialogDismissed = { openedDialogs.remove(it) }, originalDialog = dialog, animateBackgroundBoundsChange) val hostDialog = launchAnimation.hostDialog - currentAnimations.add(launchAnimation) + openedDialogs.add(launchAnimation) // If the dialog is dismissed/hidden/shown, then we should actually dismiss/hide/show the // host dialog. @@ -151,7 +163,7 @@ class DialogLaunchAnimator( * TODO(b/193634619): Remove this function and animate dialog into opening activity instead. */ fun disableAllCurrentDialogsExitAnimations() { - currentAnimations.forEach { it.exitAnimationDisabled = true } + openedDialogs.forEach { it.exitAnimationDisabled = true } } } @@ -206,7 +218,7 @@ interface DialogListener { fun onSizeChanged() } -private class DialogLaunchAnimation( +private class AnimatedDialog( private val context: Context, private val launchAnimator: LaunchAnimator, hostDialogProvider: HostDialogProvider, @@ -215,10 +227,10 @@ private class DialogLaunchAnimation( private val touchSurface: View, /** - * A callback that will be called with this [DialogLaunchAnimation] after the dialog was + * A callback that will be called with this [AnimatedDialog] after the dialog was * dismissed and the exit animation is done. */ - private val onDialogDismissed: (DialogLaunchAnimation) -> Unit, + private val onDialogDismissed: (AnimatedDialog) -> Unit, /** The original dialog whose content will be shown and animate in/out in [hostDialog]. */ private val originalDialog: Dialog, @@ -241,7 +253,7 @@ private class DialogLaunchAnimation( * the same size as the original dialog window and to which we will set the original dialog * window background. */ - private val dialogContentParent = FrameLayout(context) + val dialogContentParent = FrameLayout(context) /** * The background color of [originalDialogView], taking into consideration the [originalDialog] @@ -574,7 +586,7 @@ private class DialogLaunchAnimation( } dismissDialogs(false /* instantDismiss */) - onDialogDismissed(this@DialogLaunchAnimation) + onDialogDismissed(this@AnimatedDialog) return } @@ -610,7 +622,7 @@ private class DialogLaunchAnimation( // and instantly dismiss the dialog. GhostView.removeGhost(touchSurface) dismissDialogs(true /* instantDismiss */) - onDialogDismissed(this@DialogLaunchAnimation) + onDialogDismissed(this@AnimatedDialog) return true } diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index 040df865bfe5..362e18d785ac 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -33,5 +33,4 @@ <!-- Notifications are sized to match the width of two (of 4) qs tiles in landscape. --> <bool name="config_skinnyNotifsInLandscape">false</bool> - <dimen name="keyguard_indication_margin_bottom">25dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml new file mode 100644 index 000000000000..3cfe05638032 --- /dev/null +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -0,0 +1,28 @@ +<?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. +*/ +--> +<resources> + + <!-- keyguard--> + <dimen name="keyguard_indication_margin_bottom">25dp</dimen> + <dimen name="ambient_indication_margin_bottom">115dp</dimen> + <dimen name="lock_icon_margin_bottom">60dp</dimen> + + <!-- margin from keyguard status bar to clock. For split shade it should be + keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp --> + <dimen name="keyguard_clock_top_margin">8dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 44589dee1126..6875cab8a7b2 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -753,6 +753,9 @@ <!-- Minimum distance the user has to drag down to go to the full shade. --> <dimen name="keyguard_drag_down_min_distance">100dp</dimen> + <!-- The margin from the top of the screen to notifications and keyguard status view in + split shade on keyguard--> + <dimen name="keyguard_split_shade_top_margin">68dp</dimen> <!-- The margin between the status view and the notifications on Keyguard.--> <dimen name="keyguard_status_view_bottom_margin">20dp</dimen> <!-- Minimum margin between clock and status bar --> @@ -935,7 +938,9 @@ <dimen name="keyguard_lock_padding">20dp</dimen> <dimen name="keyguard_indication_margin_bottom">32dp</dimen> - <dimen name="lock_icon_margin_bottom">98dp</dimen> + <dimen name="lock_icon_margin_bottom">110dp</dimen> + <dimen name="ambient_indication_margin_bottom">71dp</dimen> + <!-- The text size for battery level --> <dimen name="battery_level_text_size">12sp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 4d2986f40a33..7216ac6e5702 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -266,8 +266,6 @@ <item name="android:paddingTop">12dp</item> <item name="android:paddingHorizontal">24dp</item> <item name="android:textSize">24sp</item> - <item name="android:singleLine">true</item> - <item name="android:ellipsize">marquee</item> </style> <style name="TextAppearance.AuthCredential.Subtitle"> @@ -275,8 +273,6 @@ <item name="android:paddingTop">8dp</item> <item name="android:paddingHorizontal">24dp</item> <item name="android:textSize">16sp</item> - <item name="android:singleLine">true</item> - <item name="android:ellipsize">marquee</item> </style> <style name="TextAppearance.AuthCredential.Description"> @@ -284,8 +280,6 @@ <item name="android:paddingTop">8dp</item> <item name="android:paddingHorizontal">24dp</item> <item name="android:textSize">14sp</item> - <item name="android:singleLine">true</item> - <item name="android:ellipsize">marquee</item> </style> <style name="TextAppearance.AuthCredential.Error"> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 3f2ff742d072..3128ffdbc67b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -23,17 +23,14 @@ import android.app.ActivityManager.TaskDescription; import android.app.TaskInfo; import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.view.ViewDebug; -import com.android.systemui.shared.recents.utilities.Utilities; +import androidx.annotation.Nullable; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Objects; /** @@ -202,8 +199,8 @@ public class Task { * The icon is the task description icon (if provided), which falls back to the activity icon, * which can then fall back to the application icon. */ - public Drawable icon; - public ThumbnailData thumbnail; + @Nullable public Drawable icon; + @Nullable public ThumbnailData thumbnail; @ViewDebug.ExportedProperty(category="recents") @Deprecated public String title; diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index a383cab94c36..ac463ebc1c97 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -31,7 +31,6 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.ViewController; @@ -49,7 +48,6 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie private final StatusBarStateController mStatusBarStateController; private final BroadcastDispatcher mBroadcastDispatcher; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final KeyguardBypassController mBypassController; private final BatteryController mBatteryController; private final int mDozingColor = Color.WHITE; private int mLockScreenColor; @@ -71,14 +69,12 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie BroadcastDispatcher broadcastDispatcher, BatteryController batteryController, KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardBypassController bypassController, @Main Resources resources ) { super(view); mStatusBarStateController = statusBarStateController; mBroadcastDispatcher = broadcastDispatcher; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mBypassController = bypassController; mBatteryController = batteryController; mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER); diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java index ef3104a21708..2a0c2855c3b2 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java @@ -27,7 +27,6 @@ import android.util.AttributeSet; import android.widget.TextView; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import java.util.Calendar; import java.util.Locale; @@ -111,6 +110,28 @@ public class AnimatableClockView extends TextView { super.onDetachedFromWindow(); } + int getDozingWeight() { + if (useBoldedVersion()) { + return mDozingWeight + 100; + } + return mDozingWeight; + } + + int getLockScreenWeight() { + if (useBoldedVersion()) { + return mLockScreenWeight + 100; + } + return mLockScreenWeight; + } + + /** + * Whether to use a bolded version based on the user specified fontWeightAdjustment. + */ + boolean useBoldedVersion() { + // "Bold text" fontWeightAdjustment is 300. + return getResources().getConfiguration().fontWeightAdjustment > 100; + } + void refreshTime() { mTime.setTimeInMillis(System.currentTimeMillis()); setText(DateFormat.format(mFormat, mTime)); @@ -162,7 +183,7 @@ public class AnimatableClockView extends TextView { } setTextStyle( - mDozingWeight, + getDozingWeight(), -1 /* text size, no update */, mLockScreenColor, false /* animate */, @@ -171,7 +192,7 @@ public class AnimatableClockView extends TextView { null /* onAnimationEnd */); setTextStyle( - mLockScreenWeight, + getLockScreenWeight(), -1 /* text size, no update */, mLockScreenColor, true, /* animate */ @@ -180,35 +201,22 @@ public class AnimatableClockView extends TextView { null /* onAnimationEnd */); } - void animateDisappear() { - if (mTextAnimator == null) { - return; - } - - setTextStyle( - 0 /* weight */, - -1 /* text size, no update */, - null /* color, no update */, - true /* animate */, - KeyguardBypassController.BYPASS_FADE_DURATION /* duration */, - 0 /* delay */, - null /* onAnimationEnd */); - } - void animateCharge(DozeStateGetter dozeStateGetter) { if (mTextAnimator == null || mTextAnimator.isRunning()) { // Skip charge animation if dozing animation is already playing. return; } Runnable startAnimPhase2 = () -> setTextStyle( - dozeStateGetter.isDozing() ? mDozingWeight : mLockScreenWeight/* weight */, + dozeStateGetter.isDozing() ? getDozingWeight() : getLockScreenWeight() /* weight */, -1, null, true /* animate */, CHARGE_ANIM_DURATION_PHASE_1, 0 /* delay */, null /* onAnimationEnd */); - setTextStyle(dozeStateGetter.isDozing() ? mLockScreenWeight : mDozingWeight/* weight */, + setTextStyle(dozeStateGetter.isDozing() + ? getLockScreenWeight() + : getDozingWeight()/* weight */, -1, null, true /* animate */, @@ -218,7 +226,7 @@ public class AnimatableClockView extends TextView { } void animateDoze(boolean isDozing, boolean animate) { - setTextStyle(isDozing ? mDozingWeight : mLockScreenWeight /* weight */, + setTextStyle(isDozing ? getDozingWeight() : getLockScreenWeight() /* weight */, -1, isDozing ? mDozingColor : mLockScreenColor, animate, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 1931c0a1645c..905495d369a0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -167,7 +167,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mBroadcastDispatcher, mBatteryController, mKeyguardUpdateMonitor, - mBypassController, mResources); mClockViewController.init(); @@ -178,7 +177,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mBroadcastDispatcher, mBatteryController, mKeyguardUpdateMonitor, - mBypassController, mResources); mLargeClockViewController.init(); } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index c7be3ce01c54..88476398e09d 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -43,6 +43,7 @@ import android.view.GestureDetector.SimpleOnGestureListener; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -85,7 +86,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private static final float sDefaultDensity = (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT; private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36); - private static final float sDistAboveKgBottomAreaPx = sDefaultDensity * 12; private static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) @@ -126,7 +126,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mUdfpsSupported; private float mHeightPixels; private float mWidthPixels; - private int mBottomPadding; // in pixels + private int mBottomPaddingPx; private boolean mShowUnlockIcon; private boolean mShowLockIcon; @@ -347,11 +347,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } private void updateConfiguration() { - final DisplayMetrics metrics = mView.getContext().getResources().getDisplayMetrics(); - mWidthPixels = metrics.widthPixels; - mHeightPixels = metrics.heightPixels; - mBottomPadding = mView.getContext().getResources().getDimensionPixelSize( - R.dimen.lock_icon_margin_bottom); + WindowManager windowManager = getContext().getSystemService(WindowManager.class); + Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); + mWidthPixels = bounds.right; + mHeightPixels = bounds.bottom; + mBottomPaddingPx = getResources().getDimensionPixelSize(R.dimen.lock_icon_margin_bottom); mUnlockedLabel = mView.getContext().getResources().getString( R.string.accessibility_unlock_button); @@ -370,8 +370,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } else { mView.setCenterLocation( new PointF(mWidthPixels / 2, - mHeightPixels - mBottomPadding - sDistAboveKgBottomAreaPx - - sLockIconRadiusPx), sLockIconRadiusPx); + mHeightPixels - mBottomPaddingPx - sLockIconRadiusPx), + sLockIconRadiusPx); } mView.getHitRect(mSensorTouchLocation); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 1eef6e87cec0..bc023cc892a6 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -1518,6 +1518,9 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Override public void onNavigationModeChanged(int mode) { mNavBarMode = mode; + // update assistant entry points on system navigation radio button click + updateAssistantEntrypoints(); + if (!QuickStepContract.isGesturalMode(mode)) { // Reset the override alpha if (getBarTransitions() != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index 4f3bbdbff030..7ca8652e1b3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -57,21 +57,6 @@ public class KeyguardClockPositionAlgorithm { private int mUserSwitchPreferredY; /** - * Whether or not there is a custom clock face on keyguard. - */ - private boolean mHasCustomClock; - - /** - * Whether or not the NSSL contains any visible notifications. - */ - private boolean mHasVisibleNotifs; - - /** - * Height of notification stack: Sum of height of each notification. - */ - private int mNotificationStackHeight; - - /** * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher * avatar. */ @@ -88,6 +73,16 @@ public class KeyguardClockPositionAlgorithm { private int mContainerTopPadding; /** + * Top margin of notifications introduced by presence of split shade header / status bar + */ + private int mSplitShadeTopNotificationsMargin; + + /** + * Target margin for notifications and clock from the top of the screen in split shade + */ + private int mSplitShadeTargetTopMargin; + + /** * @see NotificationPanelViewController#getExpandedFraction() */ private float mPanelExpansion; @@ -152,6 +147,10 @@ public class KeyguardClockPositionAlgorithm { public void loadDimens(Resources res) { mStatusViewBottomMargin = res.getDimensionPixelSize( R.dimen.keyguard_status_view_bottom_margin); + mSplitShadeTopNotificationsMargin = + res.getDimensionPixelSize(R.dimen.split_shade_header_height); + mSplitShadeTargetTopMargin = + res.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin); mContainerTopPadding = res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); @@ -214,7 +213,7 @@ public class KeyguardClockPositionAlgorithm { if (mBypassEnabled) { return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount); } else if (mIsSplitShade) { - return clockYPosition; + return Math.max(0, clockYPosition - mSplitShadeTopNotificationsMargin); } else { return clockYPosition + mKeyguardStatusHeight; } @@ -224,14 +223,18 @@ public class KeyguardClockPositionAlgorithm { if (mBypassEnabled) { return mUnlockedStackScrollerPadding; } else if (mIsSplitShade) { - return mMinTopMargin; + return Math.max(mSplitShadeTargetTopMargin, mMinTopMargin); } else { return mMinTopMargin + mKeyguardStatusHeight; } } private int getExpandedPreferredClockY() { - return mMinTopMargin + mUserSwitchHeight; + if (mIsSplitShade) { + return Math.max(mSplitShadeTargetTopMargin, mMinTopMargin); + } else { + return mMinTopMargin; + } } public int getLockscreenStatusViewHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 994aebbea01b..988034f9c5fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3558,7 +3558,12 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.setPulsing(pulsing, animatePulse); } - public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) { + public void setAmbientIndicationTop(int ambientIndicationTop, boolean ambientTextVisible) { + int ambientIndicationBottomPadding = 0; + if (ambientTextVisible) { + int stackBottom = mNotificationStackScrollLayoutController.getView().getBottom(); + ambientIndicationBottomPadding = stackBottom - ambientIndicationTop; + } if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) { mAmbientIndicationBottomPadding = ambientIndicationBottomPadding; updateMaxDisplayedNotifications(true); diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index cd5865f58c85..426bc91a606d 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -259,8 +259,13 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); reevaluateSystemTheme(true /* forceReload */); } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) { - mAcceptColorEvents = true; - Log.i(TAG, "Allowing color events again"); + if (intent.getBooleanExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, false)) { + mAcceptColorEvents = true; + Log.i(TAG, "Wallpaper changed, allowing color events again"); + } else { + Log.i(TAG, "Wallpaper changed from background app, " + + "keep deferring color events. Accepting: " + mAcceptColorEvents); + } } } }; diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt index 209df6b54f8f..d4c3840356d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -86,7 +86,14 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { assertFalse(dialog.isShowing) assertFalse(dialog.onStopCalled) - runOnMainThreadAndWaitForIdleSync { dialog.dismiss() } + runOnMainThreadAndWaitForIdleSync { + // TODO(b/204561691): Remove this call to disableAllCurrentDialogsExitAnimations() and + // make sure that the test still pass on git_master/cf_x86_64_phone-userdebug in + // Forrest. + dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() + + dialog.dismiss() + } assertFalse(hostDialog.isShowing) assertFalse(dialog.isShowing) assertTrue(hostDialog.wasDismissed) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java index df112840ed87..5a4bb86dba45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java @@ -40,7 +40,6 @@ import com.android.settingslib.Utils; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.BatteryController; import org.junit.After; @@ -69,8 +68,6 @@ public class AnimatableClockControllerTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock - private KeyguardBypassController mBypassController; - @Mock private Resources mResources; private MockitoSession mStaticMockSession; @@ -99,7 +96,6 @@ public class AnimatableClockControllerTest extends SysuiTestCase { mBroadcastDispatcher, mBatteryController, mKeyguardUpdateMonitor, - mBypassController, mResources ); mAnimatableClockController.init(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java index d64319b278b4..e01583e1cb1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.drawable.AnimatedStateListDrawable; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.SensorLocationInternal; @@ -39,10 +40,10 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Vibrator; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.util.DisplayMetrics; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; +import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; @@ -70,6 +71,7 @@ import com.airbnb.lottie.LottieAnimationView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; @@ -88,7 +90,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { private @Mock AnimatedStateListDrawable mIconDrawable; private @Mock Context mContext; private @Mock Resources mResources; - private @Mock DisplayMetrics mDisplayMetrics; + private @Mock(answer = Answers.RETURNS_DEEP_STUBS) WindowManager mWindowManager; private @Mock StatusBarStateController mStatusBarStateController; private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; private @Mock KeyguardViewController mKeyguardViewController; @@ -137,7 +139,9 @@ public class LockIconViewControllerTest extends SysuiTestCase { when(mLockIconView.getContext()).thenReturn(mContext); when(mLockIconView.findViewById(R.layout.udfps_aod_lock_icon)).thenReturn(mAodFp); when(mContext.getResources()).thenReturn(mResources); - when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); + when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager); + Rect windowBounds = new Rect(0, 0, 800, 1200); + when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds); when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL); when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index 624bedc30be9..11826954baee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -263,6 +263,34 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { } @Test + public void clockPositionedDependingOnMarginInSplitShade() { + when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) + .thenReturn(400); + mClockPositionAlgorithm.loadDimens(mResources); + givenLockScreen(); + mIsSplitShade = true; + // WHEN the position algorithm is run + positionClock(); + + assertThat(mClockPosition.clockY).isEqualTo(400); + } + + @Test + public void notifPaddingMakesUpToFullMarginInSplitShade() { + when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) + .thenReturn(100); + when(mResources.getDimensionPixelSize(R.dimen.split_shade_header_height)) + .thenReturn(70); + mClockPositionAlgorithm.loadDimens(mResources); + givenLockScreen(); + mIsSplitShade = true; + // WHEN the position algorithm is run + positionClock(); + // THEN the notif padding makes up lacking margin (margin - header height = 30). + assertThat(mClockPosition.stackScrollerPadding).isEqualTo(30); + } + + @Test public void notifPaddingExpandedAlignedWithClockInSplitShadeMode() { givenLockScreen(); mIsSplitShade = true; @@ -271,7 +299,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { positionClock(); // THEN the padding DOESN'T adjust for keyguard status height. assertThat(mClockPosition.stackScrollerPaddingExpanded) - .isEqualTo(mClockPosition.clockYFullyDozing); + .isEqualTo(mClockPosition.clockY); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index f89bbe898e35..766471b9a695 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -152,7 +152,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test - public void onWallpaperColorsChanged_setsTheme() { + public void onWallpaperColorsChanged_setsTheme_whenForeground() { // Should ask for a new theme when wallpaper colors change WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), Color.valueOf(Color.BLUE), null); @@ -180,13 +180,43 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { // But should change theme after changing wallpapers clearInvocations(mThemeOverlayApplier); - mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED)); + Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED); + intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, true); + mBroadcastReceiver.getValue().onReceive(null, intent); mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), null, null), WallpaperManager.FLAG_SYSTEM); verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); } @Test + public void onWallpaperColorsChanged_setsTheme_skipWhenBackground() { + // Should ask for a new theme when wallpaper colors change + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); + ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays = + ArgumentCaptor.forClass(Map.class); + + verify(mThemeOverlayApplier) + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); + + // Assert that we received the colors that we were expecting + assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) + .isEqualTo(new OverlayIdentifier("ffff0000")); + assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR)) + .isEqualTo(new OverlayIdentifier("ffff0000")); + + // Should not change theme after changing wallpapers, if intent doesn't have + // WallpaperManager.EXTRA_FROM_FOREGROUND_APP set to true. + clearInvocations(mThemeOverlayApplier); + mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED)); + mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), + null, null), WallpaperManager.FLAG_SYSTEM); + verify(mThemeOverlayApplier, never()) + .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + } + + @Test public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() { // Should ask for a new theme when wallpaper colors change WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), @@ -455,7 +485,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { // Regression test: null events should not reset the internal state and allow colors to be // applied again. clearInvocations(mThemeOverlayApplier); - mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED)); + Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED); + intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, true); + mBroadcastReceiver.getValue().onReceive(null, intent); mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM); verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(), any()); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 319fa94058dd..429696fd88a2 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -421,10 +421,10 @@ class UserController implements Handler.Callback { void setStopUserOnSwitch(@StopUserOnSwitch int value) { if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS) == PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + android.Manifest.permission.INTERACT_ACROSS_USERS) == PackageManager.PERMISSION_DENIED) { throw new SecurityException( - "You either need MANAGE_USERS or INTERACT_ACROSS_USERS_FULL permission to " + "You either need MANAGE_USERS or INTERACT_ACROSS_USERS permission to " + "call setStopUserOnSwitch()"); } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index a3b5e79cc8c7..fadaf1082e46 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2862,14 +2862,31 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public void setBrightnessConfigurationForUser( BrightnessConfiguration c, @UserIdInt int userId, String packageName) { - mLogicalDisplayMapper.forEachLocked(logicalDisplay -> { - if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) { - return; + mContext.enforceCallingOrSelfPermission( + Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS, + "Permission required to change the display's brightness configuration"); + if (userId != UserHandle.getCallingUserId()) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS, + "Permission required to change the display brightness" + + " configuration of another user"); + } + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + mLogicalDisplayMapper.forEachLocked(logicalDisplay -> { + if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) { + return; + } + final DisplayDevice displayDevice = + logicalDisplay.getPrimaryDisplayDeviceLocked(); + setBrightnessConfigurationForDisplayInternal(c, displayDevice.getUniqueId(), + userId, packageName); + }); } - final DisplayDevice displayDevice = logicalDisplay.getPrimaryDisplayDeviceLocked(); - setBrightnessConfigurationForDisplay(c, displayDevice.getUniqueId(), userId, - packageName); - }); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override // Binder call diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2f550c6e9338..47d8022a5acc 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -7699,7 +7699,10 @@ public class NotificationManagerService extends SystemService { final int waitMs = mAudioManager.getFocusRampTimeMs( AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, record.getAudioAttributes()); - if (DBG) Slog.v(TAG, "Delaying vibration by " + waitMs + "ms"); + if (DBG) { + Slog.v(TAG, "Delaying vibration for notification " + + record.getKey() + " by " + waitMs + "ms"); + } try { Thread.sleep(waitMs); } catch (InterruptedException e) { } @@ -7707,9 +7710,17 @@ public class NotificationManagerService extends SystemService { // so need to check the notification still valide for vibrate. synchronized (mNotificationLock) { if (mNotificationsByKey.get(record.getKey()) != null) { - vibrate(record, effect, true); + if (record.getKey().equals(mVibrateNotificationKey)) { + vibrate(record, effect, true); + } else { + if (DBG) { + Slog.v(TAG, "No vibration for notification " + + record.getKey() + ": a new notification is " + + "vibrating, or effects were cleared while waiting"); + } + } } else { - Slog.e(TAG, "No vibration for canceled notification : " + Slog.w(TAG, "No vibration for canceled notification " + record.getKey()); } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index a51ed09790a4..2f353d19b5df 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -17,6 +17,7 @@ package com.android.server.wallpaper; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.WallpaperManager.COMMAND_REAPPLY; import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; @@ -791,6 +792,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private final Context mContext; private final WindowManagerInternal mWindowManagerInternal; private final IPackageManager mIPackageManager; + private final ActivityManager mActivityManager; private final MyPackageMonitor mMonitor; private final AppOpsManager mAppOpsManager; @@ -939,6 +941,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub */ WallpaperColors primaryColors; + /** + * If the wallpaper was set from a foreground app (instead of from a background service). + */ + public boolean fromForegroundApp; + WallpaperConnection connection; long lastDiedTime; boolean wallpaperUpdating; @@ -1688,6 +1695,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mDisplayManager = mContext.getSystemService(DisplayManager.class); mDisplayManager.registerDisplayListener(mDisplayListener, null /* handler */); + mActivityManager = mContext.getSystemService(ActivityManager.class); mMonitor = new MyPackageMonitor(); mColorsChangedListeners = new SparseArray<>(); @@ -2648,6 +2656,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + final boolean fromForegroundApp = Binder.withCleanCallingIdentity(() -> + mActivityManager.getPackageImportance(callingPackage) == IMPORTANCE_FOREGROUND); + synchronized (mLock) { if (DEBUG) Slog.v(TAG, "setWallpaper which=0x" + Integer.toHexString(which)); WallpaperData wallpaper; @@ -2670,6 +2681,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.imageWallpaperPending = true; wallpaper.whichPending = which; wallpaper.setComplete = completion; + wallpaper.fromForegroundApp = fromForegroundApp; wallpaper.cropHint.set(cropHint); wallpaper.allowBackup = allowBackup; } @@ -3052,6 +3064,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.callbacks.finishBroadcast(); final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED); + intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, wallpaper.fromForegroundApp); mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId)); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 4190ff0b9bc7..1b7a012094f6 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1126,7 +1126,11 @@ class Task extends TaskFragment { if (inMultiWindowMode() || !hasChild()) return false; if (intent != null) { final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME; - return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags; + final Task task = getDisplayArea() != null ? getDisplayArea().getRootHomeTask() : null; + final boolean isLockTaskModeViolation = task != null + && mAtmService.getLockTaskController().isLockTaskModeViolation(task); + return (intent.getFlags() & returnHomeFlags) == returnHomeFlags + && !isLockTaskModeViolation; } final Task bottomTask = getBottomMostTask(); return bottomTask != this && bottomTask.returnsToHomeRootTask(); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 3ac30d0258a5..cadc81600bbb 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -51,6 +51,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -184,6 +185,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public DevicePolicyManager parentDpm; public DevicePolicyManagerServiceTestable dpms; + private boolean mIsAutomotive; + /* * The CA cert below is the content of cacert.pem as generated by: * @@ -266,6 +269,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { setUpUserManager(); when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true); + + mIsAutomotive = mContext.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } private TransferOwnershipMetadataManager getMockTransferMetadataManager() { @@ -2117,10 +2123,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertThat(dpm.getPasswordExpirationTimeout(admin1)) .isEqualTo(originalPasswordExpirationTimeout); - int originalPasswordQuality = dpm.getPasswordQuality(admin1); - assertExpectException(SecurityException.class, /* messageRegex= */ null, - () -> dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC)); - assertThat(dpm.getPasswordQuality(admin1)).isEqualTo(originalPasswordQuality); + if (isDeprecatedPasswordApisSupported()) { + int originalPasswordQuality = dpm.getPasswordQuality(admin1); + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpm.setPasswordQuality(admin1, + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC)); + assertThat(dpm.getPasswordQuality(admin1)).isEqualTo(originalPasswordQuality); + } } @Test @@ -5231,6 +5240,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testIsActivePasswordSufficient() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; mContext.packageName = admin1.getPackageName(); setupDeviceOwner(); @@ -5283,6 +5294,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testIsActivePasswordSufficient_noLockScreen() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // If there is no lock screen, the password is considered empty no matter what, because // it provides no security. when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false); @@ -5363,6 +5376,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testGetAggregatedPasswordMetrics_IgnoreProfileRequirement() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -5392,6 +5407,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testCanSetPasswordRequirementOnParentPreS() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -5407,6 +5424,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testCannotSetPasswordRequirementOnParent() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -5427,6 +5446,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_SeparateWorkChallenge_ProfileQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with empty separate challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5450,6 +5471,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_SeparateWorkChallenge_ProfileComplexityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with empty separate challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5473,6 +5496,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_SeparateWorkChallenge_ParentQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with empty separate challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5519,6 +5544,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_UnifiedWorkChallenge_ProfileQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with unified challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5565,6 +5592,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_UnifiedWorkChallenge_ParentQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with unified challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5625,6 +5654,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testPasswordQualityAppliesToParentPreS() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -7285,6 +7316,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_UnauthorizedCallersOnDO() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); // DO must be able to set it. @@ -7300,6 +7333,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_UnauthorizedCallersOnPO() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); // PO must be able to set it. @@ -7314,6 +7349,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_validValuesOnly() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7335,6 +7372,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_setAndGet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7348,6 +7387,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexityOnParent_setAndGet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -7366,6 +7407,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_isSufficient() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; mContext.packageName = admin1.getPackageName(); setupDeviceOwner(); @@ -7395,6 +7438,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_resetBySettingQuality() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7407,6 +7452,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_overridesQuality() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7421,6 +7468,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexityFailsWithQualityOnParent() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -7435,6 +7484,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetQualityOnParentFailsWithComplexityOnProfile() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -8015,4 +8066,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(mContext.getResources().getStringArray(R.array.vendor_policy_exempt_apps)) .thenReturn(new String[0]); } + + private boolean isDeprecatedPasswordApisSupported() { + return !mIsAutomotive; + } + + private void assumeDeprecatedPasswordApisSupported() { + assumeTrue("device doesn't support deprecated password APIs", + isDeprecatedPasswordApisSupported()); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index ea46eab6e8f9..d593e8000048 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -32,7 +32,6 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -40,6 +39,7 @@ import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.after; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -48,6 +48,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; @@ -73,7 +74,6 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; -import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; @@ -102,6 +102,7 @@ import java.util.Objects; @SmallTest @RunWith(AndroidJUnit4.class) +@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. public class BuzzBeepBlinkTest extends UiServiceTestCase { @Mock AudioManager mAudioManager; @@ -156,6 +157,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); + when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50); when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); when(mVibrator.hasFrequencyControl()).thenReturn(false); when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false); @@ -444,6 +446,11 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { timeout(MAX_VIBRATION_DELAY).times(1)); } + private void verifyDelayedNeverVibrate() { + verify(mVibrator, after(MAX_VIBRATION_DELAY).never()).vibrate(anyInt(), anyString(), any(), + anyString(), any(AudioAttributes.class)); + } + private void verifyVibrate(ArgumentMatcher<VibrationEffect> effectMatcher, VerificationMode verification) { ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class); @@ -1588,8 +1595,51 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // beep wasn't reset verifyNeverBeep(); verifyNeverVibrate(); - verify(mRingtonePlayer, never()).stopAsync(); - verify(mVibrator, never()).cancel(); + verifyNeverStopAudio(); + verifyNeverStopVibrate(); + } + + @Test + public void testRingtoneInsistentBeep_clearEffectsStopsSoundAndVibration() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"), + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification)); + mService.buzzBeepBlinkLocked(ringtoneNotification); + verifyBeepLooped(); + verifyDelayedVibrateLooped(); + + mService.clearSoundLocked(); + mService.clearVibrateLocked(); + + verifyStopAudio(); + verifyStopVibrate(); + } + + @Test + public void testRingtoneInsistentBeep_neverVibratesWhenEffectsClearedBeforeDelay() + throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"), + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification)); + mService.buzzBeepBlinkLocked(ringtoneNotification); + verifyBeepLooped(); + verifyNeverVibrate(); + + mService.clearSoundLocked(); + mService.clearVibrateLocked(); + + verifyStopAudio(); + verifyDelayedNeverVibrate(); } @Test diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 54d3af520d3c..4d2e00785d49 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3893,6 +3893,30 @@ public class CarrierConfigManager { public static final String KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL = "enabled_4g_opportunistic_network_scan_bool"; + /** + * Only relevant when the device supports opportunistic networks but does not support + * simultaneuous 5G+5G. Controls how long, in milliseconds, to wait before opportunistic network + * goes out of service before switching the 5G capability back to primary stack. The idea of + * waiting a few seconds is to minimize the calling of the expensive capability switching + * operation in the case where CBRS goes back into service shortly after going out of it. + * + * @hide + */ + public static final String KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG = + "time_to_switch_back_to_primary_if_opportunistic_oos_long"; + + /** + * Only relevant when the device supports opportunistic networks but does not support + * simultaneuous 5G+5G. Controls how long, in milliseconds, after 5G capability has switched back + * to primary stack due to opportunistic network being OOS. The idea is to minimizing the + * 'ping-ponging' effect where device is constantly witching capability back and forth between + * primary and opportunistic stack. + * + * @hide + */ + public static final String KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG + = "opportunistic_time_to_scan_after_capability_switch_to_primary_long"; + /** * Indicates zero or more emergency number prefix(es), because some carrier requires * if users dial an emergency number address with a specific prefix, the combination of the @@ -5766,6 +5790,10 @@ public class CarrierConfigManager { /* Default value is 2 seconds. */ sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_5G_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG, 2000); sDefaults.putBoolean(KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL, true); + sDefaults.putInt(KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG, 60000); + sDefaults.putInt( + KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG, + 120000); sDefaults.putAll(Gps.getDefaults()); sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY, new int[] { |