diff options
104 files changed, 2204 insertions, 870 deletions
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 14d0a563f2ca..9d624b6c0ed8 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -3517,7 +3517,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>An array of mandatory stream combinations which are applicable when device support the * 10-bit output capability * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } - * This is an app-readable conversion of the maximum resolution mandatory stream combination + * This is an app-readable conversion of the 10 bit output mandatory stream combination * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is @@ -3542,8 +3542,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>An array of mandatory stream combinations which are applicable when device lists * {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}. - * This is an app-readable conversion of the maximum resolution mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p> + * This is an app-readable conversion of the preview stabilization mandatory stream + * combination {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index c67a560b5885..7055c9c6aa4d 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -1000,24 +1000,25 @@ public abstract class CameraMetadata<TKey> { * camera's crop region is set to maximum size, the FOV of the physical streams for the * ultrawide lens will be the same as the logical stream, by making the crop region * smaller than its active array size to compensate for the smaller focal length.</p> - * <p>There are two ways for the application to capture RAW images from a logical camera - * with RAW capability:</p> + * <p>For a logical camera, typically the underlying physical cameras have different RAW + * capabilities (such as resolution or CFA pattern). There are two ways for the + * application to capture RAW images from the logical camera:</p> * <ul> - * <li>Because the underlying physical cameras may have different RAW capabilities (such - * as resolution or CFA pattern), to maintain backward compatibility, when a RAW stream - * is configured, the camera device makes sure the default active physical camera remains - * active and does not switch to other physical cameras. (One exception is that, if the - * logical camera consists of identical image sensors and advertises multiple focalLength - * due to different lenses, the camera device may generate RAW images from different - * physical cameras based on the focalLength being set by the application.) This - * backward-compatible approach usually results in loss of optical zoom, to telephoto - * lens or to ultrawide lens.</li> - * <li>Alternatively, to take advantage of the full zoomRatio range of the logical camera, - * the application should use {@link android.hardware.camera2.MultiResolutionImageReader } - * to capture RAW images from the currently active physical camera. Because different - * physical camera may have different RAW characteristics, the application needs to use - * the characteristics and result metadata of the active physical camera for the - * relevant RAW metadata.</li> + * <li>If the logical camera has RAW capability, the application can create and use RAW + * streams in the same way as before. In case a RAW stream is configured, to maintain + * backward compatibility, the camera device makes sure the default active physical + * camera remains active and does not switch to other physical cameras. (One exception + * is that, if the logical camera consists of identical image sensors and advertises + * multiple focalLength due to different lenses, the camera device may generate RAW + * images from different physical cameras based on the focalLength being set by the + * application.) This backward-compatible approach usually results in loss of optical + * zoom, to telephoto lens or to ultrawide lens.</li> + * <li>Alternatively, if supported by the device, + * {@link android.hardware.camera2.MultiResolutionImageReader } + * can be used to capture RAW images from one of the underlying physical cameras ( + * depending on current zoom level). Because different physical cameras may have + * different RAW characteristics, the application needs to use the characteristics + * and result metadata of the active physical camera for the relevant RAW metadata.</li> * </ul> * <p>The capture request and result metadata tags required for backward compatible camera * functionalities will be solely based on the logical camera capability. On the other diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 953f17a3a827..f9e84114a7da 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -9883,9 +9883,12 @@ public final class ViewRootImpl implements ViewParent, } void checkThread() { - if (mThread != Thread.currentThread()) { + Thread current = Thread.currentThread(); + if (mThread != current) { throw new CalledFromWrongThreadException( - "Only the original thread that created a view hierarchy can touch its views."); + "Only the original thread that created a view hierarchy can touch its views." + + " Expected: " + mThread.getName() + + " Calling: " + current.getName()); } } 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 1239cdc5b606..45785238fe00 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 @@ -194,7 +194,8 @@ public abstract class WMShellModule { DisplayController displayController, SyncTransactionQueue syncQueue, Optional<DesktopModeController> desktopModeController, - Optional<DesktopTasksController> desktopTasksController) { + Optional<DesktopTasksController> desktopTasksController, + Optional<SplitScreenController> splitScreenController) { if (DesktopModeStatus.isAnyEnabled()) { return new DesktopModeWindowDecorViewModel( context, @@ -204,7 +205,8 @@ public abstract class WMShellModule { displayController, syncQueue, desktopModeController, - desktopTasksController); + desktopTasksController, + splitScreenController); } return new CaptionWindowDecorViewModel( context, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index b59fe1818780..4cfaae6e51c7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -36,6 +36,7 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_U import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import android.content.ClipDescription; +import android.content.ComponentCallbacks2; import android.content.Context; import android.content.res.Configuration; import android.graphics.PixelFormat; @@ -58,9 +59,9 @@ import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.annotations.ExternalMainThread; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; -import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -70,7 +71,7 @@ import java.util.ArrayList; * Handles the global drag and drop handling for the Shell. */ public class DragAndDropController implements DisplayController.OnDisplaysChangedListener, - View.OnDragListener, ConfigurationChangeListener { + View.OnDragListener, ComponentCallbacks2 { private static final String TAG = DragAndDropController.class.getSimpleName(); @@ -119,7 +120,6 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange mMainExecutor.executeDelayed(() -> { mDisplayController.addDisplayWindowListener(this); }, 0); - mShellController.addConfigurationChangeListener(this); } /** @@ -180,6 +180,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange try { wm.addView(rootView, layoutParams); addDisplayDropTarget(displayId, context, wm, rootView, dragLayout); + context.registerComponentCallbacks(this); } catch (WindowManager.InvalidDisplayException e) { Slog.w(TAG, "Unable to add view for display id: " + displayId); } @@ -209,6 +210,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange if (pd == null) { return; } + pd.context.unregisterComponentCallbacks(this); pd.wm.removeViewImmediate(pd.rootView); mDisplayDropTargets.remove(displayId); } @@ -328,18 +330,29 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange return mimeTypes; } + // Note: Component callbacks are always called on the main thread of the process + @ExternalMainThread @Override - public void onThemeChanged() { - for (int i = 0; i < mDisplayDropTargets.size(); i++) { - mDisplayDropTargets.get(i).dragLayout.onThemeChange(); - } + public void onConfigurationChanged(Configuration newConfig) { + mMainExecutor.execute(() -> { + for (int i = 0; i < mDisplayDropTargets.size(); i++) { + mDisplayDropTargets.get(i).dragLayout.onConfigChanged(newConfig); + } + }); } + // Note: Component callbacks are always called on the main thread of the process + @ExternalMainThread @Override - public void onConfigurationChanged(Configuration newConfig) { - for (int i = 0; i < mDisplayDropTargets.size(); i++) { - mDisplayDropTargets.get(i).dragLayout.onConfigChanged(newConfig); - } + public void onTrimMemory(int level) { + // Do nothing + } + + // Note: Component callbacks are always called on the main thread of the process + @ExternalMainThread + @Override + public void onLowMemory() { + // Do nothing } private static class PerDisplay { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index 44fd8eec4d06..fe42822ab6a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -18,6 +18,8 @@ package com.android.wm.shell.draganddrop; import static android.app.StatusBarManager.DISABLE_NONE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS; +import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -72,6 +74,7 @@ public class DragLayout extends LinearLayout { private final SplitScreenController mSplitScreenController; private final IconProvider mIconProvider; private final StatusBarManager mStatusBarManager; + private final Configuration mLastConfiguration = new Configuration(); private DragAndDropPolicy.Target mCurrentTarget = null; private DropZoneView mDropZoneView1; @@ -92,6 +95,7 @@ public class DragLayout extends LinearLayout { mIconProvider = iconProvider; mPolicy = new DragAndDropPolicy(context, splitScreenController); mStatusBarManager = context.getSystemService(StatusBarManager.class); + mLastConfiguration.setTo(context.getResources().getConfiguration()); mDisplayMargin = context.getResources().getDimensionPixelSize( R.dimen.drop_layout_display_margin); @@ -132,11 +136,6 @@ public class DragLayout extends LinearLayout { return super.onApplyWindowInsets(insets); } - public void onThemeChange() { - mDropZoneView1.onThemeChange(); - mDropZoneView2.onThemeChange(); - } - public void onConfigChanged(Configuration newConfig) { if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE && getOrientation() != HORIZONTAL) { @@ -147,6 +146,15 @@ public class DragLayout extends LinearLayout { setOrientation(LinearLayout.VERTICAL); updateContainerMargins(newConfig.orientation); } + + final int diff = newConfig.diff(mLastConfiguration); + final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0 + || (diff & CONFIG_UI_MODE) != 0; + if (themeChanged) { + mDropZoneView1.onThemeChange(); + mDropZoneView2.onThemeChange(); + } + mLastConfiguration.setTo(newConfig); } private void updateContainerMarginsForSingleTask() { 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 c7ad4fdcacf0..94b9e907fa76 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 @@ -422,6 +422,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.goToFullscreenFromSplit(); } + /** Move the specified task to fullscreen, regardless of focus state. */ + public void moveTaskToFullscreen(int taskId) { + mStageCoordinator.moveTaskToFullscreen(taskId); + } + public boolean isLaunchToSplit(TaskInfo taskInfo) { return mStageCoordinator.isLaunchToSplit(taskInfo); } 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 f102f8e2f5df..a6733843d4c3 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 @@ -2390,6 +2390,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT); } + /** Move the specified task to fullscreen, regardless of focus state. */ + public void moveTaskToFullscreen(int taskId) { + boolean leftOrTop; + if (mMainStage.containsTask(taskId)) { + leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); + } else if (mSideStage.containsTask(taskId)) { + leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT); + } else { + return; + } + mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT); + + } + boolean isLaunchToSplit(TaskInfo taskInfo) { return getActivateSplitPosition(taskInfo) != SPLIT_POSITION_UNDEFINED; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 8dfa18ca2891..c517f7be330b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -20,10 +20,14 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; + import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; +import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Handler; import android.os.Looper; @@ -37,7 +41,6 @@ import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; @@ -50,6 +53,7 @@ import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.splitscreen.SplitScreenController; import java.util.Optional; @@ -80,6 +84,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final InputMonitorFactory mInputMonitorFactory; private TaskOperations mTaskOperations; + private Optional<SplitScreenController> mSplitScreenController; + public DesktopModeWindowDecorViewModel( Context context, Handler mainHandler, @@ -88,7 +94,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { DisplayController displayController, SyncTransactionQueue syncQueue, Optional<DesktopModeController> desktopModeController, - Optional<DesktopTasksController> desktopTasksController) { + Optional<DesktopTasksController> desktopTasksController, + Optional<SplitScreenController> splitScreenController) { this( context, mainHandler, @@ -98,6 +105,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { syncQueue, desktopModeController, desktopTasksController, + splitScreenController, new DesktopModeWindowDecoration.Factory(), new InputMonitorFactory()); } @@ -112,6 +120,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { SyncTransactionQueue syncQueue, Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController, + Optional<SplitScreenController> splitScreenController, DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory, InputMonitorFactory inputMonitorFactory) { mContext = context; @@ -120,6 +129,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); mTaskOrganizer = taskOrganizer; mDisplayController = displayController; + mSplitScreenController = splitScreenController; mSyncQueue = syncQueue; mDesktopModeController = desktopModeController; mDesktopTasksController = desktopTasksController; @@ -230,6 +240,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final int id = v.getId(); if (id == R.id.close_window || id == R.id.close_button) { mTaskOperations.closeTask(mTaskToken); + if (mSplitScreenController.isPresent() + && mSplitScreenController.get().isSplitScreenVisible()) { + int remainingTaskPosition = mTaskId == mSplitScreenController.get() + .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId + ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT; + ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController.get() + .getTaskInfo(remainingTaskPosition); + mSplitScreenController.get().moveTaskToFullscreen(remainingTask.taskId); + } } else if (id == R.id.back_button) { mTaskOperations.injectBackKey(); } else if (id == R.id.caption_handle) { @@ -261,9 +280,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (taskInfo.isFocused) { return mDragDetector.isDragEvent(); } - final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reorder(mTaskToken, true /* onTop */); - mSyncQueue.queue(wct); return false; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: @@ -401,14 +417,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { * @param ev the {@link MotionEvent} received by {@link EventReceiver} */ private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) { + final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev); if (DesktopModeStatus.isProto2Enabled()) { - final DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); - if (focusedDecor == null - || focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) { - handleCaptionThroughStatusBar(ev); + if (relevantDecor == null + || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) { + handleCaptionThroughStatusBar(ev, relevantDecor); } } - handleEventOutsideFocusedCaption(ev); + handleEventOutsideFocusedCaption(ev, relevantDecor); // Prevent status bar from reacting to a caption drag. if (DesktopModeStatus.isProto2Enabled()) { if (mTransitionDragActive) { @@ -422,16 +438,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu - private void handleEventOutsideFocusedCaption(MotionEvent ev) { + private void handleEventOutsideFocusedCaption(MotionEvent ev, + DesktopModeWindowDecoration relevantDecor) { final int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { - final DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); - if (focusedDecor == null) { + if (relevantDecor == null) { return; } if (!mTransitionDragActive) { - focusedDecor.closeHandleMenuIfNeeded(ev); + relevantDecor.closeHandleMenuIfNeeded(ev); } } } @@ -441,39 +457,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { * Perform caption actions if not able to through normal means. * Turn on desktop mode if handle is dragged below status bar. */ - private void handleCaptionThroughStatusBar(MotionEvent ev) { + private void handleCaptionThroughStatusBar(MotionEvent ev, + DesktopModeWindowDecoration relevantDecor) { switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: { // Begin drag through status bar if applicable. - final DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); - if (focusedDecor != null) { + if (relevantDecor != null) { boolean dragFromStatusBarAllowed = false; if (DesktopModeStatus.isProto2Enabled()) { // In proto2 any full screen task can be dragged to freeform - dragFromStatusBarAllowed = focusedDecor.mTaskInfo.getWindowingMode() + dragFromStatusBarAllowed = relevantDecor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; } - if (dragFromStatusBarAllowed && focusedDecor.checkTouchEventInHandle(ev)) { + if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) { mTransitionDragActive = true; } } break; } case MotionEvent.ACTION_UP: { - final DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); - if (focusedDecor == null) { + if (relevantDecor == null) { mTransitionDragActive = false; return; } if (mTransitionDragActive) { mTransitionDragActive = false; final int statusBarHeight = mDisplayController - .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top; + .getDisplayLayout(relevantDecor.mTaskInfo.displayId).stableInsets().top; if (ev.getY() > statusBarHeight) { if (DesktopModeStatus.isProto2Enabled()) { mDesktopTasksController.ifPresent( - c -> c.moveToDesktop(focusedDecor.mTaskInfo)); + c -> c.moveToDesktop(relevantDecor.mTaskInfo)); } else if (DesktopModeStatus.isProto1Enabled()) { mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true)); } @@ -481,7 +496,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } } - focusedDecor.checkClickEvent(ev); + relevantDecor.checkClickEvent(ev); break; } case MotionEvent.ACTION_CANCEL: { @@ -491,6 +506,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } @Nullable + private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) { + if (mSplitScreenController.isPresent() + && mSplitScreenController.get().isSplitScreenVisible()) { + // We can't look at focused task here as only one task will have focus. + return getSplitScreenDecor(ev); + } else { + return getFocusedDecor(); + } + } + + @Nullable + private DesktopModeWindowDecoration getSplitScreenDecor(MotionEvent ev) { + ActivityManager.RunningTaskInfo topOrLeftTask = + mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); + ActivityManager.RunningTaskInfo bottomOrRightTask = + mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); + if (topOrLeftTask != null && topOrLeftTask.getConfiguration() + .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) { + return mWindowDecorByTaskId.get(topOrLeftTask.taskId); + } else if (bottomOrRightTask != null && bottomOrRightTask.getConfiguration() + .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) { + Rect bottomOrRightBounds = bottomOrRightTask.getConfiguration().windowConfiguration + .getBounds(); + ev.offsetLocation(-bottomOrRightBounds.left, -bottomOrRightBounds.top); + return mWindowDecorByTaskId.get(bottomOrRightTask.taskId); + } else { + return null; + } + + } + + @Nullable private DesktopModeWindowDecoration getFocusedDecor() { final int size = mWindowDecorByTaskId.size(); DesktopModeWindowDecoration focusedDecor = null; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java index b6dbcf204364..523cb6629d9a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java @@ -48,7 +48,6 @@ import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -82,7 +81,7 @@ public class DragAndDropControllerTest extends ShellTestCase { @Mock private ShellExecutor mMainExecutor; @Mock - private SplitScreenController mSplitScreenController; + private WindowManager mWindowManager; private DragAndDropController mController; @@ -100,11 +99,6 @@ public class DragAndDropControllerTest extends ShellTestCase { } @Test - public void instantiateController_registerConfigChangeListener() { - verify(mShellController, times(1)).addConfigurationChangeListener(any()); - } - - @Test public void testIgnoreNonDefaultDisplays() { final int nonDefaultDisplayId = 12345; final View dragLayout = mock(View.class); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java index 355072116cb1..1d1aa795173c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java @@ -49,6 +49,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopTasksController; +import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; import org.junit.Test; @@ -73,6 +74,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { @Mock private Choreographer mMainChoreographer; @Mock private ShellTaskOrganizer mTaskOrganizer; @Mock private DisplayController mDisplayController; + @Mock private SplitScreenController mSplitScreenController; @Mock private SyncTransactionQueue mSyncQueue; @Mock private DesktopModeController mDesktopModeController; @Mock private DesktopTasksController mDesktopTasksController; @@ -98,6 +100,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { mSyncQueue, Optional.of(mDesktopModeController), Optional.of(mDesktopTasksController), + Optional.of(mSplitScreenController), mDesktopModeWindowDecorFactory, mMockInputMonitorFactory ); diff --git a/packages/AppPredictionLib/Android.bp b/packages/AppPredictionLib/Android.bp index 5a68fdc9ae75..31c193631602 100644 --- a/packages/AppPredictionLib/Android.bp +++ b/packages/AppPredictionLib/Android.bp @@ -25,7 +25,7 @@ android_library { name: "app_prediction", sdk_version: "system_current", - min_sdk_version: "system_current", + min_sdk_version: "current", srcs: [ "src/**/*.java", diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index 2614644feb20..688fc720d058 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -55,6 +55,7 @@ public class DreamBackend { public ComponentName settingsComponentName; public CharSequence description; public Drawable previewImage; + public boolean supportsComplications = false; @Override public String toString() { @@ -175,6 +176,7 @@ public class DreamBackend { if (dreamMetadata != null) { dreamInfo.settingsComponentName = dreamMetadata.settingsActivity; dreamInfo.previewImage = dreamMetadata.previewImage; + dreamInfo.supportsComplications = dreamMetadata.showComplications; } dreamInfos.add(dreamInfo); } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index ab36d5899739..00c0a0b3e7b3 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -38,6 +38,7 @@ import kotlinx.coroutines.launch private val TAG = ClockRegistry::class.simpleName!! private const val DEBUG = true +private val KEY_TIMESTAMP = "appliedTimestamp" /** ClockRegistry aggregates providers and plugins */ open class ClockRegistry( @@ -134,9 +135,9 @@ open class ClockRegistry( assertNotMainThread() try { - value?._applied_timestamp = System.currentTimeMillis() - val json = ClockSettings.serialize(value) + value?.metadata?.put(KEY_TIMESTAMP, System.currentTimeMillis()) + val json = ClockSettings.serialize(value) if (handleAllUsers) { Settings.Secure.putStringForUser( context.contentResolver, @@ -172,7 +173,7 @@ open class ClockRegistry( clockChangeListeners.forEach(func) } - private fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) { + public fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) { scope.launch(bgDispatcher) { applySettings(mutator(settings ?: ClockSettings())) } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 54c837fb8760..babe5700a01c 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -192,12 +192,13 @@ data class ClockSettings( val clockId: ClockId? = null, val seedColor: Int? = null, ) { - var _applied_timestamp: Long? = null + // Exclude metadata from equality checks + var metadata: JSONObject = JSONObject() companion object { private val KEY_CLOCK_ID = "clockId" private val KEY_SEED_COLOR = "seedColor" - private val KEY_TIMESTAMP = "_applied_timestamp" + private val KEY_METADATA = "metadata" fun serialize(setting: ClockSettings?): String { if (setting == null) { @@ -207,7 +208,7 @@ data class ClockSettings( return JSONObject() .put(KEY_CLOCK_ID, setting.clockId) .put(KEY_SEED_COLOR, setting.seedColor) - .put(KEY_TIMESTAMP, setting._applied_timestamp) + .put(KEY_METADATA, setting.metadata) .toString() } @@ -219,11 +220,11 @@ data class ClockSettings( val json = JSONObject(jsonStr) val result = ClockSettings( - json.getString(KEY_CLOCK_ID), + if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null, if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null ) - if (!json.isNull(KEY_TIMESTAMP)) { - result._applied_timestamp = json.getLong(KEY_TIMESTAMP) + if (!json.isNull(KEY_METADATA)) { + result.metadata = json.getJSONObject(KEY_METADATA) } return result } diff --git a/packages/SystemUI/res-keyguard/drawable-mdpi/ic_lockscreen_sim.png b/packages/SystemUI/res-keyguard/drawable-mdpi/ic_lockscreen_sim.png Binary files differdeleted file mode 100644 index 2e259c3e17c1..000000000000 --- a/packages/SystemUI/res-keyguard/drawable-mdpi/ic_lockscreen_sim.png +++ /dev/null diff --git a/packages/SystemUI/res-keyguard/drawable-xhdpi/ic_lockscreen_sim.png b/packages/SystemUI/res-keyguard/drawable-xhdpi/ic_lockscreen_sim.png Binary files differdeleted file mode 100644 index f4de96adae30..000000000000 --- a/packages/SystemUI/res-keyguard/drawable-xhdpi/ic_lockscreen_sim.png +++ /dev/null diff --git a/packages/SystemUI/res-keyguard/drawable-hdpi/ic_lockscreen_sim.png b/packages/SystemUI/res-keyguard/drawable/ic_lockscreen_sim.png Binary files differindex 7cf9e3699ceb..7cf9e3699ceb 100644 --- a/packages/SystemUI/res-keyguard/drawable-hdpi/ic_lockscreen_sim.png +++ b/packages/SystemUI/res-keyguard/drawable/ic_lockscreen_sim.png diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml index 411fea5dd22d..48769fdebd99 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml @@ -14,10 +14,12 @@ ~ limitations under the License --> -<merge xmlns:android="http://schemas.android.com/apk/res/android"> +<merge xmlns:android="http://schemas.android.com/apk/res/android" > <TextView android:id="@+id/digit_text" style="@style/Widget.TextView.NumPadKey.Digit" + android:autoSizeMaxTextSize="32sp" + android:autoSizeTextType="uniform" android:layout_width="wrap_content" android:layout_height="wrap_content" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml index 7db0fe908ec0..728d861ab693 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml @@ -1,5 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- +<?xml version="1.0" encoding="utf-8"?><!-- ** ** Copyright 2012, The Android Open Source Project ** @@ -17,185 +16,185 @@ */ --> <!-- This is the SIM PIN view that allows the user to enter a SIM PIN to unlock the device. --> -<com.android.keyguard.KeyguardSimPinView - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/res-auto" - android:id="@+id/keyguard_sim_pin_view" - android:orientation="vertical" +<com.android.keyguard.KeyguardSimPinView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/res-auto" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/keyguard_sim_pin_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + androidprv:layout_maxWidth="@dimen/keyguard_security_width" + android:layout_gravity="center_horizontal|bottom"> + <include layout="@layout/keyguard_bouncer_message_area"/> + + <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" - android:layout_height="match_parent" - androidprv:layout_maxWidth="@dimen/keyguard_security_width" - android:layout_gravity="center_horizontal|bottom"> - <include layout="@layout/keyguard_bouncer_message_area" /> - <Space - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" /> - <ImageView - android:id="@+id/keyguard_sim" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:tint="@color/background_protected" - android:src="@drawable/ic_lockscreen_sim"/> - <LinearLayout + android:layout_height="0dp" + android:layout_weight="1" + android:layoutDirection="ltr"> + <LinearLayout + android:id="@+id/pin_area" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:gravity="center" - android:layoutDirection="ltr" - > - <include layout="@layout/keyguard_esim_area" - android:id="@+id/keyguard_esim_area" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - <RelativeLayout - android:id="@+id/row0" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingBottom="4dp" - > + android:gravity="center_horizontal" + android:paddingTop="@dimen/num_pad_entry_row_margin_bottom" + android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom" + androidprv:layout_constraintBottom_toTopOf="@+id/flow1" + androidprv:layout_constraintEnd_toEndOf="parent" + androidprv:layout_constraintStart_toStartOf="parent" + androidprv:layout_constraintTop_toTopOf="parent"> + + <ImageView + android:id="@+id/keyguard_sim" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_gravity="center_horizontal" + android:src="@drawable/ic_lockscreen_sim" + app:tint="@color/background_protected" /> + + <include + android:id="@+id/keyguard_esim_area" + layout="@layout/keyguard_esim_area" + android:layout_gravity="center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + <com.android.keyguard.PasswordTextView android:id="@+id/simPinEntry" style="@style/Widget.TextView.Password" android:layout_width="@dimen/keyguard_security_width" android:layout_height="@dimen/keyguard_password_height" - android:layout_centerHorizontal="true" - android:layout_marginRight="72dp" android:contentDescription="@string/keyguard_accessibility_sim_pin_area" - android:gravity="center" - androidprv:scaledTextSize="@integer/scaled_password_text_size" /> - </RelativeLayout> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" android:layout_gravity="center_horizontal" - android:layout_marginBottom="@dimen/num_pad_row_margin_bottom" - > - <com.android.keyguard.NumPadKey - android:id="@+id/key1" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/simPinEntry" - androidprv:digit="1" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key2" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/simPinEntry" - androidprv:digit="2" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key3" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - androidprv:textView="@+id/simPinEntry" - androidprv:digit="3" - /> - </LinearLayout> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="@dimen/num_pad_row_margin_bottom" - > - <com.android.keyguard.NumPadKey - android:id="@+id/key4" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/simPinEntry" - androidprv:digit="4" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key5" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/simPinEntry" - androidprv:digit="5" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key6" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - androidprv:textView="@+id/simPinEntry" - androidprv:digit="6" - /> - </LinearLayout> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="@dimen/num_pad_row_margin_bottom" - > - <com.android.keyguard.NumPadKey - android:id="@+id/key7" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/simPinEntry" - androidprv:digit="7" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key8" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/simPinEntry" - androidprv:digit="8" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key9" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - androidprv:textView="@+id/simPinEntry" - androidprv:digit="9" - /> - </LinearLayout> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_gravity="center_horizontal" - > - <com.android.keyguard.NumPadButton - android:id="@+id/delete_button" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - android:contentDescription="@string/keyboardview_keycode_delete" - style="@style/NumPadKey.Delete" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key0" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/simPinEntry" - androidprv:digit="0" - /> - <com.android.keyguard.NumPadButton - android:id="@+id/key_enter" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - style="@style/NumPadKey.Enter" - android:contentDescription="@string/keyboardview_keycode_enter" - /> + androidprv:scaledTextSize="@integer/scaled_password_text_size" /> </LinearLayout> - </LinearLayout> - <include layout="@layout/keyguard_eca" - android:id="@+id/keyguard_selector_fade_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:layout_gravity="bottom|center_horizontal" - android:layout_marginTop="@dimen/keyguard_eca_top_margin" - android:layout_marginBottom="2dp" - android:gravity="center_horizontal"/> + + <androidx.constraintlayout.helper.widget.Flow + android:id="@+id/flow1" + android:layout_width="0dp" + android:layout_height="0dp" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="horizontal" + androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter" + androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end" + androidprv:flow_horizontalStyle="packed" + androidprv:flow_maxElementsWrap="3" + androidprv:flow_verticalBias="1.0" + androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom" + androidprv:flow_verticalStyle="packed" + androidprv:flow_wrapMode="aligned" + androidprv:layout_constraintBottom_toBottomOf="parent" + androidprv:layout_constraintEnd_toEndOf="parent" + androidprv:layout_constraintStart_toStartOf="parent" + androidprv:layout_constraintTop_toBottomOf="@id/pin_area" /> + + <com.android.keyguard.NumPadButton + android:id="@+id/delete_button" + style="@style/NumPadKey.Delete" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key0" + android:contentDescription="@string/keyboardview_keycode_delete" /> + + <com.android.keyguard.NumPadButton + android:id="@+id/key_enter" + style="@style/NumPadKey.Enter" + android:layout_width="0dp" + android:layout_height="0dp" + android:contentDescription="@string/keyboardview_keycode_enter" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key1" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key2" + androidprv:digit="1" + androidprv:textView="@+id/simPinEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key2" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key3" + androidprv:digit="2" + androidprv:textView="@+id/simPinEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key3" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key4" + androidprv:digit="3" + androidprv:textView="@+id/simPinEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key4" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key5" + androidprv:digit="4" + androidprv:textView="@+id/simPinEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key5" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key6" + androidprv:digit="5" + androidprv:textView="@+id/simPinEntry" /> + + + <com.android.keyguard.NumPadKey + android:id="@+id/key6" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key7" + androidprv:digit="6" + androidprv:textView="@+id/simPinEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key7" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key8" + androidprv:digit="7" + androidprv:textView="@+id/simPinEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key8" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key9" + androidprv:digit="8" + androidprv:textView="@+id/simPinEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key9" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/delete_button" + androidprv:digit="9" + androidprv:textView="@+id/simPinEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key0" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key_enter" + androidprv:digit="0" + androidprv:textView="@+id/simPinEntry" /> + </androidx.constraintlayout.widget.ConstraintLayout> + + <include + android:id="@+id/keyguard_selector_fade_container" + layout="@layout/keyguard_eca" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom|center_horizontal" + android:layout_marginBottom="2dp" + android:layout_marginTop="@dimen/keyguard_eca_top_margin" + android:gravity="center_horizontal" + android:orientation="vertical" /> </com.android.keyguard.KeyguardSimPinView> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml index 422bd4c12e8e..7e24d1231aee 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml @@ -21,6 +21,7 @@ <com.android.keyguard.KeyguardSimPukView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/res-auto" + xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/keyguard_sim_puk_view" android:orientation="vertical" android:layout_width="match_parent" @@ -29,173 +30,165 @@ android:layout_gravity="center_horizontal|bottom"> <include layout="@layout/keyguard_bouncer_message_area"/> - <Space + <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="0dp" - android:layout_weight="1" /> - - <ImageView - android:id="@+id/keyguard_sim" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:tint="@color/background_protected" - android:src="@drawable/ic_lockscreen_sim"/> - - <LinearLayout + android:layout_weight="1" + android:layoutDirection="ltr"> + <LinearLayout + android:id="@+id/pin_area" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:gravity="center" - android:layoutDirection="ltr" - > - <include layout="@layout/keyguard_esim_area" - android:id="@+id/keyguard_esim_area" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - - <RelativeLayout - android:id="@+id/row0" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingBottom="4dp" - > + android:gravity="center_horizontal" + android:paddingTop="@dimen/num_pad_entry_row_margin_bottom" + android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom" + androidprv:layout_constraintBottom_toTopOf="@+id/flow1" + androidprv:layout_constraintEnd_toEndOf="parent" + androidprv:layout_constraintStart_toStartOf="parent" + androidprv:layout_constraintTop_toTopOf="parent"> + + <ImageView + android:id="@+id/keyguard_sim" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_gravity="center_horizontal" + android:src="@drawable/ic_lockscreen_sim" + app:tint="@color/background_protected" /> + + <include + android:id="@+id/keyguard_esim_area" + layout="@layout/keyguard_esim_area" + android:layout_gravity="center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> <com.android.keyguard.PasswordTextView android:id="@+id/pukEntry" style="@style/Widget.TextView.Password" android:layout_width="@dimen/keyguard_security_width" android:layout_height="@dimen/keyguard_password_height" - android:layout_centerHorizontal="true" - android:layout_marginRight="72dp" - android:contentDescription="@string/keyguard_accessibility_sim_puk_area" - android:gravity="center" - androidprv:scaledTextSize="@integer/scaled_password_text_size" /> - </RelativeLayout> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="@dimen/num_pad_row_margin_bottom" - > - <com.android.keyguard.NumPadKey - android:id="@+id/key1" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/pukEntry" - androidprv:digit="1" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key2" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/pukEntry" - androidprv:digit="2" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key3" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - androidprv:textView="@+id/pukEntry" - androidprv:digit="3" - /> - </LinearLayout> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="@dimen/num_pad_row_margin_bottom" - - > - <com.android.keyguard.NumPadKey - android:id="@+id/key4" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/pukEntry" - androidprv:digit="4" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key5" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/pukEntry" - androidprv:digit="5" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key6" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - androidprv:textView="@+id/pukEntry" - androidprv:digit="6" - /> - </LinearLayout> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="@dimen/num_pad_row_margin_bottom" - > - <com.android.keyguard.NumPadKey - android:id="@+id/key7" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/pukEntry" - androidprv:digit="7" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key8" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/pukEntry" - androidprv:digit="8" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key9" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - androidprv:textView="@+id/pukEntry" - androidprv:digit="9" - /> - </LinearLayout> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" + android:contentDescription="@string/keyguard_accessibility_sim_pin_area" android:layout_gravity="center_horizontal" - > - <com.android.keyguard.NumPadButton - android:id="@+id/delete_button" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - android:contentDescription="@string/keyboardview_keycode_delete" - style="@style/NumPadKey.Delete" - /> - <com.android.keyguard.NumPadKey - android:id="@+id/key0" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/num_pad_key_margin_end" - androidprv:textView="@+id/pukEntry" - androidprv:digit="0" - /> - <com.android.keyguard.NumPadButton - android:id="@+id/key_enter" - android:layout_width="@dimen/num_pad_key_width" - android:layout_height="match_parent" - style="@style/NumPadKey.Enter" - android:contentDescription="@string/keyboardview_keycode_enter" - /> + androidprv:scaledTextSize="@integer/scaled_password_text_size" /> </LinearLayout> - </LinearLayout> + + <androidx.constraintlayout.helper.widget.Flow + android:id="@+id/flow1" + android:layout_width="0dp" + android:layout_height="0dp" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="horizontal" + androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter" + androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end" + androidprv:flow_horizontalStyle="packed" + androidprv:flow_maxElementsWrap="3" + androidprv:flow_verticalBias="1.0" + androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom" + androidprv:flow_verticalStyle="packed" + androidprv:flow_wrapMode="aligned" + androidprv:layout_constraintBottom_toBottomOf="parent" + androidprv:layout_constraintEnd_toEndOf="parent" + androidprv:layout_constraintStart_toStartOf="parent" + androidprv:layout_constraintTop_toBottomOf="@id/pin_area" /> + + <com.android.keyguard.NumPadButton + android:id="@+id/delete_button" + style="@style/NumPadKey.Delete" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key0" + android:contentDescription="@string/keyboardview_keycode_delete" /> + + <com.android.keyguard.NumPadButton + android:id="@+id/key_enter" + style="@style/NumPadKey.Enter" + android:layout_width="0dp" + android:layout_height="0dp" + android:contentDescription="@string/keyboardview_keycode_enter" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key1" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key2" + androidprv:digit="1" + androidprv:textView="@+id/pukEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key2" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key3" + androidprv:digit="2" + androidprv:textView="@+id/pukEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key3" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key4" + androidprv:digit="3" + androidprv:textView="@+id/pukEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key4" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key5" + androidprv:digit="4" + androidprv:textView="@+id/pukEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key5" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key6" + androidprv:digit="5" + androidprv:textView="@+id/pukEntry" /> + + + <com.android.keyguard.NumPadKey + android:id="@+id/key6" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key7" + androidprv:digit="6" + androidprv:textView="@+id/pukEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key7" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key8" + androidprv:digit="7" + androidprv:textView="@+id/pukEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key8" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key9" + androidprv:digit="8" + androidprv:textView="@+id/pukEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key9" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/delete_button" + androidprv:digit="9" + androidprv:textView="@+id/pukEntry" /> + + <com.android.keyguard.NumPadKey + android:id="@+id/key0" + android:layout_width="0dp" + android:layout_height="0dp" + android:accessibilityTraversalBefore="@id/key_enter" + androidprv:digit="0" + androidprv:textView="@+id/pukEntry" /> + </androidx.constraintlayout.widget.ConstraintLayout> <include layout="@layout/keyguard_eca" android:id="@+id/keyguard_selector_fade_container" diff --git a/packages/SystemUI/res/drawable/controls_panel_background.xml b/packages/SystemUI/res/drawable/controls_panel_background.xml index 9092877fc6fa..fc108a5e1e06 100644 --- a/packages/SystemUI/res/drawable/controls_panel_background.xml +++ b/packages/SystemUI/res/drawable/controls_panel_background.xml @@ -18,5 +18,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#1F1F1F" /> - <corners android:radius="@dimen/notification_corner_radius" /> + <corners android:radius="@dimen/controls_panel_corner_radius" /> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_footer_edit_circle.xml b/packages/SystemUI/res/drawable/qs_footer_edit_circle.xml new file mode 100644 index 000000000000..2e29cae0ca4f --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_footer_edit_circle.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:inset="@dimen/qs_footer_action_inset"> + <ripple + android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <!-- We make this shape a rounded rectangle instead of a oval so that it can animate --> + <!-- properly into an app/dialog. --> + <shape android:shape="rectangle"> + <solid android:color="@android:color/white"/> + <corners android:radius="@dimen/qs_footer_action_corner_radius"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/qs_footer_action_corner_radius"/> + </shape> + </item> + + </ripple> +</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_fullscreen.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml index e08e63b39e59..fa703038cc7e 100644 --- a/packages/SystemUI/res/layout/controls_fullscreen.xml +++ b/packages/SystemUI/res/layout/controls_fullscreen.xml @@ -15,19 +15,11 @@ limitations under the License. --> -<FrameLayout +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/control_detail_root" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> - - <LinearLayout - android:id="@+id/global_actions_controls" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:paddingHorizontal="@dimen/controls_padding_horizontal" /> - -</FrameLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml index aa211bf8cfdc..71561c07ebd3 100644 --- a/packages/SystemUI/res/layout/controls_with_favorites.xml +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -13,82 +13,94 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<merge - xmlns:android="http://schemas.android.com/apk/res/android"> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:orientation="vertical" + tools:parentTag="android.widget.LinearLayout"> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_marginBottom="@dimen/controls_header_bottom_margin"> - - <!-- make sure the header stays centered in the layout by adding a spacer --> - <Space - android:id="@+id/controls_spacer" - android:layout_width="@dimen/controls_header_menu_size" - android:layout_height="1dp" - android:visibility="gone" /> - - <ImageView - android:id="@+id/controls_close" - android:contentDescription="@string/accessibility_desc_close" - android:src="@drawable/ic_close" - android:background="?android:attr/selectableItemBackgroundBorderless" - android:tint="@color/control_primary_text" - android:layout_width="@dimen/controls_header_menu_size" - android:layout_height="@dimen/controls_header_menu_size" - android:padding="12dp" - android:visibility="gone" /> - <!-- need to keep this outer view in order to have a correctly sized anchor - for the dropdown menu, as well as dropdown background in the right place --> <LinearLayout - android:id="@+id/controls_header" - android:orientation="horizontal" - android:layout_width="0dp" - android:layout_weight="1" - android:minHeight="48dp" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center" - android:gravity="center"> - <TextView - style="@style/Control.Spinner.Header" - android:clickable="false" - android:id="@+id/app_or_structure_spinner" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" /> + android:paddingHorizontal="@dimen/controls_header_horizontal_padding" + android:layout_marginBottom="@dimen/controls_header_bottom_margin" + android:orientation="horizontal"> + + <!-- make sure the header stays centered in the layout by adding a spacer --> + <Space + android:id="@+id/controls_spacer" + android:layout_width="@dimen/controls_header_menu_button_size" + android:layout_height="1dp" + android:visibility="gone" /> + + <ImageView + android:id="@+id/controls_close" + android:layout_width="@dimen/controls_header_menu_button_size" + android:layout_height="@dimen/controls_header_menu_button_size" + android:layout_gravity="center_vertical" + android:background="?android:attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/accessibility_desc_close" + android:padding="12dp" + android:src="@drawable/ic_close" + android:tint="@color/control_primary_text" + android:visibility="gone" + tools:visibility="visible" /> + + <!-- need to keep this outer view in order to have a correctly sized anchor + for the dropdown menu, as well as dropdown background in the right place --> + <LinearLayout + android:id="@+id/controls_header" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="1" + android:gravity="center" + android:minHeight="48dp" + android:orientation="horizontal"> + + <TextView + android:id="@+id/app_or_structure_spinner" + style="@style/Control.Spinner.Header" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:clickable="false" + tools:text="Test app" /> + </LinearLayout> + + <ImageView + android:id="@+id/controls_more" + android:layout_width="@dimen/controls_header_menu_button_size" + android:layout_height="@dimen/controls_header_menu_button_size" + android:layout_gravity="center_vertical" + android:background="?android:attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/accessibility_menu" + android:padding="12dp" + android:src="@drawable/ic_more_vert" + android:tint="@color/control_more_vert" /> </LinearLayout> - <ImageView - android:id="@+id/controls_more" - android:src="@drawable/ic_more_vert" - android:layout_width="@dimen/controls_header_menu_size" - android:layout_height="@dimen/controls_header_menu_size" - android:padding="12dp" - android:tint="@color/control_more_vert" - android:layout_gravity="center" - android:contentDescription="@string/accessibility_menu" - android:background="?android:attr/selectableItemBackgroundBorderless" /> - </LinearLayout> - <ScrollView + <ScrollView android:id="@+id/controls_scroll_view" android:layout_width="match_parent" android:layout_height="0dp" + android:layout_marginHorizontal="@dimen/controls_content_margin_horizontal" android:layout_weight="1" - android:orientation="vertical" android:clipChildren="true" + android:orientation="vertical" android:paddingHorizontal="16dp" android:scrollbars="none"> - <include layout="@layout/global_actions_controls_list_view" /> - </ScrollView> + <include layout="@layout/global_actions_controls_list_view" /> + + </ScrollView> - <FrameLayout - android:id="@+id/controls_panel" - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" - android:background="@drawable/controls_panel_background" - android:visibility="gone" - /> + <FrameLayout + android:id="@+id/controls_panel" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginHorizontal="@dimen/controls_content_margin_horizontal" + android:layout_weight="1" + android:background="@drawable/controls_panel_background" + android:visibility="gone" + tools:visibility="visible" /> </merge> diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index b1d3ed05333b..745cfc6c1655 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -64,7 +64,7 @@ android:layout_width="@dimen/qs_footer_action_button_size" android:layout_height="@dimen/qs_footer_action_button_size" android:layout_gravity="center_vertical|end" - android:background="?android:attr/selectableItemBackground" + android:background="@drawable/qs_footer_edit_circle" android:clickable="true" android:contentDescription="@string/accessibility_quick_settings_edit" android:focusable="true" diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index bc63c9f46a5b..4f38e6058723 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -64,5 +64,6 @@ <dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen> - <dimen name="controls_padding_horizontal">16dp</dimen> + <dimen name="controls_header_horizontal_padding">12dp</dimen> + <dimen name="controls_content_margin_horizontal">16dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 7cd147099e9c..59becc69506c 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -87,4 +87,7 @@ <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> <dimen name="biometric_auth_pattern_view_size">348dp</dimen> + + <dimen name="controls_header_horizontal_padding">12dp</dimen> + <dimen name="controls_content_margin_horizontal">24dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml index 9ed936050aa2..8583f0549960 100644 --- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml @@ -37,6 +37,8 @@ <dimen name="qs_media_rec_album_size">112dp</dimen> <dimen name="qs_media_rec_album_side_margin">16dp</dimen> + <dimen name="controls_panel_corner_radius">40dp</dimen> + <dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen> <!-- Roughly the same distance as media on LS to media on QS. We will translate by this value diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml index 8b41a44b9ba3..9248d585bba7 100644 --- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml @@ -33,5 +33,7 @@ side --> <dimen name="qs_tiles_page_horizontal_margin">60dp</dimen> + <dimen name="controls_panel_corner_radius">46dp</dimen> + <dimen name="notification_section_divider_height">16dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml index 8f59df655c3a..20864591ae5a 100644 --- a/packages/SystemUI/res/values-sw720dp/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp/dimens.xml @@ -19,7 +19,8 @@ <!-- gap on either side of status bar notification icons --> <dimen name="status_bar_icon_padding">1dp</dimen> - <dimen name="controls_padding_horizontal">40dp</dimen> + <dimen name="controls_header_horizontal_padding">28dp</dimen> + <dimen name="controls_content_margin_horizontal">40dp</dimen> <dimen name="large_screen_shade_header_height">56dp</dimen> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index f7310b045744..bf949a0f4368 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1153,11 +1153,13 @@ <!-- Home Controls --> <dimen name="controls_header_menu_size">48dp</dimen> + <dimen name="controls_header_menu_button_size">48dp</dimen> <dimen name="controls_header_bottom_margin">16dp</dimen> + <dimen name="controls_header_horizontal_padding">12dp</dimen> <dimen name="controls_header_app_icon_size">24dp</dimen> <dimen name="controls_top_margin">48dp</dimen> - <dimen name="controls_padding_horizontal">0dp</dimen> - <dimen name="control_header_text_size">20sp</dimen> + <dimen name="controls_content_margin_horizontal">0dp</dimen> + <dimen name="control_header_text_size">24sp</dimen> <dimen name="control_item_text_size">16sp</dimen> <dimen name="control_menu_item_text_size">16sp</dimen> <dimen name="control_menu_item_min_height">56dp</dimen> @@ -1188,6 +1190,8 @@ <item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">1.0</item> <dimen name="controls_task_view_right_margin">0dp</dimen> + <dimen name="controls_panel_corner_radius">42dp</dimen> + <!-- Home Controls activity view detail panel--> <dimen name="controls_activity_view_corner_radius">@*android:dimen/config_bottomDialogCornerRadius</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 464ce0333fd1..48f257a7f4c9 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2262,6 +2262,10 @@ panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] --> <string name="controls_panel_authorization">When you add <xliff:g id="appName" example="My app">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here.</string> + <!-- Shows in a dialog presented to the user to authorize this app removal from a Device + controls panel [CHAR LIMIT=NONE] --> + <string name="controls_panel_remove_app_authorization">Remove controls for <xliff:g example="My app" id="appName">%s</xliff:g>?</string> + <!-- a11y state description for a control that is currently favorited [CHAR LIMIT=NONE] --> <string name="accessibility_control_favorite">Favorited</string> <!-- a11y state description for a control that is currently favorited with its position [CHAR LIMIT=NONE] --> @@ -2302,6 +2306,8 @@ <string name="controls_dialog_title">Add to device controls</string> <!-- Controls dialog add to favorites [CHAR LIMIT=40] --> <string name="controls_dialog_ok">Add</string> + <!-- Controls dialog remove app from a panel [CHAR LIMIT=40] --> + <string name="controls_dialog_remove">Remove</string> <!-- Controls dialog message. Indicates app that suggested this control [CHAR LIMIT=NONE] --> <string name="controls_dialog_message">Suggested by <xliff:g id="app" example="System UI">%s</xliff:g></string> <!-- Controls tile secondary label when device is locked and user does not want access to controls from lockscreen [CHAR LIMIT=20] --> @@ -2419,6 +2425,8 @@ <string name="controls_menu_edit">Edit controls</string> <!-- Controls menu, add another app [CHAR LIMIT=30] --> <string name="controls_menu_add_another_app">Add app</string> + <!-- Controls menu, remove app [CHAR_LIMIT=30] --> + <string name="controls_menu_remove">Remove app</string> <!-- Title for the media output dialog with media related devices [CHAR LIMIT=50] --> <string name="media_output_dialog_add_output">Add outputs</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java index b92715516a75..8690b36c12d7 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java @@ -62,7 +62,7 @@ public class PreviewPositionHelper { */ public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData, int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx, - int taskbarSize, boolean isTablet, + int taskbarSize, boolean isLargeScreen, int currentRotation, boolean isRtl) { boolean isRotated = false; boolean isOrientationDifferent; @@ -95,7 +95,7 @@ public class PreviewPositionHelper { canvasScreenRatio = (float) canvasWidth / screenWidthPx; } scaledTaskbarSize = taskbarSize * canvasScreenRatio; - thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0; + thumbnailClipHint.bottom = isLargeScreen ? scaledTaskbarSize : 0; float scale = thumbnailData.scale; final float thumbnailScale; @@ -103,7 +103,7 @@ public class PreviewPositionHelper { // Landscape vs portrait change. // Note: Disable rotation in grid layout. boolean windowingModeSupportsRotation = - thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isTablet; + thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isLargeScreen; isOrientationDifferent = isOrientationChange(deltaRotate) && windowingModeSupportsRotation; if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 77a13bd91b90..751a3f8458bd 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -137,7 +137,7 @@ public class Utilities { /** @return whether or not {@param context} represents that of a large screen device or not */ @TargetApi(Build.VERSION_CODES.R) - public static boolean isTablet(Context context) { + public static boolean isLargeScreen(Context context) { final WindowManager windowManager = context.getSystemService(WindowManager.class); final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index d221e22a4fcd..a010c9a16517 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -26,6 +26,7 @@ import android.text.method.TextKeyListener; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup.MarginLayoutParams; +import android.view.WindowInsets; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; @@ -156,6 +157,15 @@ public class KeyguardPasswordViewController // TODO: Remove this workaround by ensuring such a race condition never happens. mMainExecutor.executeDelayed( this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON); + mView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { + @Override + public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + if (!mKeyguardViewController.isBouncerShowing()) { + mView.hideKeyboard(); + } + return insets; + } + }); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 6a28b5d20864..be013770519f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -73,11 +73,9 @@ import static com.android.systemui.statusbar.policy.DevicePostureController.DEVI import android.annotation.AnyThread; import android.annotation.MainThread; import android.annotation.SuppressLint; -import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.AlarmManager; -import android.app.UserSwitchObserver; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; @@ -104,7 +102,6 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.nfc.NfcAdapter; import android.os.CancellationSignal; import android.os.Handler; -import android.os.IRemoteCallback; import android.os.Looper; import android.os.Message; import android.os.PowerManager; @@ -175,6 +172,7 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.TimeZone; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.stream.Collectors; @@ -2203,7 +2201,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab handleDevicePolicyManagerStateChanged(msg.arg1); break; case MSG_USER_SWITCHING: - handleUserSwitching(msg.arg1, (IRemoteCallback) msg.obj); + handleUserSwitching(msg.arg1, (CountDownLatch) msg.obj); break; case MSG_USER_SWITCH_COMPLETE: handleUserSwitchComplete(msg.arg1); @@ -2328,11 +2326,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler, UserHandle.ALL); mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener); - try { - ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); - } + mUserTracker.addCallback(mUserChangedCallback, mainExecutor); mTrustManager.registerTrustListener(this); @@ -2468,17 +2462,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mIsFaceEnrolled; } - private final UserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() { + private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { @Override - public void onUserSwitching(int newUserId, IRemoteCallback reply) { + public void onUserChanging(int newUser, Context userContext, CountDownLatch latch) { mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHING, - newUserId, 0, reply)); + newUser, 0, latch)); } @Override - public void onUserSwitchComplete(int newUserId) { + public void onUserChanged(int newUser, Context userContext) { mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCH_COMPLETE, - newUserId, 0)); + newUser, 0)); } }; @@ -3191,7 +3185,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * Handle {@link #MSG_USER_SWITCHING} */ @VisibleForTesting - void handleUserSwitching(int userId, IRemoteCallback reply) { + void handleUserSwitching(int userId, CountDownLatch latch) { Assert.isMainThread(); clearBiometricRecognized(); mUserTrustIsUsuallyManaged.put(userId, mTrustManager.isTrustUsuallyManaged(userId)); @@ -3201,11 +3195,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onUserSwitching(userId); } } - try { - reply.sendResult(null); - } catch (RemoteException e) { - mLogger.logException(e, "Ignored exception while userSwitching"); - } + latch.countDown(); } /** @@ -3975,13 +3965,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver); } - try { - ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); - } catch (RemoteException e) { - mLogger.logException( - e, - "RemoteException onDestroy. cannot unregister userSwitchObserver"); - } + mUserTracker.removeCallback(mUserChangedCallback); TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java index b30a0e010e4b..ad669099284f 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -51,7 +51,6 @@ class NumPadAnimator { private float mStartRadius; private float mEndRadius; private int mHeight; - private boolean mInitialized; private static final int EXPAND_ANIMATION_MS = 100; private static final int EXPAND_COLOR_ANIMATION_MS = 50; @@ -93,15 +92,15 @@ class NumPadAnimator { } void onLayout(int height) { + boolean shouldUpdateHeight = height != mHeight; mHeight = height; mStartRadius = height / 2f; mEndRadius = height / 4f; mExpandAnimator.setFloatValues(mStartRadius, mEndRadius); mContractAnimator.setFloatValues(mEndRadius, mStartRadius); // Set initial corner radius. - if (!mInitialized) { + if (shouldUpdateHeight) { mBackground.setCornerRadius(mStartRadius); - mInitialized = true; } } diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index cef415c8a490..f6217f16d054 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -129,8 +129,8 @@ final class WirelessChargingLayout extends FrameLayout { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator); - // For tablet docking animation, we don't play the background scrim. - if (!Utilities.isTablet(context)) { + // For large screens docking animation, we don't play the background scrim. + if (!Utilities.isLargeScreen(context)) { ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this, "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR); scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION); diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index 822190f21da1..3555d0a7e7fb 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -166,6 +166,19 @@ interface ControlsController : UserAwareController { ) /** + * Removes favorites for a given component + * @param componentName the name of the service that provides the [Control] + * @return true when favorites is scheduled for deletion + */ + fun removeFavorites(componentName: ComponentName): Boolean + + /** + * Checks if the favorites can be removed. You can't remove components from the preferred list. + * @param componentName the name of the service that provides the [Control] + */ + fun canRemoveFavorites(componentName: ComponentName): Boolean + + /** * Replaces the favorites for the given structure. * * Calling this method will eliminate the previous selection of favorites and replace it with a diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 278ee7079720..854790360f6a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -497,6 +497,21 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun canRemoveFavorites(componentName: ComponentName): Boolean = + !authorizedPanelsRepository.getPreferredPackages().contains(componentName.packageName) + + override fun removeFavorites(componentName: ComponentName): Boolean { + if (!confirmAvailability()) return false + if (!canRemoveFavorites(componentName)) return false + + executor.execute { + Favorites.removeStructures(componentName) + authorizedPanelsRepository.removeAuthorizedPanels(setOf(componentName.packageName)) + persistenceWrapper.storeFavorites(Favorites.getAllStructures()) + } + return true + } + override fun replaceFavoritesForStructure(structureInfo: StructureInfo) { if (!confirmAvailability()) return executor.execute { @@ -655,10 +670,11 @@ private object Favorites { return true } - fun removeStructures(componentName: ComponentName) { + fun removeStructures(componentName: ComponentName): Boolean { val newFavMap = favMap.toMutableMap() - newFavMap.remove(componentName) + val removed = newFavMap.remove(componentName) != null favMap = newFavMap + return removed } fun addFavorite( diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt index 3e672f391e81..ae9c37aa0e7b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt @@ -26,6 +26,14 @@ interface AuthorizedPanelsRepository { /** A set of package names that the user has previously authorized to show panels. */ fun getAuthorizedPanels(): Set<String> + /** Preferred applications to query controls suggestions from */ + fun getPreferredPackages(): Set<String> + /** Adds [packageNames] to the set of packages that the user has authorized to show panels. */ fun addAuthorizedPanels(packageNames: Set<String>) + + /** + * Removes [packageNames] from the set of packages that the user has authorized to show panels. + */ + fun removeAuthorizedPanels(packageNames: Set<String>) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt index f7e43a77b573..e51e8326c0a5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt @@ -37,10 +37,20 @@ constructor( return getAuthorizedPanelsInternal(instantiateSharedPrefs()) } + override fun getPreferredPackages(): Set<String> = + context.resources.getStringArray(R.array.config_controlsPreferredPackages).toSet() + override fun addAuthorizedPanels(packageNames: Set<String>) { addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames) } + override fun removeAuthorizedPanels(packageNames: Set<String>) { + with(instantiateSharedPrefs()) { + val currentSet = getAuthorizedPanelsInternal(this) + edit().putStringSet(KEY, currentSet - packageNames).apply() + } + } + private fun getAuthorizedPanelsInternal(sharedPreferences: SharedPreferences): Set<String> { return sharedPreferences.getStringSet(KEY, emptySet())!! } @@ -63,15 +73,7 @@ constructor( // If we've never run this (i.e., the key doesn't exist), add the default packages if (sharedPref.getStringSet(KEY, null) == null) { - sharedPref - .edit() - .putStringSet( - KEY, - context.resources - .getStringArray(R.array.config_controlsPreferredPackages) - .toSet() - ) - .apply() + sharedPref.edit().putStringSet(KEY, getPreferredPackages()).apply() } return sharedPref } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt index 3a3f9b4e5265..bf0a69296dfd 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -68,7 +68,7 @@ class ControlsActivity @Inject constructor( getLifecycle().addObserver( ControlsAnimations.observerForAnimations( - requireViewById<ViewGroup>(R.id.control_detail_root), + requireViewById(R.id.control_detail_root), window, intent, !featureFlags.isEnabled(Flags.USE_APP_PANELS) @@ -95,7 +95,7 @@ class ControlsActivity @Inject constructor( override fun onStart() { super.onStart() - parent = requireViewById<ViewGroup>(R.id.global_actions_controls) + parent = requireViewById(R.id.control_detail_root) parent.alpha = 0f if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && !keyguardStateController.isUnlocked) { controlsSettingsDialogManager.maybeShowDialog(this) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt new file mode 100644 index 000000000000..d6cfb79101d7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.controls.ui + +import android.app.Dialog +import android.content.Context +import android.content.DialogInterface +import com.android.systemui.R +import com.android.systemui.statusbar.phone.SystemUIDialog +import java.util.function.Consumer +import javax.inject.Inject + +class ControlsDialogsFactory(private val internalDialogFactory: (Context) -> SystemUIDialog) { + + @Inject constructor() : this({ SystemUIDialog(it) }) + + fun createRemoveAppDialog( + context: Context, + appName: CharSequence, + response: Consumer<Boolean> + ): Dialog { + val listener = + DialogInterface.OnClickListener { _, which -> + response.accept(which == DialogInterface.BUTTON_POSITIVE) + } + return internalDialogFactory(context).apply { + setTitle(context.getString(R.string.controls_panel_remove_app_authorization, appName)) + setCanceledOnTouchOutside(true) + setOnCancelListener { response.accept(false) } + setPositiveButton(R.string.controls_dialog_remove, listener) + setNeutralButton(R.string.cancel, listener) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 9405c602caf7..c61dad6fc075 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter import android.animation.ObjectAnimator import android.app.Activity import android.app.ActivityOptions +import android.app.Dialog import android.app.PendingIntent import android.content.ComponentName import android.content.Context @@ -52,7 +53,6 @@ import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.ControlsServiceInfo -import com.android.systemui.controls.settings.ControlsSettingsRepository import com.android.systemui.controls.CustomIconCache import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.StructureInfo @@ -64,6 +64,7 @@ import com.android.systemui.controls.management.ControlsFavoritingActivity import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.management.ControlsProviderSelectorActivity import com.android.systemui.controls.panels.AuthorizedPanelsRepository +import com.android.systemui.controls.settings.ControlsSettingsRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -83,7 +84,7 @@ import com.android.wm.shell.TaskViewFactory import dagger.Lazy import java.io.PrintWriter import java.text.Collator -import java.util.Optional +import java.util.* import java.util.function.Consumer import javax.inject.Inject @@ -108,6 +109,7 @@ class ControlsUiControllerImpl @Inject constructor ( private val controlsSettingsRepository: ControlsSettingsRepository, private val authorizedPanelsRepository: AuthorizedPanelsRepository, private val featureFlags: FeatureFlags, + private val dialogsFactory: ControlsDialogsFactory, dumpManager: DumpManager ) : ControlsUiController, Dumpable { @@ -122,6 +124,7 @@ class ControlsUiControllerImpl @Inject constructor ( private const val ADD_CONTROLS_ID = 1L private const val ADD_APP_ID = 2L private const val EDIT_CONTROLS_ID = 3L + private const val REMOVE_APP_ID = 4L } private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION @@ -151,6 +154,7 @@ class ControlsUiControllerImpl @Inject constructor ( private var openAppIntent: Intent? = null private var overflowMenuAdapter: BaseAdapter? = null + private var removeAppDialog: Dialog? = null private val onSeedingComplete = Consumer<Boolean> { accepted -> @@ -330,6 +334,31 @@ class ControlsUiControllerImpl @Inject constructor ( } } + @VisibleForTesting + internal fun startRemovingApp(componentName: ComponentName, appName: CharSequence) { + removeAppDialog?.cancel() + removeAppDialog = dialogsFactory.createRemoveAppDialog(context, appName) { + if (!controlsController.get().removeFavorites(componentName)) { + return@createRemoveAppDialog + } + if ( + sharedPreferences.getString(PREF_COMPONENT, "") == + componentName.flattenToString() + ) { + sharedPreferences + .edit() + .remove(PREF_COMPONENT) + .remove(PREF_STRUCTURE_OR_APP_NAME) + .remove(PREF_IS_PANEL) + .commit() + } + + allStructures = controlsController.get().getFavorites() + selectedItem = getPreferredSelectedItem(allStructures) + reload(parent) + }.apply { show() } + } + private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) { val i = Intent(activityContext, klazz) putIntentExtras(i, si) @@ -433,7 +462,10 @@ class ControlsUiControllerImpl @Inject constructor ( val currentApps = panelsAndStructures.map { it.componentName }.toSet() val allApps = controlsListingController.get() .getCurrentServices().map { it.componentName }.toSet() - createMenu(extraApps = (allApps - currentApps).isNotEmpty()) + createMenu( + selectionItem = selectionItem, + extraApps = (allApps - currentApps).isNotEmpty(), + ) } private fun createPanelView(componentName: ComponentName) { @@ -472,7 +504,7 @@ class ControlsUiControllerImpl @Inject constructor ( } } - private fun createMenu(extraApps: Boolean) { + private fun createMenu(selectionItem: SelectionItem, extraApps: Boolean) { val isPanel = selectedItem is SelectedItem.PanelItem val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure ?: EMPTY_STRUCTURE @@ -490,6 +522,13 @@ class ControlsUiControllerImpl @Inject constructor ( ADD_APP_ID )) } + if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED) && + controlsController.get().canRemoveFavorites(selectedItem.componentName)) { + add(OverflowMenuAdapter.MenuItem( + context.getText(R.string.controls_menu_remove), + REMOVE_APP_ID, + )) + } } else { add(OverflowMenuAdapter.MenuItem( context.getText(R.string.controls_menu_add), @@ -529,6 +568,9 @@ class ControlsUiControllerImpl @Inject constructor ( ADD_APP_ID -> startProviderSelectorActivity() ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure) EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure) + REMOVE_APP_ID -> startRemovingApp( + selectedStructure.componentName, selectionItem.appName + ) } dismiss() } @@ -546,8 +588,12 @@ class ControlsUiControllerImpl @Inject constructor ( RenderInfo.registerComponentIcon(it.componentName, it.icon) } - var adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply { - addAll(items) + val adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply { + add(selected) + addAll(items + .filter { it !== selected } + .sortedBy { it.appName.toString() } + ) } val iconSize = context.resources @@ -728,6 +774,7 @@ class ControlsUiControllerImpl @Inject constructor ( it.value.dismiss() } controlActionCoordinator.closeDialogs() + removeAppDialog?.cancel() } override fun hide(parent: ViewGroup) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt index 3b6ab20e39d4..78e87cafc4f2 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt @@ -71,7 +71,7 @@ class PanelTaskViewController( taskView.post { val roundedCorner = activityContext.resources.getDimensionPixelSize( - R.dimen.notification_corner_radius + R.dimen.controls_panel_corner_radius ) val radii = FloatArray(8) { roundedCorner.toFloat() } taskView.background = diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java index e39073bb6711..ff1f31245570 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java @@ -28,6 +28,8 @@ import android.widget.FrameLayout; import com.android.systemui.CoreStartable; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.smartspace.DreamSmartspaceController; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.BcSmartspaceDataPlugin; import com.android.systemui.shared.condition.Monitor; import com.android.systemui.util.condition.ConditionalCoreStartable; @@ -68,6 +70,7 @@ public class SmartSpaceComplication implements Complication { private final DreamSmartspaceController mSmartSpaceController; private final DreamOverlayStateController mDreamOverlayStateController; private final SmartSpaceComplication mComplication; + private final FeatureFlags mFeatureFlags; private final BcSmartspaceDataPlugin.SmartspaceTargetListener mSmartspaceListener = new BcSmartspaceDataPlugin.SmartspaceTargetListener() { @@ -85,15 +88,21 @@ public class SmartSpaceComplication implements Complication { DreamOverlayStateController dreamOverlayStateController, SmartSpaceComplication smartSpaceComplication, DreamSmartspaceController smartSpaceController, - @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) { + @Named(DREAM_PRETEXT_MONITOR) Monitor monitor, + FeatureFlags featureFlags) { super(monitor); mDreamOverlayStateController = dreamOverlayStateController; mComplication = smartSpaceComplication; mSmartSpaceController = smartSpaceController; + mFeatureFlags = featureFlags; } @Override public void onStart() { + if (mFeatureFlags.isEnabled(Flags.HIDE_SMARTSPACE_ON_DREAM_OVERLAY)) { + return; + } + mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() { @Override public void onStateChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index b2609a7da069..4fb763ff6038 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -231,6 +231,10 @@ object Flags { val SMARTSPACE_DATE_WEATHER_DECOUPLED = sysPropBooleanFlag(403, "persist.sysui.ss.dw_decoupled", default = false) + // TODO(b/270223352): Tracking Bug + @JvmField + val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = unreleasedFlag(404, "hide_smartspace_on_dream_overlay") + // 500 - quick settings val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile") @@ -373,6 +377,9 @@ object Flags { // TODO(b/267166152) : Tracking Bug val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag(916, "media_retain_recommendations") + // TODO(b/270437894): Tracking Bug + val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume") + // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index f964cb39a8d4..8ae171f9264f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -950,9 +950,9 @@ class KeyguardUnlockAnimationController @Inject constructor( return false } - // We don't do the shared element on tablets because they're large and the smartspace has to - // fly across large distances, which is distracting. - if (Utilities.isTablet(context)) { + // We don't do the shared element on large screens because the smartspace has to fly across + // large distances, which is distracting. + if (Utilities.isLargeScreen(context)) { return false } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index baadc66170cc..84abf57cacf2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -24,8 +24,10 @@ import android.hardware.biometrics.BiometricManager import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback import android.os.Looper import android.os.UserHandle +import android.util.Log import com.android.internal.widget.LockPatternUtils import com.android.systemui.Dumpable +import com.android.systemui.R import com.android.systemui.biometrics.AuthController import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging @@ -35,6 +37,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.shared.model.DevicePosture import com.android.systemui.user.data.repository.UserRepository import java.io.PrintWriter import javax.inject.Inject @@ -47,8 +50,10 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest @@ -82,6 +87,12 @@ interface BiometricSettingsRepository { /** Whether fingerprint feature is enabled for the current user by the DevicePolicy */ val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> + + /** + * Whether face authentication is supported for the current device posture. Face auth can be + * restricted to specific postures using [R.integer.config_face_auth_supported_posture] + */ + val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> } @SysUISingleton @@ -98,11 +109,27 @@ constructor( @Background backgroundDispatcher: CoroutineDispatcher, biometricManager: BiometricManager?, @Main looper: Looper, + devicePostureRepository: DevicePostureRepository, dumpManager: DumpManager, ) : BiometricSettingsRepository, Dumpable { + override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> + init { dumpManager.registerDumpable(this) + val configFaceAuthSupportedPosture = + DevicePosture.toPosture( + context.resources.getInteger(R.integer.config_face_auth_supported_posture) + ) + isFaceAuthSupportedInCurrentPosture = + if (configFaceAuthSupportedPosture == DevicePosture.UNKNOWN) { + flowOf(true) + } else { + devicePostureRepository.currentDevicePosture.map { + it == configFaceAuthSupportedPosture + } + } + .onEach { Log.d(TAG, "isFaceAuthSupportedInCurrentPosture value changed to: $it") } } override fun dump(pw: PrintWriter, args: Array<String?>) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt new file mode 100644 index 000000000000..adb1e01d0d00 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.DevicePosture +import com.android.systemui.statusbar.policy.DevicePostureController +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +/** Provide current device posture state. */ +interface DevicePostureRepository { + /** Provides the current device posture. */ + val currentDevicePosture: Flow<DevicePosture> +} + +@SysUISingleton +class DevicePostureRepositoryImpl +@Inject +constructor(private val postureController: DevicePostureController) : DevicePostureRepository { + override val currentDevicePosture: Flow<DevicePosture> + get() = conflatedCallbackFlow { + val sendPostureUpdate = { posture: Int -> + val currentDevicePosture = DevicePosture.toPosture(posture) + trySendWithFailureLogging( + currentDevicePosture, + TAG, + "Error sending posture update to $currentDevicePosture" + ) + } + val callback = DevicePostureController.Callback { sendPostureUpdate(it) } + postureController.addCallback(callback) + sendPostureUpdate(postureController.devicePosture) + + awaitClose { postureController.removeCallback(callback) } + } + + companion object { + const val TAG = "PostureRepositoryImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index 4a262f580749..f27f89995dbd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -31,6 +31,8 @@ interface KeyguardRepositoryModule { @Binds fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository + @Binds fun devicePostureRepository(impl: DevicePostureRepositoryImpl): DevicePostureRepository + @Binds fun biometricSettingsRepository( impl: BiometricSettingsRepositoryImpl diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DevicePosture.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DevicePosture.kt new file mode 100644 index 000000000000..fff7cfe1d6a3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DevicePosture.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared.model + +import com.android.systemui.statusbar.policy.DevicePostureController + +/** Represents the possible posture states of the device. */ +enum class DevicePosture { + UNKNOWN, + CLOSED, + HALF_OPENED, + OPENED, + FLIPPED; + + companion object { + fun toPosture(@DevicePostureController.DevicePostureInt posture: Int): DevicePosture { + return when (posture) { + DevicePostureController.DEVICE_POSTURE_CLOSED -> CLOSED + DevicePostureController.DEVICE_POSTURE_HALF_OPENED -> HALF_OPENED + DevicePostureController.DEVICE_POSTURE_OPENED -> OPENED + DevicePostureController.DEVICE_POSTURE_FLIPPED -> FLIPPED + DevicePostureController.DEVICE_POSTURE_UNKNOWN -> UNKNOWN + else -> UNKNOWN + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt index 1e3b60c27d84..e7184f74ca59 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt @@ -187,6 +187,7 @@ constructor( previewMode.isInPreviewMode && previewMode.shouldHighlightSelectedAffordance && !isSelected, + forceInactive = previewMode.isInPreviewMode ) } .distinctUntilChanged() @@ -198,6 +199,7 @@ constructor( isClickable: Boolean, isSelected: Boolean, isDimmed: Boolean, + forceInactive: Boolean, ): KeyguardQuickAffordanceViewModel { return when (this) { is KeyguardQuickAffordanceModel.Visible -> @@ -213,7 +215,7 @@ constructor( ) }, isClickable = isClickable, - isActivated = activationState is ActivationState.Active, + isActivated = !forceInactive && activationState is ActivationState.Active, isSelected = isSelected, useLongPress = quickAffordanceInteractor.useLongPress, isDimmed = isDimmed, diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 817de7976352..642c9f73f625 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -62,6 +62,15 @@ public class LogModule { return factory.create("NotifLog", maxSize, false /* systrace */); } + /** Provides a logging buffer for all logs related to notifications on the lockscreen. */ + @Provides + @SysUISingleton + @NotificationLockscreenLog + public static LogBuffer provideNotificationLockScreenLogBuffer( + LogBufferFactory factory) { + return factory.create("NotifLockscreenLog", 50, false /* systrace */); + } + /** Provides a logging buffer for logs related to heads up presentation of notifications. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java new file mode 100644 index 000000000000..a2d381ec90f0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.plugins.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for notification & lockscreen related messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface NotificationLockscreenLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index eb6893f0f141..e70a2f3ed446 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -1339,10 +1339,13 @@ class MediaDataManager( fun onNotificationRemoved(key: String) { Assert.isMainThread() val removed = mediaEntries.remove(key) ?: return - + val isEligibleForResume = + removed.isLocalSession() || + (mediaFlags.isRemoteResumeAllowed() && + removed.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE) if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) { logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) - } else if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) { + } else if (useMediaResumption && removed.resumeAction != null && isEligibleForResume) { convertToResumePlayer(key, removed) } else if (mediaFlags.isRetainingPlayersEnabled()) { handlePossibleRemoval(key, removed, notificationRemoved = true) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt index 2d10b823f784..2af21c4dbd9f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt @@ -37,6 +37,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT +import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.settings.UserTracker import com.android.systemui.tuner.TunerService import com.android.systemui.util.Utils @@ -63,7 +64,8 @@ constructor( private val tunerService: TunerService, private val mediaBrowserFactory: ResumeMediaBrowserFactory, dumpManager: DumpManager, - private val systemClock: SystemClock + private val systemClock: SystemClock, + private val mediaFlags: MediaFlags, ) : MediaDataManager.Listener, Dumpable { private var useMediaResumption: Boolean = Utils.useMediaResumption(context) @@ -231,7 +233,11 @@ constructor( mediaBrowser = null } // If we don't have a resume action, check if we haven't already - if (data.resumeAction == null && !data.hasCheckedForResume && data.isLocalSession()) { + val isEligibleForResume = + data.isLocalSession() || + (mediaFlags.isRemoteResumeAllowed() && + data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE) + if (data.resumeAction == null && !data.hasCheckedForResume && isEligibleForResume) { // TODO also check for a media button receiver intended for restarting (b/154127084) Log.d(TAG, "Checking for service component for " + data.packageName) val pm = context.packageManager diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index c3fa76ec9433..9bc66f6c98d0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -61,4 +61,7 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { /** If true, do not automatically dismiss the recommendation card */ fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS) + + /** Check whether we allow remote media to generate resume controls */ + fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME) } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt index d4991f90a86b..9b9d561b5180 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt @@ -33,7 +33,7 @@ import com.android.systemui.R import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.shared.recents.model.ThumbnailData import com.android.systemui.shared.recents.utilities.PreviewPositionHelper -import com.android.systemui.shared.recents.utilities.Utilities.isTablet +import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen /** * Custom view that shows a thumbnail preview of one recent task based on [ThumbnailData]. @@ -150,9 +150,9 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 val displayWidthPx = windowMetrics.bounds.width() val displayHeightPx = windowMetrics.bounds.height() val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL - val isTablet = isTablet(context) + val isLargeScreen = isLargeScreen(context) val taskbarSize = - if (isTablet) { + if (isLargeScreen) { resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height) } else { 0 @@ -166,7 +166,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 displayWidthPx, displayHeightPx, taskbarSize, - isTablet, + isLargeScreen, currentRotation, isRtl ) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt index 88d5eaaff216..1c901540ed39 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt @@ -23,7 +23,7 @@ import android.view.WindowManager import com.android.internal.R as AndroidR import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener -import com.android.systemui.shared.recents.utilities.Utilities.isTablet +import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener @@ -61,8 +61,8 @@ constructor( val width = windowMetrics.bounds.width() var height = maximumWindowHeight - val isTablet = isTablet(context) - if (isTablet) { + val isLargeScreen = isLargeScreen(context) + if (isLargeScreen) { val taskbarSize = context.resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height) height -= taskbarSize diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 97c290d3e1f0..f81743928e3b 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -37,7 +37,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSE import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS; import static com.android.systemui.navigationbar.NavBarHelper.transitionMode; import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; -import static com.android.systemui.shared.recents.utilities.Utilities.isTablet; +import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY; @@ -1724,7 +1724,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private void setNavigationIconHints(int hints) { if (hints == mNavigationIconHints) return; - if (!isTablet(mContext)) { + if (!isLargeScreen(mContext)) { // All IME functions handled by launcher via Sysui flags for large screen final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; final boolean oldBackAlt = diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 8c19111cab24..153d5a651f9c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -21,7 +21,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG; -import static com.android.systemui.shared.recents.utilities.Utilities.isTablet; +import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; import android.content.Context; import android.content.pm.ActivityInfo; @@ -91,7 +91,7 @@ public class NavigationBarController implements private final DisplayManager mDisplayManager; private final TaskbarDelegate mTaskbarDelegate; private int mNavMode; - @VisibleForTesting boolean mIsTablet; + @VisibleForTesting boolean mIsLargeScreen; /** A displayId - nav bar maps. */ @VisibleForTesting @@ -138,16 +138,16 @@ public class NavigationBarController implements navBarHelper, navigationModeController, sysUiFlagsContainer, dumpManager, autoHideController, lightBarController, pipOptional, backAnimation.orElse(null), taskStackChangeListeners); - mIsTablet = isTablet(mContext); + mIsLargeScreen = isLargeScreen(mContext); dumpManager.registerDumpable(this); } @Override public void onConfigChanged(Configuration newConfig) { - boolean isOldConfigTablet = mIsTablet; - mIsTablet = isTablet(mContext); + boolean isOldConfigLargeScreen = mIsLargeScreen; + mIsLargeScreen = isLargeScreen(mContext); boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources()); - boolean largeScreenChanged = mIsTablet != isOldConfigTablet; + boolean largeScreenChanged = mIsLargeScreen != isOldConfigLargeScreen; // TODO(b/243765256): Disable this logging once b/243765256 is fixed. Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized() @@ -235,8 +235,9 @@ public class NavigationBarController implements /** @return {@code true} if taskbar is enabled, false otherwise */ private boolean initializeTaskbarIfNecessary() { - // Enable for tablet or (phone AND flag is set); assuming phone = !mIsTablet - boolean taskbarEnabled = mIsTablet || mFeatureFlags.isEnabled(Flags.HIDE_NAVBAR_WINDOW); + // Enable for large screens or (phone AND flag is set); assuming phone = !mIsLargeScreen + boolean taskbarEnabled = mIsLargeScreen || mFeatureFlags.isEnabled( + Flags.HIDE_NAVBAR_WINDOW); if (taskbarEnabled) { Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary"); @@ -258,7 +259,7 @@ public class NavigationBarController implements @Override public void onDisplayReady(int displayId) { Display display = mDisplayManager.getDisplay(displayId); - mIsTablet = isTablet(mContext); + mIsLargeScreen = isLargeScreen(mContext); createNavigationBar(display, null /* savedState */, null /* result */); } @@ -470,7 +471,7 @@ public class NavigationBarController implements @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("mIsTablet=" + mIsTablet); + pw.println("mIsLargeScreen=" + mIsLargeScreen); pw.println("mNavMode=" + mNavMode); for (int i = 0; i < mNavigationBars.size(); i++) { if (i > 0) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 645b1256e5f1..346acf958e51 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -16,7 +16,7 @@ package com.android.systemui.recents; -import static com.android.systemui.shared.recents.utilities.Utilities.isTablet; +import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; @@ -265,7 +265,7 @@ public class ScreenPinningRequest implements View.OnClickListener, .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); View buttons = mLayout.findViewById(R.id.screen_pinning_buttons); if (!QuickStepContract.isGesturalMode(mNavBarMode) - && hasSoftNavigationBar(mContext.getDisplayId()) && !isTablet(mContext)) { + && hasSoftNavigationBar(mContext.getDisplayId()) && !isLargeScreen(mContext)) { buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); swapChildrenIfRtlAndVertical(buttons); } else { diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index 287e8101f86d..33a3125d1c68 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -19,6 +19,7 @@ package com.android.systemui.settings import android.content.Context import android.content.pm.UserInfo import android.os.UserHandle +import java.util.concurrent.CountDownLatch import java.util.concurrent.Executor /** @@ -67,14 +68,25 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { interface Callback { /** + * Same as {@link onUserChanging(Int, Context, CountDownLatch)} but the latch will be + * auto-decremented after the completion of this method. + */ + @JvmDefault + fun onUserChanging(newUser: Int, userContext: Context) {} + + /** * Notifies that the current user is being changed. * Override this method to run things while the screen is frozen for the user switch. * Please use {@link #onUserChanged} if the task doesn't need to push the unfreezing of the * screen further. Please be aware that code executed in this callback will lengthen the - * user switch duration. + * user switch duration. When overriding this method, countDown() MUST be called on the + * latch once execution is complete. */ @JvmDefault - fun onUserChanging(newUser: Int, userContext: Context) {} + fun onUserChanging(newUser: Int, userContext: Context, latch: CountDownLatch) { + onUserChanging(newUser, userContext) + latch.countDown() + } /** * Notifies that the current user has changed. diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 9f551c6ebd34..867403689292 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -183,9 +183,22 @@ class UserTrackerImpl internal constructor( Log.i(TAG, "Switching to user $newUserId") setUserIdInternal(newUserId) - notifySubscribers { - onUserChanging(newUserId, userContext) - }.await() + + val list = synchronized(callbacks) { + callbacks.toList() + } + val latch = CountDownLatch(list.size) + list.forEach { + val callback = it.callback.get() + if (callback != null) { + it.executor.execute { + callback.onUserChanging(userId, userContext, latch) + } + } else { + latch.countDown() + } + } + latch.await() } @WorkerThread @@ -225,25 +238,18 @@ class UserTrackerImpl internal constructor( } } - private inline fun notifySubscribers( - crossinline action: UserTracker.Callback.() -> Unit - ): CountDownLatch { + private inline fun notifySubscribers(crossinline action: UserTracker.Callback.() -> Unit) { val list = synchronized(callbacks) { callbacks.toList() } - val latch = CountDownLatch(list.size) list.forEach { if (it.callback.get() != null) { it.executor.execute { it.callback.get()?.action() - latch.countDown() } - } else { - latch.countDown() } } - return latch } override fun dump(pw: PrintWriter, args: Array<out String>) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java index 0a5e9867a17f..11582d7e3cc8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java @@ -29,7 +29,6 @@ import android.app.AppGlobals; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.SynchronousUserSwitchObserver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -52,7 +51,9 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.CoreStartable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.NotificationChannels; @@ -73,6 +74,8 @@ public class InstantAppNotifier private final Context mContext; private final Handler mHandler = new Handler(); + private final UserTracker mUserTracker; + private final Executor mMainExecutor; private final Executor mUiBgExecutor; private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>(); private final CommandQueue mCommandQueue; @@ -82,10 +85,14 @@ public class InstantAppNotifier public InstantAppNotifier( Context context, CommandQueue commandQueue, + UserTracker userTracker, + @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor, KeyguardStateController keyguardStateController) { mContext = context; mCommandQueue = commandQueue; + mUserTracker = userTracker; + mMainExecutor = mainExecutor; mUiBgExecutor = uiBgExecutor; mKeyguardStateController = keyguardStateController; } @@ -93,11 +100,7 @@ public class InstantAppNotifier @Override public void start() { // listen for user / profile change. - try { - ActivityManager.getService().registerUserSwitchObserver(mUserSwitchListener, TAG); - } catch (RemoteException e) { - // Ignore - } + mUserTracker.addCallback(mUserSwitchListener, mMainExecutor); mCommandQueue.addCallback(this); mKeyguardStateController.addCallback(this); @@ -129,13 +132,10 @@ public class InstantAppNotifier updateForegroundInstantApps(); } - private final SynchronousUserSwitchObserver mUserSwitchListener = - new SynchronousUserSwitchObserver() { - @Override - public void onUserSwitching(int newUserId) throws RemoteException {} - + private final UserTracker.Callback mUserSwitchListener = + new UserTracker.Callback() { @Override - public void onUserSwitchComplete(int newUserId) throws RemoteException { + public void onUserChanged(int newUser, Context userContext) { mHandler.post( () -> { updateForegroundInstantApps(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt index 44645315ca80..88d9ffcdcf3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt @@ -13,7 +13,7 @@ package com.android.systemui.statusbar.notification -import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.log.dagger.NotificationLockscreenLog import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.statusbar.StatusBarState @@ -21,7 +21,12 @@ import javax.inject.Inject class NotificationWakeUpCoordinatorLogger @Inject -constructor(@NotificationLog private val buffer: LogBuffer) { +constructor(@NotificationLockscreenLog private val buffer: LogBuffer) { + private var lastSetDozeAmountLogWasFractional = false + private var lastSetDozeAmountLogState = -1 + private var lastSetDozeAmountLogSource = "undefined" + private var lastOnDozeAmountChangedLogWasFractional = false + fun logSetDozeAmount( linear: Float, eased: Float, @@ -29,6 +34,20 @@ constructor(@NotificationLog private val buffer: LogBuffer) { state: Int, changed: Boolean, ) { + // Avoid logging on every frame of the animation if important values are not changing + val isFractional = linear != 1f && linear != 0f + if ( + lastSetDozeAmountLogWasFractional && + isFractional && + lastSetDozeAmountLogState == state && + lastSetDozeAmountLogSource == source + ) { + return + } + lastSetDozeAmountLogWasFractional = isFractional + lastSetDozeAmountLogState = state + lastSetDozeAmountLogSource = source + buffer.log( TAG, DEBUG, @@ -66,6 +85,10 @@ constructor(@NotificationLog private val buffer: LogBuffer) { } fun logOnDozeAmountChanged(linear: Float, eased: Float) { + // Avoid logging on every frame of the animation when values are fractional + val isFractional = linear != 1f && linear != 0f + if (lastOnDozeAmountChangedLogWasFractional && isFractional) return + lastOnDozeAmountChangedLogWasFractional = isFractional buffer.log( TAG, DEBUG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 6c532a5c5fab..e6b76ad0e00c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -22,8 +22,6 @@ import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.AlarmManager; import android.app.AlarmManager.AlarmClockInfo; -import android.app.IActivityManager; -import android.app.SynchronousUserSwitchObserver; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -134,7 +132,6 @@ public class PhoneStatusBarPolicy private final NextAlarmController mNextAlarmController; private final AlarmManager mAlarmManager; private final UserInfoController mUserInfoController; - private final IActivityManager mIActivityManager; private final UserManager mUserManager; private final UserTracker mUserTracker; private final DevicePolicyManager mDevicePolicyManager; @@ -149,6 +146,7 @@ public class PhoneStatusBarPolicy private final KeyguardStateController mKeyguardStateController; private final LocationController mLocationController; private final PrivacyItemController mPrivacyItemController; + private final Executor mMainExecutor; private final Executor mUiBgExecutor; private final SensorPrivacyController mSensorPrivacyController; private final RecordingController mRecordingController; @@ -168,16 +166,17 @@ public class PhoneStatusBarPolicy @Inject public PhoneStatusBarPolicy(StatusBarIconController iconController, CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher, - @UiBackground Executor uiBgExecutor, @Main Looper looper, @Main Resources resources, - CastController castController, HotspotController hotspotController, - BluetoothController bluetoothController, NextAlarmController nextAlarmController, - UserInfoController userInfoController, RotationLockController rotationLockController, - DataSaverController dataSaverController, ZenModeController zenModeController, + @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor, @Main Looper looper, + @Main Resources resources, CastController castController, + HotspotController hotspotController, BluetoothController bluetoothController, + NextAlarmController nextAlarmController, UserInfoController userInfoController, + RotationLockController rotationLockController, DataSaverController dataSaverController, + ZenModeController zenModeController, DeviceProvisionedController deviceProvisionedController, KeyguardStateController keyguardStateController, LocationController locationController, - SensorPrivacyController sensorPrivacyController, IActivityManager iActivityManager, - AlarmManager alarmManager, UserManager userManager, UserTracker userTracker, + SensorPrivacyController sensorPrivacyController, AlarmManager alarmManager, + UserManager userManager, UserTracker userTracker, DevicePolicyManager devicePolicyManager, RecordingController recordingController, @Nullable TelecomManager telecomManager, @DisplayId int displayId, @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil, @@ -195,7 +194,6 @@ public class PhoneStatusBarPolicy mNextAlarmController = nextAlarmController; mAlarmManager = alarmManager; mUserInfoController = userInfoController; - mIActivityManager = iActivityManager; mUserManager = userManager; mUserTracker = userTracker; mDevicePolicyManager = devicePolicyManager; @@ -208,6 +206,7 @@ public class PhoneStatusBarPolicy mPrivacyItemController = privacyItemController; mSensorPrivacyController = sensorPrivacyController; mRecordingController = recordingController; + mMainExecutor = mainExecutor; mUiBgExecutor = uiBgExecutor; mTelecomManager = telecomManager; mRingerModeTracker = ringerModeTracker; @@ -256,11 +255,7 @@ public class PhoneStatusBarPolicy mRingerModeTracker.getRingerModeInternal().observeForever(observer); // listen for user / profile change. - try { - mIActivityManager.registerUserSwitchObserver(mUserSwitchListener, TAG); - } catch (RemoteException e) { - // Ignore - } + mUserTracker.addCallback(mUserSwitchListener, mMainExecutor); // TTY status updateTTY(); @@ -555,15 +550,15 @@ public class PhoneStatusBarPolicy }); } - private final SynchronousUserSwitchObserver mUserSwitchListener = - new SynchronousUserSwitchObserver() { + private final UserTracker.Callback mUserSwitchListener = + new UserTracker.Callback() { @Override - public void onUserSwitching(int newUserId) throws RemoteException { + public void onUserChanging(int newUser, Context userContext) { mHandler.post(() -> mUserInfoController.reloadUserInfo()); } @Override - public void onUserSwitchComplete(int newUserId) throws RemoteException { + public void onUserChanged(int newUser, Context userContext) { mHandler.post(() -> { updateAlarm(); updateManagedProfile(); diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt index 2683971f852c..981f429d1f8f 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt @@ -61,8 +61,6 @@ class FoldStateLoggingProviderImpl( foldStateProvider.stop() } - override fun onHingeAngleUpdate(angle: Float) {} - override fun onFoldUpdate(@FoldUpdate update: Int) { val now = clock.elapsedRealtime() when (update) { @@ -77,6 +75,10 @@ class FoldStateLoggingProviderImpl( } } + override fun onUnfoldedScreenAvailable() { + Log.d(TAG, "Unfolded screen available") + } + private fun dispatchState(@LoggedFoldedStates current: Int) { val now = clock.elapsedRealtime() val previous = lastState diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index ad1e5fe08619..b2b7c0b8056f 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -17,11 +17,8 @@ package com.android.systemui.user.data.repository -import android.app.IActivityManager -import android.app.UserSwitchObserver import android.content.Context import android.content.pm.UserInfo -import android.os.IRemoteCallback import android.os.UserHandle import android.os.UserManager import android.provider.Settings @@ -118,7 +115,6 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, private val globalSettings: GlobalSettings, private val tracker: UserTracker, - private val activityManager: IActivityManager, featureFlags: FeatureFlags, ) : UserRepository { @@ -203,18 +199,18 @@ constructor( private fun observeUserSwitching() { conflatedCallbackFlow { val callback = - object : UserSwitchObserver() { - override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback) { + object : UserTracker.Callback { + override fun onUserChanging(newUser: Int, userContext: Context) { trySendWithFailureLogging(true, TAG, "userSwitching started") } - override fun onUserSwitchComplete(newUserId: Int) { + override fun onUserChanged(newUserId: Int, userContext: Context) { trySendWithFailureLogging(false, TAG, "userSwitching completed") } } - activityManager.registerUserSwitchObserver(callback, TAG) + tracker.addCallback(callback, mainDispatcher.asExecutor()) trySendWithFailureLogging(false, TAG, "initial value defaulting to false") - awaitClose { activityManager.unregisterUserSwitchObserver(callback) } + awaitClose { tracker.removeCallback(callback) } } .onEach { _isUserSwitchingInProgress.value = it } // TODO (b/262838215), Make this stateIn and initialize directly in field declaration diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index d91279399341..ed928702b981 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -18,8 +18,10 @@ package com.android.keyguard import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.EditText +import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils @@ -30,6 +32,7 @@ import com.android.systemui.util.concurrency.DelayableExecutor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock @@ -37,6 +40,7 @@ import org.mockito.Mockito import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` +import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @SmallTest @@ -76,7 +80,9 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry)) .thenReturn(passwordEntry) `when`(keyguardPasswordView.resources).thenReturn(context.resources) - keyguardPasswordViewController = + `when`(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button)) + .thenReturn(mock(ImageView::class.java)) + keyguardPasswordViewController = KeyguardPasswordViewController( keyguardPasswordView, keyguardUpdateMonitor, @@ -113,6 +119,18 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { } @Test + fun onApplyWindowInsetsListener_onApplyWindowInsets() { + `when`(keyguardViewController.isBouncerShowing).thenReturn(false) + val argumentCaptor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + + keyguardPasswordViewController.onViewAttached() + verify(keyguardPasswordView).setOnApplyWindowInsetsListener(argumentCaptor.capture()) + argumentCaptor.value.onApplyWindowInsets(keyguardPasswordView, null) + + verify(keyguardPasswordView).hideKeyboard() + } + + @Test fun testHideKeyboardWhenOnPause() { keyguardPasswordViewController.onPause() keyguardPasswordView.post { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index fe812dbf4a67..3c80dad2bd82 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -56,8 +56,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; -import android.app.ActivityManager; -import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.app.trust.IStrongAuthTracker; import android.app.trust.TrustManager; @@ -90,7 +88,6 @@ import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; -import android.os.IRemoteCallback; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; @@ -148,6 +145,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; @@ -231,8 +229,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitorLogger mKeyguardUpdateMonitorLogger; @Mock - private IActivityManager mActivityService; - @Mock private SessionTracker mSessionTracker; @Mock private UiEventLogger mUiEventLogger; @@ -269,8 +265,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Before public void setup() throws RemoteException { MockitoAnnotations.initMocks(this); - when(mActivityService.getCurrentUser()).thenReturn(mCurrentUserInfo); - when(mActivityService.getCurrentUserId()).thenReturn(mCurrentUserId); when(mFaceManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.hasEnrolledTemplates()).thenReturn(true); when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); @@ -310,13 +304,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mMockitoSession = ExtendedMockito.mockitoSession() .spyStatic(SubscriptionManager.class) - .spyStatic(ActivityManager.class) .startMocking(); ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID) .when(SubscriptionManager::getDefaultSubscriptionId); KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId); when(mUserTracker.getUserId()).thenReturn(mCurrentUserId); - ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService); mContext.getOrCreateTestableResources().addOverride( com.android.systemui.R.integer.config_face_auth_supported_posture, @@ -1071,11 +1063,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testBiometricsCleared_whenUserSwitches() throws Exception { - final IRemoteCallback reply = new IRemoteCallback.Stub() { - @Override - public void sendResult(Bundle data) { - } // do nothing - }; final BiometricAuthenticated dummyAuthentication = new BiometricAuthenticated(true /* authenticated */, true /* strong */); mKeyguardUpdateMonitor.mUserFaceAuthenticated.put(0 /* user */, dummyAuthentication); @@ -1083,18 +1070,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(1); assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(1); - mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, reply); + mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, new CountDownLatch(0)); assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(0); assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(0); } @Test public void testMultiUserJankMonitor_whenUserSwitches() throws Exception { - final IRemoteCallback reply = new IRemoteCallback.Stub() { - @Override - public void sendResult(Bundle data) { - } // do nothing - }; mKeyguardUpdateMonitor.handleUserSwitchComplete(10 /* user */); verify(mInteractionJankMonitor).end(InteractionJankMonitor.CUJ_USER_SWITCH); verify(mLatencyTracker).onActionEnd(LatencyTracker.ACTION_USER_SWITCH); diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index e35b2a384bd0..28e80057a672 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -39,11 +39,9 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat -import java.io.File -import java.util.Optional -import java.util.function.Consumer import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -58,7 +56,9 @@ import org.mockito.ArgumentMatchers.anyString import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.`when` import org.mockito.Mockito.anyInt +import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.inOrder import org.mockito.Mockito.mock import org.mockito.Mockito.never @@ -66,9 +66,10 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.Mockito.`when` -import org.mockito.Mockito.clearInvocations import org.mockito.MockitoAnnotations +import java.io.File +import java.util.* +import java.util.function.Consumer @SmallTest @RunWith(AndroidTestingRunner::class) @@ -146,6 +147,7 @@ class ControlsControllerImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf()) `when`(userTracker.userHandle).thenReturn(UserHandle.of(user)) delayableExecutor = FakeExecutor(FakeSystemClock()) @@ -945,6 +947,28 @@ class ControlsControllerImplTest : SysuiTestCase() { controller.bindComponentForPanel(TEST_COMPONENT) verify(bindingController).bindServiceForPanel(TEST_COMPONENT) } + + @Test + fun testRemoveFavoriteRemovesFavorite() { + val componentName = ComponentName(context, "test.Cls") + controller.addFavorite( + componentName, + "test structure", + ControlInfo( + controlId = "testId", + controlTitle = "Test Control", + controlSubtitle = "test control subtitle", + deviceType = DeviceTypes.TYPE_LIGHT, + ), + ) + + controller.removeFavorites(componentName) + delayableExecutor.runAllReady() + + verify(authorizedPanelsRepository) + .removeAuthorizedPanels(eq(setOf(componentName.packageName))) + assertThat(controller.getFavorites()).isEmpty() + } } private class DidRunRunnable() : Runnable { diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt index b91a3fd4b28c..7ac1953ee495 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt @@ -115,6 +115,18 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() { assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE) } + @Test + fun testRemoveAuthorizedPackageRemovesIt() { + val sharedPrefs = FakeSharedPreferences() + val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs)) + val repository = createRepository(fileManager) + repository.addAuthorizedPanels(setOf(TEST_PACKAGE)) + + repository.removeAuthorizedPanels(setOf(TEST_PACKAGE)) + + assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty() + } + private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl { return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt new file mode 100644 index 000000000000..1e8cd4117688 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.controls.ui + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.FakeSystemUIDialogController +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.eq +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ControlsDialogsFactoryTest : SysuiTestCase() { + + private companion object { + const val APP_NAME = "Test App" + } + + private val fakeDialogController = FakeSystemUIDialogController() + + private lateinit var underTest: ControlsDialogsFactory + + @Before + fun setup() { + underTest = ControlsDialogsFactory { fakeDialogController.dialog } + } + + @Test + fun testCreatesRemoveAppDialog() { + val dialog = underTest.createRemoveAppDialog(context, APP_NAME) {} + + verify(dialog) + .setTitle( + eq(context.getString(R.string.controls_panel_remove_app_authorization, APP_NAME)) + ) + verify(dialog).setCanceledOnTouchOutside(eq(true)) + } + + @Test + fun testPositiveClickRemoveAppDialogWorks() { + var dialogResult: Boolean? = null + underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it } + + fakeDialogController.clickPositive() + + assertThat(dialogResult).isTrue() + } + + @Test + fun testNeutralClickRemoveAppDialogWorks() { + var dialogResult: Boolean? = null + underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it } + + fakeDialogController.clickNeutral() + + assertThat(dialogResult).isFalse() + } + + @Test + fun testCancelRemoveAppDialogWorks() { + var dialogResult: Boolean? = null + underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it } + + fakeDialogController.cancel() + + assertThat(dialogResult).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt index aa90e2a45f10..23faa99c0b9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt @@ -52,6 +52,7 @@ import com.android.systemui.shade.ShadeController import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences +import com.android.systemui.util.FakeSystemUIDialogController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -63,21 +64,20 @@ import com.android.systemui.util.time.FakeSystemClock import com.android.wm.shell.TaskView import com.android.wm.shell.TaskViewFactory import com.google.common.truth.Truth.assertThat -import dagger.Lazy -import java.util.Optional -import java.util.function.Consumer import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.`when` import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyString import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import java.util.Optional +import java.util.function.Consumer @SmallTest @RunWith(AndroidTestingRunner::class) @@ -98,13 +98,15 @@ class ControlsUiControllerImplTest : SysuiTestCase() { @Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository @Mock lateinit var featureFlags: FeatureFlags @Mock lateinit var packageManager: PackageManager - val sharedPreferences = FakeSharedPreferences() - lateinit var controlsSettingsRepository: FakeControlsSettingsRepository - var uiExecutor = FakeExecutor(FakeSystemClock()) - var bgExecutor = FakeExecutor(FakeSystemClock()) - lateinit var underTest: ControlsUiControllerImpl - lateinit var parent: FrameLayout + private val sharedPreferences = FakeSharedPreferences() + private val fakeDialogController = FakeSystemUIDialogController() + private val uiExecutor = FakeExecutor(FakeSystemClock()) + private val bgExecutor = FakeExecutor(FakeSystemClock()) + + private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository + private lateinit var parent: FrameLayout + private lateinit var underTest: ControlsUiControllerImpl @Before fun setup() { @@ -125,12 +127,12 @@ class ControlsUiControllerImplTest : SysuiTestCase() { underTest = ControlsUiControllerImpl( - Lazy { controlsController }, + { controlsController }, context, packageManager, uiExecutor, bgExecutor, - Lazy { controlsListingController }, + { controlsListingController }, controlActionCoordinator, activityStarter, iconCache, @@ -142,7 +144,8 @@ class ControlsUiControllerImplTest : SysuiTestCase() { controlsSettingsRepository, authorizedPanelsRepository, featureFlags, - dumpManager + ControlsDialogsFactory { fakeDialogController.dialog }, + dumpManager, ) `when`( userFileManager.getSharedPreferences( @@ -410,8 +413,45 @@ class ControlsUiControllerImplTest : SysuiTestCase() { verify(controlsListingController, never()).removeCallback(any()) } + @Test + fun testRemovingAppsRemovesFavorite() { + val componentName = ComponentName(context, "cls") + whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true) + val panel = SelectedItem.PanelItem("App name", componentName) + sharedPreferences + .edit() + .putString("controls_component", panel.componentName.flattenToString()) + .putString("controls_structure", panel.appName.toString()) + .putBoolean("controls_is_panel", true) + .commit() + underTest.show(parent, {}, context) + underTest.startRemovingApp(componentName, "Test App") + + fakeDialogController.clickPositive() + + verify(controlsController).removeFavorites(eq(componentName)) + assertThat(underTest.getPreferredSelectedItem(emptyList())) + .isEqualTo(SelectedItem.EMPTY_SELECTION) + with(sharedPreferences) { + assertThat(contains("controls_component")).isFalse() + assertThat(contains("controls_structure")).isFalse() + assertThat(contains("controls_is_panel")).isFalse() + } + } + + @Test + fun testHideCancelsTheRemoveAppDialog() { + val componentName = ComponentName(context, "cls") + underTest.show(parent, {}, context) + underTest.startRemovingApp(componentName, "Test App") + + underTest.hide(parent) + + verify(fakeDialogController.dialog).cancel() + } + private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo { - val activity = ComponentName("pkg", "activity") + val activity = ComponentName(context, "activity") sharedPreferences .edit() .putString("controls_component", panel.componentName.flattenToString()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java index ef62abfe36de..175da0b7a5c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java @@ -33,6 +33,8 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.condition.SelfExecutingMonitor; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.smartspace.DreamSmartspaceController; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.BcSmartspaceDataPlugin; import com.android.systemui.shared.condition.Condition; import com.android.systemui.shared.condition.Monitor; @@ -65,6 +67,9 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { @Mock private View mBcSmartspaceView; + @Mock + private FeatureFlags mFeatureFlags; + private Monitor mMonitor; private final Set<Condition> mPreconditions = new HashSet<>(); @@ -73,6 +78,8 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); mMonitor = SelfExecutingMonitor.createInstance(); + + when(mFeatureFlags.isEnabled(Flags.HIDE_SMARTSPACE_ON_DREAM_OVERLAY)).thenReturn(false); } /** @@ -85,12 +92,22 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication)); } - private SmartSpaceComplication.Registrant getRegistrant() { - return new SmartSpaceComplication.Registrant( - mDreamOverlayStateController, - mComplication, - mSmartspaceController, - mMonitor); + @Test + public void testRegistrantStart_featureEnabled_addOverlayStateCallback() { + final SmartSpaceComplication.Registrant registrant = getRegistrant(); + registrant.start(); + + verify(mDreamOverlayStateController).addCallback(any()); + } + + @Test + public void testRegistrantStart_featureDisabled_doesNotAddOverlayStateCallback() { + when(mFeatureFlags.isEnabled(Flags.HIDE_SMARTSPACE_ON_DREAM_OVERLAY)).thenReturn(true); + + final SmartSpaceComplication.Registrant registrant = getRegistrant(); + registrant.start(); + + verify(mDreamOverlayStateController, never()).addCallback(any()); } @Test @@ -188,4 +205,13 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mBcSmartspaceView); assertEquals(viewHolder.getView(), viewHolder.getView()); } + + private SmartSpaceComplication.Registrant getRegistrant() { + return new SmartSpaceComplication.Registrant( + mDreamOverlayStateController, + mComplication, + mSmartspaceController, + mMonitor, + mFeatureFlags); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index 21ad5e2cd311..5dc04f7efa63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.coroutines.collectLastValue @@ -38,11 +39,14 @@ import com.android.systemui.keyguard.data.repository.BiometricType.FACE import com.android.systemui.keyguard.data.repository.BiometricType.REAR_FINGERPRINT import com.android.systemui.keyguard.data.repository.BiometricType.SIDE_FINGERPRINT import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT +import com.android.systemui.keyguard.shared.model.DevicePosture +import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.TestScope @@ -62,6 +66,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) @RunWith(AndroidTestingRunner::class) @@ -78,6 +83,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { private lateinit var biometricManagerCallback: ArgumentCaptor<IBiometricEnabledOnKeyguardCallback.Stub> private lateinit var userRepository: FakeUserRepository + private lateinit var devicePostureRepository: FakeDevicePostureRepository private lateinit var testDispatcher: TestDispatcher private lateinit var testScope: TestScope @@ -90,6 +96,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { testDispatcher = StandardTestDispatcher() testScope = TestScope(testDispatcher) userRepository = FakeUserRepository() + devicePostureRepository = FakeDevicePostureRepository() } private suspend fun createBiometricSettingsRepository() { @@ -108,6 +115,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { looper = testableLooper!!.looper, dumpManager = dumpManager, biometricManager = biometricManager, + devicePostureRepository = devicePostureRepository, ) testScope.runCurrent() } @@ -299,6 +307,50 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { verify(biometricManager, times(1)).registerEnabledOnKeyguardCallback(any()) } + @Test + fun faceAuthIsAlwaysSupportedIfSpecificPostureIsNotConfigured() = + testScope.runTest { + overrideResource( + R.integer.config_face_auth_supported_posture, + DevicePostureController.DEVICE_POSTURE_UNKNOWN + ) + + createBiometricSettingsRepository() + + assertThat(collectLastValue(underTest.isFaceAuthSupportedInCurrentPosture)()).isTrue() + } + + @Test + fun faceAuthIsSupportedOnlyWhenDevicePostureMatchesConfigValue() = + testScope.runTest { + overrideResource( + R.integer.config_face_auth_supported_posture, + DevicePostureController.DEVICE_POSTURE_FLIPPED + ) + + createBiometricSettingsRepository() + + val isFaceAuthSupported = + collectLastValue(underTest.isFaceAuthSupportedInCurrentPosture) + + assertThat(isFaceAuthSupported()).isFalse() + + devicePostureRepository.setCurrentPosture(DevicePosture.CLOSED) + assertThat(isFaceAuthSupported()).isFalse() + + devicePostureRepository.setCurrentPosture(DevicePosture.HALF_OPENED) + assertThat(isFaceAuthSupported()).isFalse() + + devicePostureRepository.setCurrentPosture(DevicePosture.OPENED) + assertThat(isFaceAuthSupported()).isFalse() + + devicePostureRepository.setCurrentPosture(DevicePosture.UNKNOWN) + assertThat(isFaceAuthSupported()).isFalse() + + devicePostureRepository.setCurrentPosture(DevicePosture.FLIPPED) + assertThat(isFaceAuthSupported()).isTrue() + } + private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) { authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt new file mode 100644 index 000000000000..bd6b7a853dfc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.shared.model.DevicePosture +import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class DevicePostureRepositoryTest : SysuiTestCase() { + private lateinit var underTest: DevicePostureRepository + private lateinit var testScope: TestScope + @Mock private lateinit var devicePostureController: DevicePostureController + @Captor private lateinit var callback: ArgumentCaptor<DevicePostureController.Callback> + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + testScope = TestScope() + underTest = DevicePostureRepositoryImpl(postureController = devicePostureController) + } + + @Test + fun postureChangesArePropagated() = + testScope.runTest { + whenever(devicePostureController.devicePosture) + .thenReturn(DevicePostureController.DEVICE_POSTURE_FLIPPED) + val currentPosture = collectLastValue(underTest.currentDevicePosture) + assertThat(currentPosture()).isEqualTo(DevicePosture.FLIPPED) + + verify(devicePostureController).addCallback(callback.capture()) + + callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_UNKNOWN) + assertThat(currentPosture()).isEqualTo(DevicePosture.UNKNOWN) + + callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_CLOSED) + assertThat(currentPosture()).isEqualTo(DevicePosture.CLOSED) + + callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED) + assertThat(currentPosture()).isEqualTo(DevicePosture.HALF_OPENED) + + callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED) + assertThat(currentPosture()).isEqualTo(DevicePosture.OPENED) + + callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_FLIPPED) + assertThat(currentPosture()).isEqualTo(DevicePosture.FLIPPED) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 8bd8be565eee..c727b3a6cd10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -308,7 +308,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { TestConfig( isVisible = true, isClickable = false, - isActivated = true, + isActivated = false, icon = icon, canShowWhileLocked = false, intent = Intent("action"), @@ -363,7 +363,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { TestConfig( isVisible = true, isClickable = false, - isActivated = true, + isActivated = false, icon = icon, canShowWhileLocked = false, intent = Intent("action"), diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index 8c54da1c3153..ab0669a28f04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -139,6 +139,7 @@ class MediaDataManagerTest : SysuiTestCase() { @Mock private lateinit var logger: MediaUiEventLogger lateinit var mediaDataManager: MediaDataManager lateinit var mediaNotification: StatusBarNotification + lateinit var remoteCastNotification: StatusBarNotification @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData> private val clock = FakeSystemClock() @Mock private lateinit var tunerService: TunerService @@ -207,6 +208,20 @@ class MediaDataManagerTest : SysuiTestCase() { } build() } + remoteCastNotification = + SbnBuilder().run { + setPkg(SYSTEM_PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle( + MediaStyle().apply { + setMediaSession(session.sessionToken) + setRemotePlaybackInfo("Remote device", 0, null) + } + ) + } + build() + } metadataBuilder = MediaMetadata.Builder().apply { putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) @@ -247,6 +262,7 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true) whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false) whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false) + whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false) whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId()) whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false) } @@ -400,33 +416,8 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnNotificationAdded_isRcn_markedRemote() { - val rcn = - SbnBuilder().run { - setPkg(SYSTEM_PACKAGE_NAME) - modifyNotification(context).also { - it.setSmallIcon(android.R.drawable.ic_media_pause) - it.setStyle( - MediaStyle().apply { - setMediaSession(session.sessionToken) - setRemotePlaybackInfo("Remote device", 0, null) - } - ) - } - build() - } + addNotificationAndLoad(remoteCastNotification) - mediaDataManager.onNotificationAdded(KEY, rcn) - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(listener) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) assertThat(mediaDataCaptor.value!!.playbackLocation) .isEqualTo(MediaData.PLAYBACK_CAST_REMOTE) verify(logger) @@ -710,6 +701,56 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testOnNotificationRemoved_withResumption_isRemoteAndRemoteAllowed() { + // With the flag enabled to allow remote media to resume + whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true) + + // GIVEN that the manager has a notification with a resume action, but is not local + whenever(controller.metadata).thenReturn(metadataBuilder.build()) + whenever(playbackInfo.playbackType) + .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) + addNotificationAndLoad() + val data = mediaDataCaptor.value + val dataRemoteWithResume = + data.copy(resumeAction = Runnable {}, playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) + mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume) + + // WHEN the notification is removed + mediaDataManager.onNotificationRemoved(KEY) + + // THEN the media data is converted to a resume state + verify(listener) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value.resumption).isTrue() + } + + @Test + fun testOnNotificationRemoved_withResumption_isRcnAndRemoteAllowed() { + // With the flag enabled to allow remote media to resume + whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true) + + // GIVEN that the manager has a remote cast notification + addNotificationAndLoad(remoteCastNotification) + val data = mediaDataCaptor.value + assertThat(data.playbackLocation).isEqualTo(MediaData.PLAYBACK_CAST_REMOTE) + val dataRemoteWithResume = data.copy(resumeAction = Runnable {}) + mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume) + + // WHEN the RCN is removed + mediaDataManager.onNotificationRemoved(KEY) + + // THEN the media data is removed + verify(listener).onMediaDataRemoved(eq(KEY)) + } + + @Test fun testOnNotificationRemoved_withResumption_tooManyPlayers() { // Given the maximum number of resume controls already val desc = @@ -1654,22 +1695,7 @@ class MediaDataManagerTest : SysuiTestCase() { ) // update to remote cast - val rcn = - SbnBuilder().run { - setPkg(SYSTEM_PACKAGE_NAME) // System package - modifyNotification(context).also { - it.setSmallIcon(android.R.drawable.ic_media_pause) - it.setStyle( - MediaStyle().apply { - setMediaSession(session.sessionToken) - setRemotePlaybackInfo("Remote device", 0, null) - } - ) - } - build() - } - - mediaDataManager.onNotificationAdded(KEY, rcn) + mediaDataManager.onNotificationAdded(KEY, remoteCastNotification) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(logger) @@ -2038,9 +2064,14 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } - /** Helper function to add a media notification and capture the resulting MediaData */ + /** Helper function to add a basic media notification and capture the resulting MediaData */ private fun addNotificationAndLoad() { - mediaDataManager.onNotificationAdded(KEY, mediaNotification) + addNotificationAndLoad(mediaNotification) + } + + /** Helper function to add the given notification and capture the resulting MediaData */ + private fun addNotificationAndLoad(sbn: StatusBarNotification) { + mediaDataManager.onNotificationAdded(KEY, sbn) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt index 136ace173795..4dfa6261b868 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT +import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.settings.UserTracker import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor @@ -92,6 +93,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Mock private lateinit var mockContext: Context @Mock private lateinit var pendingIntent: PendingIntent @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var mediaFlags: MediaFlags @Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback> @Captor lateinit var actionCaptor: ArgumentCaptor<Runnable> @@ -134,6 +136,7 @@ class MediaResumeListenerTest : SysuiTestCase() { whenever(mockContext.packageManager).thenReturn(context.packageManager) whenever(mockContext.contentResolver).thenReturn(context.contentResolver) whenever(mockContext.userId).thenReturn(context.userId) + whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false) executor = FakeExecutor(clock) resumeListener = @@ -146,7 +149,8 @@ class MediaResumeListenerTest : SysuiTestCase() { tunerService, resumeBrowserFactory, dumpManager, - clock + clock, + mediaFlags, ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) @@ -188,7 +192,8 @@ class MediaResumeListenerTest : SysuiTestCase() { tunerService, resumeBrowserFactory, dumpManager, - clock + clock, + mediaFlags, ) listener.setManager(mediaDataManager) verify(broadcastDispatcher, never()) @@ -244,6 +249,32 @@ class MediaResumeListenerTest : SysuiTestCase() { } @Test + fun testOnLoad_localCast_remoteResumeAllowed_doesCheck() { + // If local cast media is allowed to resume + whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true) + + // When media data is loaded that has not been checked yet, and is a local cast + val dataCast = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) + resumeListener.onMediaDataLoaded(KEY, null, dataCast) + + // Then we report back to the manager + verify(mediaDataManager).setResumeAction(KEY, null) + } + + @Test + fun testOnLoad_remoteCast_remoteResumeAllowed_doesCheck() { + // If local cast media is allowed to resume + whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true) + + // When media data is loaded that has not been checked yet, and is a remote cast + val dataRcn = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_REMOTE) + resumeListener.onMediaDataLoaded(KEY, null, dataRcn) + + // Then we do not take action + verify(mediaDataManager, never()).setResumeAction(any(), any()) + } + + @Test fun testOnLoad_checksForResume_hasService() { setUpMbsWithValidResolveInfo() @@ -389,7 +420,8 @@ class MediaResumeListenerTest : SysuiTestCase() { tunerService, resumeBrowserFactory, dumpManager, - clock + clock, + mediaFlags, ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) @@ -421,7 +453,8 @@ class MediaResumeListenerTest : SysuiTestCase() { tunerService, resumeBrowserFactory, dumpManager, - clock + clock, + mediaFlags, ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) @@ -463,7 +496,8 @@ class MediaResumeListenerTest : SysuiTestCase() { tunerService, resumeBrowserFactory, dumpManager, - clock + clock, + mediaFlags, ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index aacbf8f2adeb..0a91a0ed0532 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -143,8 +143,8 @@ public class NavigationBarControllerTest extends SysuiTestCase { @Test public void testCreateNavigationBarsIncludeDefaultTrue() { - // Tablets may be using taskbar and the logic is different - mNavigationBarController.mIsTablet = false; + // Large screens may be using taskbar and the logic is different + mNavigationBarController.mIsLargeScreen = false; doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any()); mNavigationBarController.createNavigationBars(true, null); @@ -292,7 +292,7 @@ public class NavigationBarControllerTest extends SysuiTestCase { @Test public void testConfigurationChange_taskbarNotInitialized() { Configuration configuration = mContext.getResources().getConfiguration(); - when(Utilities.isTablet(any())).thenReturn(true); + when(Utilities.isLargeScreen(any())).thenReturn(true); mNavigationBarController.onConfigChanged(configuration); verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration); } @@ -300,7 +300,7 @@ public class NavigationBarControllerTest extends SysuiTestCase { @Test public void testConfigurationChange_taskbarInitialized() { Configuration configuration = mContext.getResources().getConfiguration(); - when(Utilities.isTablet(any())).thenReturn(true); + when(Utilities.isLargeScreen(any())).thenReturn(true); when(mTaskbarDelegate.isInitialized()).thenReturn(true); mNavigationBarController.onConfigChanged(configuration); verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index 26eff61066ee..1fdb3647fcb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -35,7 +35,6 @@ import junit.framework.Assert.fail import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope -import org.json.JSONException import org.junit.Before import org.junit.Rule import org.junit.Test @@ -271,10 +270,14 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun jsonDeserialization_gotExpectedObject() { - val expected = ClockSettings("ID", null).apply { _applied_timestamp = 500 } + val expected = ClockSettings("ID", null).apply { + metadata.put("appliedTimestamp", 500) + } val actual = ClockSettings.deserialize("""{ "clockId":"ID", - "_applied_timestamp":500 + "metadata": { + "appliedTimestamp":500 + } }""") assertEquals(expected, actual) } @@ -291,29 +294,32 @@ class ClockRegistryTest : SysuiTestCase() { val expected = ClockSettings("ID", null) val actual = ClockSettings.deserialize("""{ "clockId":"ID", - "_applied_timestamp":null + "metadata":null }""") assertEquals(expected, actual) } - @Test(expected = JSONException::class) - fun jsonDeserialization_noId_threwException() { - val expected = ClockSettings(null, null).apply { _applied_timestamp = 500 } - val actual = ClockSettings.deserialize("{\"_applied_timestamp\":500}") + @Test + fun jsonDeserialization_noId_deserializedEmpty() { + val expected = ClockSettings(null, null).apply { + metadata.put("appliedTimestamp", 500) + } + val actual = ClockSettings.deserialize("{\"metadata\":{\"appliedTimestamp\":500}}") assertEquals(expected, actual) } @Test fun jsonSerialization_gotExpectedString() { - val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}" - val actual = ClockSettings.serialize(ClockSettings("ID", null) - .apply { _applied_timestamp = 500 }) + val expected = "{\"clockId\":\"ID\",\"metadata\":{\"appliedTimestamp\":500}}" + val actual = ClockSettings.serialize(ClockSettings("ID", null).apply { + metadata.put("appliedTimestamp", 500) + }) assertEquals(expected, actual) } @Test fun jsonSerialization_noTimestamp_gotExpectedString() { - val expected = "{\"clockId\":\"ID\"}" + val expected = "{\"clockId\":\"ID\",\"metadata\":{}}" val actual = ClockSettings.serialize(ClockSettings("ID", null)) assertEquals(expected, actual) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt new file mode 100644 index 000000000000..7a6779684fc5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogcatEchoTracker +import com.android.systemui.statusbar.StatusBarState +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class NotificationWakeUpCoordinatorLoggerTest : SysuiTestCase() { + + private val logBufferCounter = LogBufferCounter() + private lateinit var logger: NotificationWakeUpCoordinatorLogger + + private fun verifyDidLog(times: Int) { + logBufferCounter.verifyDidLog(times) + } + + @Before + fun setup() { + logger = NotificationWakeUpCoordinatorLogger(logBufferCounter.logBuffer) + } + + @Test + fun setDozeAmountWillThrottleFractionalUpdates() { + logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false) + verifyDidLog(1) + logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true) + verifyDidLog(1) + logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true) + logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true) + logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true) + logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true) + verifyDidLog(0) + logger.logSetDozeAmount(1f, 1f, "source1", StatusBarState.SHADE, changed = true) + verifyDidLog(1) + } + + @Test + fun setDozeAmountWillIncludeFractionalUpdatesWhenStateChanges() { + logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false) + verifyDidLog(1) + logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true) + verifyDidLog(1) + logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true) + logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true) + logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true) + logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true) + verifyDidLog(0) + logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.KEYGUARD, changed = false) + verifyDidLog(1) + } + + @Test + fun setDozeAmountWillIncludeFractionalUpdatesWhenSourceChanges() { + logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false) + verifyDidLog(1) + logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true) + verifyDidLog(1) + logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true) + logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true) + logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true) + logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true) + verifyDidLog(0) + logger.logSetDozeAmount(0.5f, 0.5f, "source2", StatusBarState.SHADE, changed = false) + verifyDidLog(1) + } + + class LogBufferCounter { + val recentLogs = mutableListOf<Pair<String, LogLevel>>() + val tracker = + object : LogcatEchoTracker { + override val logInBackgroundThread: Boolean = false + override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = false + override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { + recentLogs.add(tagName to level) + return true + } + } + val logBuffer = + LogBuffer(name = "test", maxSize = 1, logcatEchoTracker = tracker, systrace = false) + + fun verifyDidLog(times: Int) { + assertThat(recentLogs).hasSize(times) + recentLogs.clear() + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt index 305b9fea7569..6b18169bcd86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.phone import android.app.AlarmManager -import android.app.IActivityManager import android.app.admin.DevicePolicyManager import android.content.SharedPreferences import android.os.UserManager @@ -87,7 +86,6 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var locationController: LocationController @Mock private lateinit var sensorPrivacyController: SensorPrivacyController - @Mock private lateinit var iActivityManager: IActivityManager @Mock private lateinit var alarmManager: AlarmManager @Mock private lateinit var userManager: UserManager @Mock private lateinit var userTracker: UserTracker @@ -176,6 +174,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { commandQueue, broadcastDispatcher, executor, + executor, testableLooper.looper, context.resources, castController, @@ -190,7 +189,6 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { keyguardStateController, locationController, sensorPrivacyController, - iActivityManager, alarmManager, userManager, userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt index 5288608a202d..0413d92b6abb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt @@ -25,7 +25,6 @@ import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING -import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE import com.android.systemui.unfold.util.TestFoldStateProvider import org.junit.Before import org.junit.Test @@ -50,7 +49,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { runOnMainThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendHingeAngleUpdate(10f) }, - { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }, + { foldStateProvider.sendUnfoldedScreenAvailable() }, { foldStateProvider.sendHingeAngleUpdate(90f) }, { foldStateProvider.sendHingeAngleUpdate(180f) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }, @@ -67,7 +66,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { runOnMainThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendHingeAngleUpdate(10f) }, - { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }, + { foldStateProvider.sendUnfoldedScreenAvailable() }, { foldStateProvider.sendHingeAngleUpdate(90f) }, { foldStateProvider.sendHingeAngleUpdate(180f) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }, @@ -84,7 +83,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { { foldStateProvider.sendHingeAngleUpdate(90f) }, { foldStateProvider.sendHingeAngleUpdate(180f) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }, - { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }, + { foldStateProvider.sendUnfoldedScreenAvailable() }, ) with(listener.ensureTransitionFinished()) { @@ -113,7 +112,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() { runOnMainThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, - { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }, + { foldStateProvider.sendUnfoldedScreenAvailable() }, { foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendHingeAngleUpdate(90f) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) }, @@ -129,7 +128,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { fun testFoldImmediatelyAfterUnfold_runsFoldAnimation() { runOnMainThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, - { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }, + { foldStateProvider.sendUnfoldedScreenAvailable() }, { foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendHingeAngleUpdate(90f) }, { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index 6086e16fb49a..8476d0d45603 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider import com.android.systemui.unfold.updates.FoldProvider.FoldCallback import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener +import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener @@ -71,6 +72,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { private val foldUpdates: MutableList<Int> = arrayListOf() private val hingeAngleUpdates: MutableList<Float> = arrayListOf() + private val unfoldedScreenAvailabilityUpdates: MutableList<Unit> = arrayListOf() private var scheduledRunnable: Runnable? = null private var scheduledRunnableDelay: Long? = null @@ -106,6 +108,10 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { override fun onFoldUpdate(update: Int) { foldUpdates.add(update) } + + override fun onUnfoldedScreenAvailable() { + unfoldedScreenAvailabilityUpdates.add(Unit) + } }) foldStateProvider.start() @@ -156,8 +162,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { sendHingeAngleEvent(10) screenOnStatusProvider.notifyScreenTurnedOn() - assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING, - FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING) + assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1) } @Test @@ -174,8 +180,9 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { sendHingeAngleEvent(40) sendHingeAngleEvent(10) - assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING, - FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, FOLD_UPDATE_START_CLOSING) + assertThat(foldUpdates) + .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING) + assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1) } @Test @@ -223,7 +230,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { fireScreenOnEvent() - assertThat(foldUpdates).containsExactly(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) + assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1) } @Test @@ -277,7 +284,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_afterTimeout_finishHalfOpenEventEmitted() { - sendHingeAngleEvent(90) + setInitialHingeAngle(90) sendHingeAngleEvent(80) simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS) @@ -288,7 +295,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_beforeTimeout_abortNotEmitted() { - sendHingeAngleEvent(90) + setInitialHingeAngle(90) sendHingeAngleEvent(80) simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1) @@ -298,7 +305,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_eventBeforeTimeout_oneEventEmitted() { - sendHingeAngleEvent(180) + setInitialHingeAngle(180) sendHingeAngleEvent(90) simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1) @@ -309,7 +316,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_timeoutAfterTimeoutRescheduled_finishHalfOpenStateEmitted() { - sendHingeAngleEvent(180) + setInitialHingeAngle(180) sendHingeAngleEvent(90) // The timeout should not trigger here. @@ -323,7 +330,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_shortTimeBetween_emitsOnlyOneEvents() { - sendHingeAngleEvent(180) + setInitialHingeAngle(180) sendHingeAngleEvent(90) sendHingeAngleEvent(80) @@ -334,20 +341,19 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_whileClosing_emittedDespiteInitialAngle() { val maxAngle = 180 - FULLY_OPEN_THRESHOLD_DEGREES.toInt() - for (i in 1..maxAngle) { - foldUpdates.clear() - - simulateFolding(startAngle = i) + val minAngle = Math.ceil(HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toDouble()).toInt() + 1 + for (startAngle in minAngle..maxAngle) { + setInitialHingeAngle(startAngle) + sendHingeAngleEvent(startAngle - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1) assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) - simulateTimeout() // Timeout to set the state to aborted. } } @Test fun startClosingEvent_whileNotOnLauncher_doesNotTriggerBeforeThreshold() { setupForegroundActivityType(isHomeActivity = false) - sendHingeAngleEvent(180) + setInitialHingeAngle(180) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) @@ -357,7 +363,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_whileActivityTypeNotAvailable_triggerBeforeThreshold() { setupForegroundActivityType(isHomeActivity = null) - sendHingeAngleEvent(180) + setInitialHingeAngle(180) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) @@ -367,7 +373,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_whileOnLauncher_doesTriggerBeforeThreshold() { setupForegroundActivityType(isHomeActivity = true) - sendHingeAngleEvent(180) + setInitialHingeAngle(180) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) @@ -377,9 +383,11 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_whileNotOnLauncher_triggersAfterThreshold() { setupForegroundActivityType(isHomeActivity = false) - sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES) + setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES) - sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES - 1) + sendHingeAngleEvent( + START_CLOSING_ON_APPS_THRESHOLD_DEGREES - + HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1) assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) } @@ -388,7 +396,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { fun startClosingEvent_whileNotOnKeyguardAndNotOnLauncher_doesNotTriggerBeforeThreshold() { setKeyguardVisibility(visible = false) setupForegroundActivityType(isHomeActivity = false) - sendHingeAngleEvent(180) + setInitialHingeAngle(180) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) @@ -398,7 +406,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_whileKeyguardStateNotAvailable_triggerBeforeThreshold() { setKeyguardVisibility(visible = null) - sendHingeAngleEvent(180) + setInitialHingeAngle(180) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) @@ -408,7 +416,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_whileonKeyguard_doesTriggerBeforeThreshold() { setKeyguardVisibility(visible = true) - sendHingeAngleEvent(180) + setInitialHingeAngle(180) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) @@ -418,9 +426,59 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Test fun startClosingEvent_whileNotOnKeyguard_triggersAfterThreshold() { setKeyguardVisibility(visible = false) - sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES) + setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES) - sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES - 1) + sendHingeAngleEvent( + START_CLOSING_ON_APPS_THRESHOLD_DEGREES - + HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) + } + + @Test + fun startClosingEvent_doesNotTriggerBelowThreshold() { + val thresholdAngle = (FULLY_OPEN_DEGREES - FULLY_OPEN_THRESHOLD_DEGREES).toInt() + setInitialHingeAngle(180) + sendHingeAngleEvent(thresholdAngle + 1) + + assertThat(foldUpdates).isEmpty() + } + + @Test + fun startClosingEvent_triggersAfterThreshold() { + val thresholdAngle = (FULLY_OPEN_DEGREES - FULLY_OPEN_THRESHOLD_DEGREES).toInt() + setInitialHingeAngle(180) + sendHingeAngleEvent(thresholdAngle + 1) + sendHingeAngleEvent(thresholdAngle - 1) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) + } + + @Test + fun startClosingEvent_triggersAfterThreshold_fromHalfOpen() { + setInitialHingeAngle(120) + sendHingeAngleEvent((120 - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES + 1).toInt()) + assertThat(foldUpdates).isEmpty() + sendHingeAngleEvent((120 - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES - 1).toInt()) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) + } + + @Test + fun startOpeningAndClosingEvents_triggerWithOpenAndClose() { + setInitialHingeAngle(120) + sendHingeAngleEvent(130) + sendHingeAngleEvent(120) + assertThat(foldUpdates) + .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING) + } + + @Test + fun startClosingEvent_notInterrupted_whenAngleIsSlightlyIncreased() { + setInitialHingeAngle(120) + sendHingeAngleEvent(110) + sendHingeAngleEvent(111) + sendHingeAngleEvent(100) assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) } @@ -504,11 +562,6 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { } } - private fun simulateFolding(startAngle: Int) { - sendHingeAngleEvent(startAngle) - sendHingeAngleEvent(startAngle - 1) - } - private fun setFoldState(folded: Boolean) { foldProvider.notifyFolded(folded) } @@ -521,6 +574,17 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { testHingeAngleProvider.notifyAngle(angle.toFloat()) } + private fun setInitialHingeAngle(angle: Int) { + setFoldState(angle == 0) + sendHingeAngleEvent(angle) + if (scheduledRunnableDelay != null) { + simulateTimeout() + } + hingeAngleUpdates.clear() + foldUpdates.clear() + unfoldedScreenAvailabilityUpdates.clear() + } + private class TestFoldProvider : FoldProvider { private val callbacks = arrayListOf<FoldCallback>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt index a064e8c81076..fbb0e5a72cd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt @@ -57,4 +57,8 @@ class TestFoldStateProvider : FoldStateProvider { fun sendHingeAngleUpdate(angle: Float) { listeners.forEach { it.onHingeAngleUpdate(angle) } } + + fun sendUnfoldedScreenAvailable() { + listeners.forEach { it.onUnfoldedScreenAvailable() } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index ccf378a71abd..ddd880b8037c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -17,10 +17,7 @@ package com.android.systemui.user.data.repository -import android.app.IActivityManager -import android.app.UserSwitchObserver import android.content.pm.UserInfo -import android.os.IRemoteCallback import android.os.UserHandle import android.os.UserManager import android.provider.Settings @@ -44,14 +41,8 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.mockito.ArgumentCaptor -import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.any -import org.mockito.Mockito.anyString import org.mockito.Mockito.mock -import org.mockito.Mockito.times -import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -60,8 +51,6 @@ import org.mockito.MockitoAnnotations class UserRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var manager: UserManager - @Mock private lateinit var activityManager: IActivityManager - @Captor private lateinit var userSwitchObserver: ArgumentCaptor<UserSwitchObserver> private lateinit var underTest: UserRepositoryImpl @@ -229,30 +218,31 @@ class UserRepositoryImplTest : SysuiTestCase() { } @Test - fun userSwitchingInProgress_registersOnlyOneUserSwitchObserver() = runSelfCancelingTest { + fun userSwitchingInProgress_registersUserTrackerCallback() = runSelfCancelingTest { underTest = create(this) underTest.userSwitchingInProgress.launchIn(this) underTest.userSwitchingInProgress.launchIn(this) underTest.userSwitchingInProgress.launchIn(this) - verify(activityManager, times(1)).registerUserSwitchObserver(any(), anyString()) + // Two callbacks registered - one for observing user switching and one for observing the + // selected user + assertThat(tracker.callbacks.size).isEqualTo(2) } @Test - fun userSwitchingInProgress_propagatesStateFromActivityManager() = runSelfCancelingTest { + fun userSwitchingInProgress_propagatesStateFromUserTracker() = runSelfCancelingTest { underTest = create(this) - verify(activityManager) - .registerUserSwitchObserver(userSwitchObserver.capture(), anyString()) + assertThat(tracker.callbacks.size).isEqualTo(2) - userSwitchObserver.value.onUserSwitching(0, mock(IRemoteCallback::class.java)) + tracker.onUserChanging(0) var mostRecentSwitchingValue = false underTest.userSwitchingInProgress.onEach { mostRecentSwitchingValue = it }.launchIn(this) assertThat(mostRecentSwitchingValue).isTrue() - userSwitchObserver.value.onUserSwitchComplete(0) + tracker.onUserChanged(0) assertThat(mostRecentSwitchingValue).isFalse() } @@ -332,7 +322,6 @@ class UserRepositoryImplTest : SysuiTestCase() { backgroundDispatcher = IMMEDIATE, globalSettings = globalSettings, tracker = tracker, - activityManager = activityManager, featureFlags = featureFlags, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt index 01dac362432d..d4b1701892c7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flowOf class FakeBiometricSettingsRepository : BiometricSettingsRepository { @@ -42,6 +43,9 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository { override val isFingerprintEnabledByDevicePolicy = _isFingerprintEnabledByDevicePolicy.asStateFlow() + override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> + get() = flowOf(true) + fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) { _isFingerprintEnrolled.value = isFingerprintEnrolled } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDevicePostureRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDevicePostureRepository.kt new file mode 100644 index 000000000000..914c786a1c7f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDevicePostureRepository.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import com.android.systemui.keyguard.shared.model.DevicePosture +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeDevicePostureRepository : DevicePostureRepository { + private val _currentDevicePosture = MutableStateFlow(DevicePosture.UNKNOWN) + override val currentDevicePosture: Flow<DevicePosture> + get() = _currentDevicePosture + + fun setCurrentPosture(posture: DevicePosture) { + _currentDevicePosture.value = posture + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt index 251014fc50b3..4242c1635468 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt @@ -22,6 +22,7 @@ import android.content.pm.UserInfo import android.os.UserHandle import android.test.mock.MockContentResolver import com.android.systemui.util.mockito.mock +import java.util.concurrent.CountDownLatch import java.util.concurrent.Executor /** A fake [UserTracker] to be used in tests. */ @@ -66,11 +67,19 @@ class FakeUserTracker( _userId = _userInfo.id _userHandle = UserHandle.of(_userId) + onUserChanging() + onUserChanged() + } + + fun onUserChanging(userId: Int = _userId) { + val copy = callbacks.toList() + val latch = CountDownLatch(copy.size) + copy.forEach { it.onUserChanging(userId, userContext, latch) } + } + + fun onUserChanged(userId: Int = _userId) { val copy = callbacks.toList() - copy.forEach { - it.onUserChanging(_userId, userContext) - it.onUserChanged(_userId, userContext) - } + copy.forEach { it.onUserChanged(userId, userContext) } } fun onProfileChanged() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt new file mode 100644 index 000000000000..0c9ce0f145f1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.util + +import android.content.DialogInterface +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import org.mockito.ArgumentCaptor +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.verify +import org.mockito.stubbing.Stubber + +class FakeSystemUIDialogController { + + val dialog: SystemUIDialog = mock() + + private val clickListeners: MutableMap<Int, DialogInterface.OnClickListener> = mutableMapOf() + + init { + saveListener(DialogInterface.BUTTON_POSITIVE) + .whenever(dialog) + .setPositiveButton(any(), any()) + saveListener(DialogInterface.BUTTON_POSITIVE) + .whenever(dialog) + .setPositiveButton(any(), any(), any()) + + saveListener(DialogInterface.BUTTON_NEGATIVE) + .whenever(dialog) + .setNegativeButton(any(), any()) + saveListener(DialogInterface.BUTTON_NEGATIVE) + .whenever(dialog) + .setNegativeButton(any(), any(), any()) + + saveListener(DialogInterface.BUTTON_NEUTRAL).whenever(dialog).setNeutralButton(any(), any()) + saveListener(DialogInterface.BUTTON_NEUTRAL) + .whenever(dialog) + .setNeutralButton(any(), any(), any()) + } + + fun clickNegative() { + performClick(DialogInterface.BUTTON_NEGATIVE, "This dialog has no negative button") + } + + fun clickPositive() { + performClick(DialogInterface.BUTTON_POSITIVE, "This dialog has no positive button") + } + + fun clickNeutral() { + performClick(DialogInterface.BUTTON_NEUTRAL, "This dialog has no neutral button") + } + + fun cancel() { + val captor = ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) + verify(dialog).setOnCancelListener(captor.capture()) + captor.value.onCancel(dialog) + } + + private fun performClick(which: Int, errorMessage: String) { + clickListeners + .getOrElse(which) { throw IllegalAccessException(errorMessage) } + .onClick(dialog, which) + } + + private fun saveListener(which: Int): Stubber = doAnswer { + val listener = it.getArgument<DialogInterface.OnClickListener>(1) + clickListeners[which] = listener + Unit + } +} diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt index 4622464b204d..c437e5c23d1b 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt @@ -21,7 +21,6 @@ import android.util.FloatProperty import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED -import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import javax.inject.Inject @@ -59,12 +58,15 @@ constructor(private val foldStateProvider: FoldStateProvider) : } override fun onFoldUpdate(@FoldUpdate update: Int) { - when (update) { - FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> animator.start() - FOLD_UPDATE_FINISH_CLOSED -> animator.cancel() + if (update == FOLD_UPDATE_FINISH_CLOSED) { + animator.cancel() } } + override fun onUnfoldedScreenAvailable() { + animator.start() + } + override fun addCallback(listener: TransitionProgressListener) { listeners.add(listener) } @@ -73,8 +75,6 @@ constructor(private val foldStateProvider: FoldStateProvider) : listeners.remove(listener) } - override fun onHingeAngleUpdate(angle: Float) {} - private object AnimationProgressProperty : FloatProperty<FixedTimingTransitionProgressProvider>("animation_progress") { diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt index 6ffbe5aa25c0..d19b414cb963 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt @@ -28,7 +28,6 @@ import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING -import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener @@ -78,21 +77,11 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor( override fun onFoldUpdate(@FoldUpdate update: Int) { when (update) { - FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> { - startTransition(startValue = 0f) - - // Stop the animation if the device has already opened by the time when - // the display is available as we won't receive the full open event anymore - if (foldStateProvider.isFinishedOpening) { - cancelTransition(endValue = 1f, animate = true) - } - } FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_FINISH_HALF_OPEN -> { // Do not cancel if we haven't started the transition yet. // This could happen when we fully unfolded the device before the screen // became available. In this case we start and immediately cancel the animation - // in FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE event handler, so we don't need to - // cancel it here. + // in onUnfoldedScreenAvailable event handler, so we don't need to cancel it here. if (isTransitionRunning) { cancelTransition(endValue = 1f, animate = true) } @@ -125,6 +114,16 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor( } } + override fun onUnfoldedScreenAvailable() { + startTransition(startValue = 0f) + + // Stop the animation if the device has already opened by the time when + // the display is available as we won't receive the full open event anymore + if (foldStateProvider.isFinishedOpening) { + cancelTransition(endValue = 1f, animate = true) + } + } + private fun cancelTransition(endValue: Float, animate: Boolean) { if (isTransitionRunning && animate) { if (endValue == 1.0f && !isAnimatedCancelRunning) { diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index 97c9ba99f096..82fd2258120a 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -54,6 +54,7 @@ constructor( @FoldUpdate private var lastFoldUpdate: Int? = null @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f + @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngleBeforeTransition: Float = 0f private val hingeAngleListener = HingeAngleListener() private val screenListener = ScreenStatusListener() @@ -112,29 +113,45 @@ constructor( private fun onHingeAngle(angle: Float) { if (DEBUG) { - Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") + Log.d( + TAG, + "Hinge angle: $angle, " + + "lastHingeAngle: $lastHingeAngle, " + + "lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition" + ) Trace.traceCounter(Trace.TRACE_TAG_APP, "hinge_angle", angle.toInt()) } - val isClosing = angle < lastHingeAngle + val currentDirection = + if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING + if (isTransitionInProgress && currentDirection != lastFoldUpdate) { + lastHingeAngleBeforeTransition = lastHingeAngle + } + + val isClosing = angle < lastHingeAngleBeforeTransition + val transitionUpdate = + if (isClosing) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING + val angleChangeSurpassedThreshold = + Math.abs(angle - lastHingeAngleBeforeTransition) > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES - val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING + val eventNotAlreadyDispatched = lastFoldUpdate != transitionUpdate val screenAvailableEventSent = isUnfoldHandled - if (isClosing // hinge angle should be decreasing since last update - && !closingEventDispatched // we haven't sent closing event already - && !isFullyOpened // do not send closing event if we are in fully opened hinge + if ( + angleChangeSurpassedThreshold && // Do not react immediately to small changes in angle + eventNotAlreadyDispatched && // we haven't sent transition event already + !isFullyOpened && // do not send transition event if we are in fully opened hinge // angle range as closing threshold could overlap this range - && screenAvailableEventSent // do not send closing event if we are still in - // the process of turning on the inner display - && isClosingThresholdMet(angle) // hinge angle is below certain threshold. + screenAvailableEventSent && // do not send transition event if we are still in the + // process of turning on the inner display + isClosingThresholdMet(angle) // hinge angle is below certain threshold. ) { - notifyFoldUpdate(FOLD_UPDATE_START_CLOSING) + notifyFoldUpdate(transitionUpdate, lastHingeAngle) } if (isTransitionInProgress) { if (isFullyOpened) { - notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) + notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN, angle) cancelTimeout() } else { // The timeout will trigger some constant time after the last angle update. @@ -146,7 +163,7 @@ constructor( outputListeners.forEach { it.onHingeAngleUpdate(angle) } } - private fun isClosingThresholdMet(currentAngle: Float) : Boolean { + private fun isClosingThresholdMet(currentAngle: Float): Boolean { val closingThreshold = getClosingThreshold() return closingThreshold == null || currentAngle < closingThreshold } @@ -179,23 +196,29 @@ constructor( if (isFolded) { hingeAngleProvider.stop() - notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) + notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED, lastHingeAngle) cancelTimeout() isUnfoldHandled = false } else { - notifyFoldUpdate(FOLD_UPDATE_START_OPENING) + notifyFoldUpdate(FOLD_UPDATE_START_OPENING, lastHingeAngle) rescheduleAbortAnimationTimeout() hingeAngleProvider.start() } } } - private fun notifyFoldUpdate(@FoldUpdate update: Int) { + private fun notifyFoldUpdate(@FoldUpdate update: Int, angle: Float) { if (DEBUG) { Log.d(TAG, update.name()) } + val previouslyTransitioning = isTransitionInProgress + outputListeners.forEach { it.onFoldUpdate(update) } lastFoldUpdate = update + + if (previouslyTransitioning != isTransitionInProgress) { + lastHingeAngleBeforeTransition = angle + } } private fun rescheduleAbortAnimationTimeout() { @@ -209,7 +232,8 @@ constructor( handler.removeCallbacks(timeoutRunnable) } - private fun cancelAnimation(): Unit = notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) + private fun cancelAnimation(): Unit = + notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN, lastHingeAngle) private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener { @@ -221,7 +245,7 @@ constructor( // receive 'folded' event. If SystemUI started when device is already folded it will // still receive 'folded' event on startup. if (!isFolded && !isUnfoldHandled) { - outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) } + outputListeners.forEach { it.onUnfoldedScreenAvailable() } isUnfoldHandled = true } } @@ -257,7 +281,6 @@ fun @receiver:FoldUpdate Int.name() = when (this) { FOLD_UPDATE_START_OPENING -> "START_OPENING" FOLD_UPDATE_START_CLOSING -> "START_CLOSING" - FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE" FOLD_UPDATE_FINISH_HALF_OPEN -> "FINISH_HALF_OPEN" FOLD_UPDATE_FINISH_FULL_OPEN -> "FINISH_FULL_OPEN" FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED" @@ -270,5 +293,8 @@ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) /** Threshold after which we consider the device fully unfolded. */ @VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f +/** Threshold after which hinge angle updates are considered. This is to eliminate noise. */ +@VisibleForTesting const val HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES = 7.5f + /** Fold animation on top of apps only when the angle exceeds this threshold. */ @VisibleForTesting const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60 diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt index c7a8bf336777..0af372f9da24 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt @@ -31,8 +31,9 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { val isFinishedOpening: Boolean interface FoldUpdatesListener { - fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float) - fun onFoldUpdate(@FoldUpdate update: Int) + @JvmDefault fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float) {} + @JvmDefault fun onFoldUpdate(@FoldUpdate update: Int) {} + @JvmDefault fun onUnfoldedScreenAvailable() {} } @IntDef( @@ -40,7 +41,6 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { [ FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING, - FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, FOLD_UPDATE_FINISH_HALF_OPEN, FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_FINISH_CLOSED]) @@ -50,7 +50,6 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { const val FOLD_UPDATE_START_OPENING = 0 const val FOLD_UPDATE_START_CLOSING = 1 -const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 2 -const val FOLD_UPDATE_FINISH_HALF_OPEN = 3 -const val FOLD_UPDATE_FINISH_FULL_OPEN = 4 -const val FOLD_UPDATE_FINISH_CLOSED = 5 +const val FOLD_UPDATE_FINISH_HALF_OPEN = 2 +const val FOLD_UPDATE_FINISH_FULL_OPEN = 3 +const val FOLD_UPDATE_FINISH_CLOSED = 4 diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index f74356debd0f..ccab7bc9f31d 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -42,6 +42,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; /** * Internal controller for starting and stopping the current dream and managing related state. @@ -119,10 +120,19 @@ final class DreamController { + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze + ", userId=" + userId + ", reason='" + reason + "'"); - if (mCurrentDream != null) { - mPreviousDreams.add(mCurrentDream); - } + final DreamRecord oldDream = mCurrentDream; mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock); + if (oldDream != null) { + if (Objects.equals(oldDream.mName, mCurrentDream.mName)) { + // We are attempting to start a dream that is currently waking up gently. + // Let's silently stop the old instance here to clear the dream state. + // This should happen after the new mCurrentDream is set to avoid announcing + // a "dream stopped" state. + stopDreamInstance(/* immediately */ true, "restarting same dream", oldDream); + } else { + mPreviousDreams.add(oldDream); + } + } mCurrentDream.mDreamStartTime = SystemClock.elapsedRealtime(); MetricsLogger.visible(mContext, diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 632a34e4470d..921233132931 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -430,6 +430,7 @@ class ShortcutPackage extends ShortcutPackageItem { @NonNull List<ShortcutInfo> changedShortcuts) { Preconditions.checkArgument(newShortcut.isEnabled(), "pushDynamicShortcuts() cannot publish disabled shortcuts"); + ensureShortcutCountBeforePush(); newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC); @@ -437,7 +438,7 @@ class ShortcutPackage extends ShortcutPackageItem { final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId()); boolean deleted = false; - if (oldShortcut == null) { + if (oldShortcut == null || !oldShortcut.isDynamic()) { final ShortcutService service = mShortcutUser.mService; final int maxShortcuts = service.getMaxActivityShortcuts(); @@ -446,18 +447,12 @@ class ShortcutPackage extends ShortcutPackageItem { final ArrayList<ShortcutInfo> activityShortcuts = all.get(newShortcut.getActivity()); if (activityShortcuts != null && activityShortcuts.size() > maxShortcuts) { - Slog.e(TAG, "Error pushing shortcut. There are already " - + activityShortcuts.size() + " shortcuts, exceeding the " + maxShortcuts - + " shortcuts limit when pushing the new shortcut " + newShortcut - + ". Id of shortcuts currently available in system memory are " - + activityShortcuts.stream().map(ShortcutInfo::getId) - .collect(Collectors.joining(",", "[", "]"))); - // TODO: This should not have happened. If it does, identify the root cause where - // possible, otherwise bail-out early to prevent memory issue. + // Root cause was discovered in b/233155034, so this should not be happening. + service.wtf("Error pushing shortcut. There are already " + + activityShortcuts.size() + " shortcuts."); } if (activityShortcuts != null && activityShortcuts.size() == maxShortcuts) { // Max has reached. Delete the shortcut with lowest rank. - // Sort by isManifestShortcut() and getRank(). Collections.sort(activityShortcuts, mShortcutTypeAndRankComparator); @@ -473,7 +468,8 @@ class ShortcutPackage extends ShortcutPackageItem { deleted = deleteDynamicWithId(shortcut.getId(), /* ignoreInvisible =*/ true, /*ignorePersistedShortcuts=*/ true) != null; } - } else { + } + if (oldShortcut != null) { // It's an update case. // Make sure the target is updatable. (i.e. should be mutable.) oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false); @@ -505,6 +501,32 @@ class ShortcutPackage extends ShortcutPackageItem { return deleted; } + private void ensureShortcutCountBeforePush() { + final ShortcutService service = mShortcutUser.mService; + // Ensure the total number of shortcuts doesn't exceed the hard limit per app. + final int maxShortcutPerApp = service.getMaxAppShortcuts(); + synchronized (mLock) { + final List<ShortcutInfo> appShortcuts = mShortcuts.values().stream().filter(si -> + !si.isPinned()).collect(Collectors.toList()); + if (appShortcuts.size() >= maxShortcutPerApp) { + // Max has reached. Removes shortcuts until they fall within the hard cap. + // Sort by isManifestShortcut(), isDynamic() and getLastChangedTimestamp(). + Collections.sort(appShortcuts, mShortcutTypeRankAndTimeComparator); + + while (appShortcuts.size() >= maxShortcutPerApp) { + final ShortcutInfo shortcut = appShortcuts.remove(appShortcuts.size() - 1); + if (shortcut.isDeclaredInManifest()) { + // All shortcuts are manifest shortcuts and cannot be removed. + throw new IllegalArgumentException(getPackageName() + " has published " + + appShortcuts.size() + " manifest shortcuts across different" + + " activities."); + } + forceDeleteShortcutInner(shortcut.getId()); + } + } + } + } + /** * Remove all shortcuts that aren't pinned, cached nor dynamic. * @@ -1371,6 +1393,61 @@ class ShortcutPackage extends ShortcutPackageItem { }; /** + * To sort by isManifestShortcut(), isDynamic(), getRank() and + * getLastChangedTimestamp(). i.e. manifest shortcuts come before non-manifest shortcuts, + * dynamic shortcuts come before floating shortcuts, then sort by last changed timestamp. + * + * This is used to decide which shortcuts to remove when the total number of shortcuts retained + * for the app exceeds the limit defined in {@link ShortcutService#getMaxAppShortcuts()}. + * + * (Note the number of manifest shortcuts is always <= the max number, because if there are + * more, ShortcutParser would ignore the rest.) + */ + final Comparator<ShortcutInfo> mShortcutTypeRankAndTimeComparator = (ShortcutInfo a, + ShortcutInfo b) -> { + if (a.isDeclaredInManifest() && !b.isDeclaredInManifest()) { + return -1; + } + if (!a.isDeclaredInManifest() && b.isDeclaredInManifest()) { + return 1; + } + if (a.isDynamic() && b.isDynamic()) { + return Integer.compare(a.getRank(), b.getRank()); + } + if (a.isDynamic()) { + return -1; + } + if (b.isDynamic()) { + return 1; + } + if (a.isCached() && b.isCached()) { + // if both shortcuts are cached, prioritize shortcuts cached by people tile, + if (a.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE) + && !b.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)) { + return -1; + } else if (!a.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE) + && b.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)) { + return 1; + } + // followed by bubbles. + if (a.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES) + && !b.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)) { + return -1; + } else if (!a.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES) + && b.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)) { + return 1; + } + } + if (a.isCached()) { + return -1; + } + if (b.isCached()) { + return 1; + } + return Long.compare(b.getLastChangedTimestamp(), a.getLastChangedTimestamp()); + }; + + /** * Build a list of shortcuts for each target activity and return as a map. The result won't * contain "floating" shortcuts because they don't belong on any activities. */ diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 49831d75db49..7fc46fd6f757 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -181,6 +181,9 @@ public class ShortcutService extends IShortcutService.Stub { static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15; @VisibleForTesting + static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 100; + + @VisibleForTesting static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96; @VisibleForTesting @@ -257,6 +260,11 @@ public class ShortcutService extends IShortcutService.Stub { String KEY_MAX_SHORTCUTS = "max_shortcuts"; /** + * Key name for the max shortcuts can be retained in system ram per app. (int) + */ + String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app"; + + /** * Key name for icon compression quality, 0-100. */ String KEY_ICON_QUALITY = "icon_quality"; @@ -329,11 +337,16 @@ public class ShortcutService extends IShortcutService.Stub { new SparseArray<>(); /** - * Max number of dynamic + manifest shortcuts that each application can have at a time. + * Max number of dynamic + manifest shortcuts that each activity can have at a time. */ private int mMaxShortcuts; /** + * Max number of shortcuts that can exists in system ram for each application. + */ + private int mMaxShortcutsPerApp; + + /** * Max number of updating API calls that each application can make during the interval. */ int mMaxUpdatesPerInterval; @@ -807,6 +820,9 @@ public class ShortcutService extends IShortcutService.Stub { mMaxShortcuts = Math.max(0, (int) parser.getLong( ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY)); + mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong( + ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP)); + final int iconDimensionDp = Math.max(1, injectIsLowRamDevice() ? (int) parser.getLong( ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM, @@ -1759,6 +1775,13 @@ public class ShortcutService extends IShortcutService.Stub { } /** + * Return the max number of shortcuts can be retaiend in system ram for each application. + */ + int getMaxAppShortcuts() { + return mMaxShortcutsPerApp; + } + + /** * - Sends a notification to LauncherApps * - Write to file */ diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 8e9a21490645..41130812d658 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1577,9 +1577,8 @@ public class DisplayPolicy { applyKeyguardPolicy(win, imeTarget); // Check if the freeform window overlaps with the navigation bar area. - final boolean isOverlappingWithNavBar = isOverlappingWithNavBar(win); - if (isOverlappingWithNavBar && !mIsFreeformWindowOverlappingWithNavBar - && win.inFreeformWindowingMode()) { + if (!mIsFreeformWindowOverlappingWithNavBar && win.inFreeformWindowingMode() + && win.mActivityRecord != null && isOverlappingWithNavBar(win)) { mIsFreeformWindowOverlappingWithNavBar = true; } @@ -1637,7 +1636,7 @@ public class DisplayPolicy { // mode; if it's in gesture navigation mode, the navigation bar will be // NAV_BAR_FORCE_TRANSPARENT and its appearance won't be decided by overlapping // windows. - if (isOverlappingWithNavBar) { + if (isOverlappingWithNavBar(win)) { if (mNavBarColorWindowCandidate == null) { mNavBarColorWindowCandidate = win; addSystemBarColorApp(win); @@ -1665,7 +1664,7 @@ public class DisplayPolicy { addSystemBarColorApp(win); } } - if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) { + if (isOverlappingWithNavBar(win) && mNavBarColorWindowCandidate == null) { mNavBarColorWindowCandidate = win; } } @@ -2858,7 +2857,7 @@ public class DisplayPolicy { @VisibleForTesting static boolean isOverlappingWithNavBar(@NonNull WindowState win) { - if (win.mActivityRecord == null || !win.isVisible()) { + if (!win.isVisible()) { return false; } |