summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/Android.bp8
-rw-r--r--core/java/android/app/ActivityTransitionState.java10
-rw-r--r--core/java/android/app/SystemServiceRegistry.java11
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java16
-rw-r--r--core/java/android/view/ViewRootImpl.java2
-rw-r--r--core/java/android/widget/RemoteViews.java382
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java3
-rw-r--r--core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java6
-rw-r--r--libs/WindowManager/Jetpack/window-extensions-release.aarbin19210 -> 21364 bytes
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java96
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java174
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java23
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java39
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java23
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java72
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java2
-rw-r--r--libs/hwui/jni/android_graphics_Canvas.cpp28
-rw-r--r--packages/SettingsLib/res/values/strings.xml3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java7
-rw-r--r--packages/SystemUI/compose/core/Android.bp38
-rw-r--r--packages/SystemUI/compose/core/AndroidManifest.xml22
-rw-r--r--packages/SystemUI/compose/core/TEST_MAPPING37
-rw-r--r--packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiController.kt294
-rw-r--r--packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt74
-rw-r--r--packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt53
-rw-r--r--packages/SystemUI/compose/core/tests/Android.bp48
-rw-r--r--packages/SystemUI/compose/core/tests/AndroidManifest.xml28
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/systemui/compose/theme/SystemUIThemeTest.kt57
-rw-r--r--packages/SystemUI/compose/features/Android.bp40
-rw-r--r--packages/SystemUI/compose/features/AndroidManifest.xml22
-rw-r--r--packages/SystemUI/compose/features/TEST_MAPPING26
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt94
-rw-r--r--packages/SystemUI/compose/features/tests/Android.bp48
-rw-r--r--packages/SystemUI/compose/features/tests/AndroidManifest.xml28
-rw-r--r--packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt46
-rw-r--r--packages/SystemUI/compose/gallery/Android.bp72
-rw-r--r--packages/SystemUI/compose/gallery/AndroidManifest.xml55
-rw-r--r--packages/SystemUI/compose/gallery/TEST_MAPPING15
-rw-r--r--packages/SystemUI/compose/gallery/app/AndroidManifest.xml39
-rw-r--r--packages/SystemUI/compose/gallery/proguard-rules.pro21
-rw-r--r--packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml30
-rw-r--r--packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml170
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml5
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml5
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webpbin0 -> 1404 bytes
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webpbin0 -> 2898 bytes
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webpbin0 -> 982 bytes
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webpbin0 -> 1772 bytes
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webpbin0 -> 1900 bytes
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webpbin0 -> 3918 bytes
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webpbin0 -> 2884 bytes
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webpbin0 -> 5914 bytes
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webpbin0 -> 3844 bytes
-rw-r--r--packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webpbin0 -> 7778 bytes
-rw-r--r--packages/SystemUI/compose/gallery/res/values/colors.xml19
-rw-r--r--packages/SystemUI/compose/gallery/res/values/strings.xml20
-rw-r--r--packages/SystemUI/compose/gallery/res/values/themes.xml30
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt139
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt210
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt28
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt80
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt125
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt95
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt67
-rw-r--r--packages/SystemUI/compose/gallery/tests/Android.bp47
-rw-r--r--packages/SystemUI/compose/gallery/tests/AndroidManifest.xml28
-rw-r--r--packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt36
-rw-r--r--packages/SystemUI/compose/testing/Android.bp43
-rw-r--r--packages/SystemUI/compose/testing/AndroidManifest.xml25
-rw-r--r--packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt89
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogMessage.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumpable.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt)43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt105
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java62
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java101
-rw-r--r--services/Android.bp2
-rw-r--r--services/core/java/com/android/server/Watchdog.java1
-rw-r--r--services/core/java/com/android/server/pm/DeletePackageHelper.java12
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java5
-rw-r--r--services/java/com/android/server/SystemServer.java7
-rw-r--r--services/selectiontoolbar/Android.bp22
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt136
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java5
-rw-r--r--services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java9
159 files changed, 4346 insertions, 1029 deletions
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 77589a213e17..a7d4342be642 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -15,14 +15,6 @@ filegroup {
"**/*.java",
"**/*.aidl",
],
- exclude_srcs: [
- // Remove election toolbar code from build time
- "android/service/selectiontoolbar/*.aidl",
- "android/service/selectiontoolbar/*.java",
- "android/view/selectiontoolbar/*.aidl",
- "android/view/selectiontoolbar/*.java",
- "com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java",
- ],
visibility: ["//frameworks/base"],
}
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 57dacd024ba1..877e7d3b3bf7 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -263,11 +263,6 @@ class ActivityTransitionState {
// After orientation change, the onResume can come in before the top Activity has
// left, so if the Activity is not top, wait a second for the top Activity to exit.
if (mEnterTransitionCoordinator == null || activity.isTopOfTask()) {
- if (mEnterTransitionCoordinator != null) {
- mEnterTransitionCoordinator.runAfterTransitionsComplete(() -> {
- mEnterTransitionCoordinator = null;
- });
- }
restoreExitedViews();
restoreReenteringViews();
} else {
@@ -276,11 +271,6 @@ class ActivityTransitionState {
public void run() {
if (mEnterTransitionCoordinator == null ||
mEnterTransitionCoordinator.isWaitingForRemoteExit()) {
- if (mEnterTransitionCoordinator != null) {
- mEnterTransitionCoordinator.runAfterTransitionsComplete(() -> {
- mEnterTransitionCoordinator = null;
- });
- }
restoreExitedViews();
restoreReenteringViews();
} else if (mEnterTransitionCoordinator.isReturning()) {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 40192836e0a7..6615374f71ec 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -230,6 +230,8 @@ import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.IContentCaptureManager;
import android.view.displayhash.DisplayHashManager;
import android.view.inputmethod.InputMethodManager;
+import android.view.selectiontoolbar.ISelectionToolbarManager;
+import android.view.selectiontoolbar.SelectionToolbarManager;
import android.view.textclassifier.TextClassificationManager;
import android.view.textservice.TextServicesManager;
import android.view.translation.ITranslationManager;
@@ -363,6 +365,15 @@ public final class SystemServiceRegistry {
return new TextClassificationManager(ctx);
}});
+ registerService(Context.SELECTION_TOOLBAR_SERVICE, SelectionToolbarManager.class,
+ new CachedServiceFetcher<SelectionToolbarManager>() {
+ @Override
+ public SelectionToolbarManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.SELECTION_TOOLBAR_SERVICE);
+ return new SelectionToolbarManager(ctx.getOuterContext(),
+ ISelectionToolbarManager.Stub.asInterface(b));
+ }});
+
registerService(Context.FONT_SERVICE, FontManager.class,
new CachedServiceFetcher<FontManager>() {
@Override
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 8be2b4873c67..e3bca9c9aadb 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -103,7 +103,6 @@ public class AppWidgetHostView extends FrameLayout {
private boolean mOnLightBackground;
private SizeF mCurrentSize = null;
private RemoteViews.ColorResources mColorResources = null;
- private SparseIntArray mColorMapping = null;
// Stores the last remote views last inflated.
private RemoteViews mLastInflatedRemoteViews = null;
private long mLastInflatedRemoteViewsId = -1;
@@ -900,11 +899,19 @@ public class AppWidgetHostView extends FrameLayout {
* {@link android.R.color#system_neutral1_500}.
*/
public void setColorResources(@NonNull SparseIntArray colorMapping) {
- if (mColorMapping != null && isSameColorMapping(mColorMapping, colorMapping)) {
+ if (mColorResources != null
+ && isSameColorMapping(mColorResources.getColorMapping(), colorMapping)) {
return;
}
- mColorMapping = colorMapping.clone();
- mColorResources = RemoteViews.ColorResources.create(mContext, mColorMapping);
+ setColorResources(RemoteViews.ColorResources.create(mContext, colorMapping));
+ }
+
+ /** @hide **/
+ public void setColorResources(RemoteViews.ColorResources colorResources) {
+ if (colorResources == mColorResources) {
+ return;
+ }
+ mColorResources = colorResources;
mColorMappingChanged = true;
mViewMode = VIEW_MODE_NOINIT;
reapplyLastRemoteViews();
@@ -934,7 +941,6 @@ public class AppWidgetHostView extends FrameLayout {
public void resetColorResources() {
if (mColorResources != null) {
mColorResources = null;
- mColorMapping = null;
mColorMappingChanged = true;
mViewMode = VIEW_MODE_NOINIT;
reapplyLastRemoteViews();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7690af693148..fa37ca177736 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -276,7 +276,7 @@ public final class ViewRootImpl implements ViewParent,
* @hide
*/
public static final boolean CAPTION_ON_SHELL =
- SystemProperties.getBoolean("persist.debug.caption_on_shell", false);
+ SystemProperties.getBoolean("persist.wm.debug.caption_on_shell", false);
/**
* Whether the client should compute the window frame on its own.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 2879cd888d2d..a33906267736 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -594,8 +594,8 @@ public class RemoteViews implements Parcelable, Filter {
* SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
*/
private abstract static class Action implements Parcelable {
- public abstract void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) throws ActionException;
+ public abstract void apply(View root, ViewGroup rootParent, ActionApplyParams params)
+ throws ActionException;
public static final int MERGE_REPLACE = 0;
public static final int MERGE_APPEND = 1;
@@ -626,7 +626,7 @@ public class RemoteViews implements Parcelable, Filter {
* Override this if some of the tasks can be performed async.
*/
public Action initActionAsync(ViewTree root, ViewGroup rootParent,
- InteractionHandler handler, ColorResources colorResources) {
+ ActionApplyParams params) {
return this;
}
@@ -661,9 +661,7 @@ public class RemoteViews implements Parcelable, Filter {
// Constant used during async execution. It is not parcelable.
private static final Action ACTION_NOOP = new RuntimeAction() {
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
- }
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { }
};
/**
@@ -798,8 +796,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View view = root.findViewById(viewId);
if (!(view instanceof AdapterView<?>)) return;
@@ -834,8 +831,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, final InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -846,7 +842,7 @@ public class RemoteViews implements Parcelable, Filter {
OnItemClickListener listener = (parent, view, position, id) -> {
RemoteResponse response = findRemoteResponseTag(view);
if (response != null) {
- response.handleViewInteraction(view, handler);
+ response.handleViewInteraction(view, params.handler);
}
};
av.setOnItemClickListener(listener);
@@ -910,8 +906,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -935,7 +930,7 @@ public class RemoteViews implements Parcelable, Filter {
((RemoteViewsListAdapter) a).setViewsList(list);
} else {
v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount,
- colorResources));
+ params.colorResources));
}
} else if (target instanceof AdapterViewAnimator) {
AdapterViewAnimator v = (AdapterViewAnimator) target;
@@ -944,7 +939,7 @@ public class RemoteViews implements Parcelable, Filter {
((RemoteViewsListAdapter) a).setViewsList(list);
} else {
v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount,
- colorResources));
+ params.colorResources));
}
}
}
@@ -1025,8 +1020,8 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) throws ActionException {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
+ throws ActionException {
View target = root.findViewById(viewId);
if (target == null) return;
@@ -1053,7 +1048,7 @@ public class RemoteViews implements Parcelable, Filter {
&& adapter.getViewTypeCount() >= mItems.getViewTypeCount()) {
try {
((RemoteCollectionItemsAdapter) adapter).setData(
- mItems, handler, colorResources);
+ mItems, params.handler, params.colorResources);
} catch (Throwable throwable) {
// setData should never failed with the validation in the items builder, but if
// it does, catch and rethrow.
@@ -1063,8 +1058,8 @@ public class RemoteViews implements Parcelable, Filter {
}
try {
- adapterView.setAdapter(
- new RemoteCollectionItemsAdapter(mItems, handler, colorResources));
+ adapterView.setAdapter(new RemoteCollectionItemsAdapter(mItems,
+ params.handler, params.colorResources));
} catch (Throwable throwable) {
// This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
// a type error.
@@ -1095,8 +1090,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -1124,17 +1118,17 @@ public class RemoteViews implements Parcelable, Filter {
if (target instanceof AbsListView) {
AbsListView v = (AbsListView) target;
v.setRemoteViewsAdapter(intent, isAsync);
- v.setRemoteViewsInteractionHandler(handler);
+ v.setRemoteViewsInteractionHandler(params.handler);
} else if (target instanceof AdapterViewAnimator) {
AdapterViewAnimator v = (AdapterViewAnimator) target;
v.setRemoteViewsAdapter(intent, isAsync);
- v.setRemoteViewsOnClickHandler(handler);
+ v.setRemoteViewsOnClickHandler(params.handler);
}
}
@Override
public Action initActionAsync(ViewTree root, ViewGroup rootParent,
- InteractionHandler handler, ColorResources colorResources) {
+ ActionApplyParams params) {
SetRemoteViewsAdapterIntent copy = new SetRemoteViewsAdapterIntent(viewId, intent);
copy.isAsync = true;
return copy;
@@ -1173,8 +1167,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, final InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -1215,7 +1208,7 @@ public class RemoteViews implements Parcelable, Filter {
target.setTagInternal(com.android.internal.R.id.fillInIntent, null);
return;
}
- target.setOnClickListener(v -> mResponse.handleViewInteraction(v, handler));
+ target.setOnClickListener(v -> mResponse.handleViewInteraction(v, params.handler));
}
@Override
@@ -1253,8 +1246,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, final InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(viewId);
if (target == null) return;
if (!(target instanceof CompoundButton)) {
@@ -1287,7 +1279,7 @@ public class RemoteViews implements Parcelable, Filter {
}
OnCheckedChangeListener onCheckedChangeListener =
- (v, isChecked) -> mResponse.handleViewInteraction(v, handler);
+ (v, isChecked) -> mResponse.handleViewInteraction(v, params.handler);
button.setTagInternal(R.id.remote_checked_change_listener_tag, onCheckedChangeListener);
button.setOnCheckedChangeListener(onCheckedChangeListener);
}
@@ -1459,8 +1451,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -1517,8 +1508,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -1561,8 +1551,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View view = root.findViewById(viewId);
if (view == null) return;
@@ -1598,7 +1587,13 @@ public class RemoteViews implements Parcelable, Filter {
public BitmapCache(Parcel source) {
mBitmaps = source.createTypedArrayList(Bitmap.CREATOR);
- mBitmapHashes = source.readSparseIntArray();
+ mBitmapHashes = new SparseIntArray();
+ for (int i = 0; i < mBitmaps.size(); i++) {
+ Bitmap b = mBitmaps.get(i);
+ if (b != null) {
+ mBitmapHashes.put(b.hashCode(), i);
+ }
+ }
}
public int getBitmapId(Bitmap b) {
@@ -1614,7 +1609,7 @@ public class RemoteViews implements Parcelable, Filter {
b = b.asShared();
}
mBitmaps.add(b);
- mBitmapHashes.put(mBitmaps.size() - 1, hash);
+ mBitmapHashes.put(hash, mBitmaps.size() - 1);
mBitmapMemory = -1;
return (mBitmaps.size() - 1);
}
@@ -1631,7 +1626,6 @@ public class RemoteViews implements Parcelable, Filter {
public void writeBitmapsToParcel(Parcel dest, int flags) {
dest.writeTypedList(mBitmaps, flags);
- dest.writeSparseIntArray(mBitmapHashes);
}
public int getBitmapMemory() {
@@ -1675,12 +1669,12 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) throws ActionException {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
+ throws ActionException {
ReflectionAction ra = new ReflectionAction(viewId, methodName,
BaseReflectionAction.BITMAP,
bitmap);
- ra.apply(root, rootParent, handler, colorResources);
+ ra.apply(root, rootParent, params);
}
@Override
@@ -1756,8 +1750,7 @@ public class RemoteViews implements Parcelable, Filter {
protected abstract Object getParameterValue(@Nullable View view) throws ActionException;
@Override
- public final void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public final void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View view = root.findViewById(viewId);
if (view == null) return;
@@ -1775,7 +1768,7 @@ public class RemoteViews implements Parcelable, Filter {
@Override
public final Action initActionAsync(ViewTree root, ViewGroup rootParent,
- InteractionHandler handler, ColorResources colorResources) {
+ ActionApplyParams params) {
final View view = root.findViewById(viewId);
if (view == null) return ACTION_NOOP;
@@ -2307,8 +2300,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
mRunnable.run();
}
}
@@ -2421,8 +2413,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final Context context = root.getContext();
final ViewGroup target = root.findViewById(viewId);
@@ -2451,8 +2442,7 @@ public class RemoteViews implements Parcelable, Filter {
target.removeViews(nextChild, recycledViewIndex - nextChild);
}
setNextRecyclableChild(target, nextChild + 1, target.getChildCount());
- rvToApply.reapplyNestedViews(context, child, rootParent, handler,
- null /* size */, colorResources);
+ rvToApply.reapplyNestedViews(context, child, rootParent, params);
return;
}
// If we cannot recycle the views, we still remove all views in between to
@@ -2463,8 +2453,7 @@ public class RemoteViews implements Parcelable, Filter {
// If we cannot recycle, insert the new view before the next recyclable child.
// Inflate nested views and add as children
- View nestedView = rvToApply.applyNestedViews(context, target, rootParent, handler,
- null /* size */, colorResources);
+ View nestedView = rvToApply.apply(context, target, rootParent, null /* size */, params);
if (mStableId != NO_ID) {
setStableId(nestedView, mStableId);
}
@@ -2477,7 +2466,7 @@ public class RemoteViews implements Parcelable, Filter {
@Override
public Action initActionAsync(ViewTree root, ViewGroup rootParent,
- InteractionHandler handler, ColorResources colorResources) {
+ ActionApplyParams params) {
// In the async implementation, update the view tree so that subsequent calls to
// findViewById return the current view.
root.createTree();
@@ -2511,8 +2500,7 @@ public class RemoteViews implements Parcelable, Filter {
setNextRecyclableChild(targetVg, nextChild + 1, target.mChildren.size());
final AsyncApplyTask reapplyTask = rvToApply.getInternalAsyncApplyTask(
context,
- targetVg, null /* listener */, handler, null /* size */,
- colorResources,
+ targetVg, null /* listener */, params, null /* size */,
recycled.mRoot);
final ViewTree tree = reapplyTask.doInBackground();
if (tree == null) {
@@ -2521,8 +2509,7 @@ public class RemoteViews implements Parcelable, Filter {
return new RuntimeAction() {
@Override
public void apply(View root, ViewGroup rootParent,
- InteractionHandler handler, ColorResources colorResources)
- throws ActionException {
+ ActionApplyParams params) throws ActionException {
reapplyTask.onPostExecute(tree);
if (recycledViewIndex > nextChild) {
targetVg.removeViews(nextChild, recycledViewIndex - nextChild);
@@ -2533,23 +2520,22 @@ public class RemoteViews implements Parcelable, Filter {
// If the layout id is different, still remove the children as if we recycled
// the view, to insert at the same place.
target.removeChildren(nextChild, recycledViewIndex - nextChild + 1);
- return insertNewView(context, target, handler, colorResources,
+ return insertNewView(context, target, params,
() -> targetVg.removeViews(nextChild,
recycledViewIndex - nextChild + 1));
}
}
// If we cannot recycle, simply add the view at the same available slot.
- return insertNewView(context, target, handler, colorResources, () -> {});
+ return insertNewView(context, target, params, () -> {});
}
- private Action insertNewView(Context context, ViewTree target, InteractionHandler handler,
- ColorResources colorResources, Runnable finalizeAction) {
+ private Action insertNewView(Context context, ViewTree target,
+ ActionApplyParams params, Runnable finalizeAction) {
ViewGroup targetVg = (ViewGroup) target.mRoot;
int nextChild = getNextRecyclableChild(targetVg);
final AsyncApplyTask task = mNestedViews.getInternalAsyncApplyTask(context, targetVg,
- null /* listener */, handler, null /* size */, colorResources,
- null /* result */);
+ null /* listener */, params, null /* size */, null /* result */);
final ViewTree tree = task.doInBackground();
if (tree == null) {
@@ -2569,8 +2555,7 @@ public class RemoteViews implements Parcelable, Filter {
return new RuntimeAction() {
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) throws ActionException {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
task.onPostExecute(tree);
finalizeAction.run();
targetVg.addView(task.mResult, insertIndex);
@@ -2627,8 +2612,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final ViewGroup target = root.findViewById(viewId);
if (target == null) {
@@ -2652,7 +2636,7 @@ public class RemoteViews implements Parcelable, Filter {
@Override
public Action initActionAsync(ViewTree root, ViewGroup rootParent,
- InteractionHandler handler, ColorResources colorResources) {
+ ActionApplyParams params) {
// In the async implementation, update the view tree so that subsequent calls to
// findViewById return the current view.
root.createTree();
@@ -2676,8 +2660,7 @@ public class RemoteViews implements Parcelable, Filter {
}
return new RuntimeAction() {
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) throws ActionException {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
for (int i = targetVg.getChildCount() - 1; i >= 0; i--) {
if (!hasStableId(targetVg.getChildAt(i))) {
@@ -2736,8 +2719,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(viewId);
if (target == null || target == root) {
@@ -2752,7 +2734,7 @@ public class RemoteViews implements Parcelable, Filter {
@Override
public Action initActionAsync(ViewTree root, ViewGroup rootParent,
- InteractionHandler handler, ColorResources colorResources) {
+ ActionApplyParams params) {
// In the async implementation, update the view tree so that subsequent calls to
// findViewById return the correct view.
root.createTree();
@@ -2771,8 +2753,7 @@ public class RemoteViews implements Parcelable, Filter {
parent.mChildren.remove(target);
return new RuntimeAction() {
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) throws ActionException {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
parentVg.removeView(target.mRoot);
}
};
@@ -2851,8 +2832,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final TextView target = root.findViewById(viewId);
if (target == null) return;
if (drawablesLoaded) {
@@ -2883,7 +2863,7 @@ public class RemoteViews implements Parcelable, Filter {
@Override
public Action initActionAsync(ViewTree root, ViewGroup rootParent,
- InteractionHandler handler, ColorResources colorResources) {
+ ActionApplyParams params) {
final TextView target = root.findViewById(viewId);
if (target == null) return ACTION_NOOP;
@@ -2961,8 +2941,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final TextView target = root.findViewById(viewId);
if (target == null) return;
target.setTextSize(units, size);
@@ -3007,8 +2986,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(viewId);
if (target == null) return;
target.setPadding(left, top, right, bottom);
@@ -3084,8 +3062,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(viewId);
if (target == null) {
return;
@@ -3230,8 +3207,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -3266,8 +3242,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
// Let's traverse the viewtree and override all textColors!
Stack<View> viewsToProcess = new Stack<>();
viewsToProcess.add(root);
@@ -3317,8 +3292,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
final View target = root.findViewById(mViewId);
if (target == null) return;
@@ -3352,8 +3326,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources)
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
throws ActionException {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -3404,8 +3377,8 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) throws ActionException {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
+ throws ActionException {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -3483,8 +3456,8 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
- ColorResources colorResources) throws ActionException {
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
+ throws ActionException {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -5578,54 +5551,41 @@ public class RemoteViews implements Parcelable, Filter {
/** @hide */
public View apply(@NonNull Context context, @NonNull ViewGroup parent,
@Nullable InteractionHandler handler, @Nullable SizeF size) {
- RemoteViews rvToApply = getRemoteViewsToApply(context, size);
-
- View result = inflateView(context, rvToApply, parent);
- rvToApply.performApply(result, parent, handler, null);
- return result;
+ return apply(context, parent, size, new ActionApplyParams()
+ .withInteractionHandler(handler));
}
/** @hide */
public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
@Nullable InteractionHandler handler, @StyleRes int applyThemeResId) {
- return applyWithTheme(context, parent, handler, applyThemeResId, null);
- }
-
- /** @hide */
- public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
- @Nullable InteractionHandler handler, @StyleRes int applyThemeResId,
- @Nullable SizeF size) {
- RemoteViews rvToApply = getRemoteViewsToApply(context, size);
-
- View result = inflateView(context, rvToApply, parent, applyThemeResId, null);
- rvToApply.performApply(result, parent, handler, null);
- return result;
+ return apply(context, parent, null, new ActionApplyParams()
+ .withInteractionHandler(handler)
+ .withThemeResId(applyThemeResId));
}
/** @hide */
public View apply(Context context, ViewGroup parent, InteractionHandler handler,
@Nullable SizeF size, @Nullable ColorResources colorResources) {
- RemoteViews rvToApply = getRemoteViewsToApply(context, size);
+ return apply(context, parent, size, new ActionApplyParams()
+ .withInteractionHandler(handler)
+ .withColorResources(colorResources));
+ }
- View result = inflateView(context, rvToApply, parent, 0, colorResources);
- rvToApply.performApply(result, parent, handler, colorResources);
- return result;
+ /** @hide **/
+ public View apply(Context context, ViewGroup parent, @Nullable SizeF size,
+ ActionApplyParams params) {
+ return apply(context, parent, parent, size, params);
}
- private View applyNestedViews(Context context, ViewGroup directParent,
- ViewGroup rootParent, InteractionHandler handler, SizeF size,
- ColorResources colorResources) {
+ private View apply(Context context, ViewGroup directParent, ViewGroup rootParent,
+ @Nullable SizeF size, ActionApplyParams params) {
RemoteViews rvToApply = getRemoteViewsToApply(context, size);
-
- View result = inflateView(context, rvToApply, directParent, 0, colorResources);
- rvToApply.performApply(result, rootParent, handler, colorResources);
+ View result = inflateView(context, rvToApply, directParent,
+ params.applyThemeResId, params.colorResources);
+ rvToApply.performApply(result, rootParent, params);
return result;
}
- private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
- return inflateView(context, rv, parent, 0, null);
- }
-
private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent,
@StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
// RemoteViews may be built by an application installed in another
@@ -5704,7 +5664,6 @@ public class RemoteViews implements Parcelable, Filter {
return applyAsync(context, parent, executor, listener, null /* handler */);
}
-
/** @hide */
public CancellationSignal applyAsync(Context context, ViewGroup parent,
Executor executor, OnViewAppliedListener listener, InteractionHandler handler) {
@@ -5723,16 +5682,19 @@ public class RemoteViews implements Parcelable, Filter {
public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor,
OnViewAppliedListener listener, InteractionHandler handler, SizeF size,
ColorResources colorResources) {
+
+ ActionApplyParams params = new ActionApplyParams()
+ .withInteractionHandler(handler)
+ .withColorResources(colorResources)
+ .withExecutor(executor);
return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener,
- handler, colorResources, null /* result */,
- true /* topLevel */).startTaskOnExecutor(executor);
+ params, null /* result */, true /* topLevel */).startTaskOnExecutor(executor);
}
private AsyncApplyTask getInternalAsyncApplyTask(Context context, ViewGroup parent,
- OnViewAppliedListener listener, InteractionHandler handler, SizeF size,
- ColorResources colorResources, View result) {
+ OnViewAppliedListener listener, ActionApplyParams params, SizeF size, View result) {
return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener,
- handler, colorResources, result, false /* topLevel */);
+ params, result, false /* topLevel */);
}
private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree>
@@ -5742,8 +5704,8 @@ public class RemoteViews implements Parcelable, Filter {
final ViewGroup mParent;
final Context mContext;
final OnViewAppliedListener mListener;
- final InteractionHandler mHandler;
- final ColorResources mColorResources;
+ final ActionApplyParams mApplyParams;
+
/**
* Whether the remote view is the top-level one (i.e. not within an action).
*
@@ -5758,16 +5720,13 @@ public class RemoteViews implements Parcelable, Filter {
private AsyncApplyTask(
RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener,
- InteractionHandler handler, ColorResources colorResources,
- View result, boolean topLevel) {
+ ActionApplyParams applyParams, View result, boolean topLevel) {
mRV = rv;
mParent = parent;
mContext = context;
mListener = listener;
- mColorResources = colorResources;
- mHandler = handler;
mTopLevel = topLevel;
-
+ mApplyParams = applyParams;
mResult = result;
}
@@ -5776,17 +5735,18 @@ public class RemoteViews implements Parcelable, Filter {
protected ViewTree doInBackground(Void... params) {
try {
if (mResult == null) {
- mResult = inflateView(mContext, mRV, mParent, 0, mColorResources);
+ mResult = inflateView(mContext, mRV, mParent, 0, mApplyParams.colorResources);
}
mTree = new ViewTree(mResult);
+
if (mRV.mActions != null) {
int count = mRV.mActions.size();
mActions = new Action[count];
for (int i = 0; i < count && !isCancelled(); i++) {
// TODO: check if isCancelled in nested views.
- mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler,
- mColorResources);
+ mActions[i] = mRV.mActions.get(i)
+ .initActionAsync(mTree, mParent, mApplyParams);
}
} else {
mActions = null;
@@ -5808,10 +5768,13 @@ public class RemoteViews implements Parcelable, Filter {
try {
if (mActions != null) {
- InteractionHandler handler = mHandler == null
- ? DEFAULT_INTERACTION_HANDLER : mHandler;
+
+ ActionApplyParams applyParams = mApplyParams.clone();
+ if (applyParams.handler == null) {
+ applyParams.handler = DEFAULT_INTERACTION_HANDLER;
+ }
for (Action a : mActions) {
- a.apply(viewTree.mRoot, mParent, handler, mColorResources);
+ a.apply(viewTree.mRoot, mParent, applyParams);
}
}
// If the parent of the view is has is a root, resolve the recycling.
@@ -5859,18 +5822,43 @@ public class RemoteViews implements Parcelable, Filter {
* the {@link #apply(Context,ViewGroup)} call.
*/
public void reapply(Context context, View v) {
- reapply(context, v, null /* handler */);
+ reapply(context, v, null /* size */, new ActionApplyParams());
}
/** @hide */
public void reapply(Context context, View v, InteractionHandler handler) {
- reapply(context, v, handler, null /* size */, null /* colorResources */);
+ reapply(context, v, null /* size */,
+ new ActionApplyParams().withInteractionHandler(handler));
}
/** @hide */
public void reapply(Context context, View v, InteractionHandler handler, SizeF size,
ColorResources colorResources) {
- reapply(context, v, handler, size, colorResources, true);
+ reapply(context, v, size, new ActionApplyParams()
+ .withInteractionHandler(handler).withColorResources(colorResources));
+ }
+
+ /** @hide */
+ public void reapply(Context context, View v, @Nullable SizeF size, ActionApplyParams params) {
+ reapply(context, v, (ViewGroup) v.getParent(), size, params, true);
+ }
+
+ private void reapplyNestedViews(Context context, View v, ViewGroup rootParent,
+ ActionApplyParams params) {
+ reapply(context, v, rootParent, null, params, false);
+ }
+
+ // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls
+ // should set it to false.
+ private void reapply(Context context, View v, ViewGroup rootParent,
+ @Nullable SizeF size, ActionApplyParams params, boolean topLevel) {
+ RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size);
+ rvToApply.performApply(v, rootParent, params);
+
+ // If the parent of the view is has is a root, resolve the recycling.
+ if (topLevel && v instanceof ViewGroup) {
+ finalizeViewRecycling((ViewGroup) v);
+ }
}
/** @hide */
@@ -5922,27 +5910,6 @@ public class RemoteViews implements Parcelable, Filter {
return rvToApply;
}
- // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls
- // should set it to false.
- private void reapply(Context context, View v, InteractionHandler handler, SizeF size,
- ColorResources colorResources, boolean topLevel) {
-
- RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size);
-
- rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources);
-
- // If the parent of the view is has is a root, resolve the recycling.
- if (topLevel && v instanceof ViewGroup) {
- finalizeViewRecycling((ViewGroup) v);
- }
- }
-
- private void reapplyNestedViews(Context context, View v, ViewGroup rootParent,
- InteractionHandler handler, SizeF size, ColorResources colorResources) {
- RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size);
- rvToApply.performApply(v, rootParent, handler, colorResources);
- }
-
/**
* Applies all the actions to the provided view, moving as much of the task on the background
* thread as possible.
@@ -5973,19 +5940,25 @@ public class RemoteViews implements Parcelable, Filter {
ColorResources colorResources) {
RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size);
+ ActionApplyParams params = new ActionApplyParams()
+ .withColorResources(colorResources)
+ .withInteractionHandler(handler)
+ .withExecutor(executor);
+
return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
- context, listener, handler, colorResources, v, true /* topLevel */)
+ context, listener, params, v, true /* topLevel */)
.startTaskOnExecutor(executor);
}
- private void performApply(View v, ViewGroup parent, InteractionHandler handler,
- ColorResources colorResources) {
+ private void performApply(View v, ViewGroup parent, ActionApplyParams params) {
+ params = params.clone();
+ if (params.handler == null) {
+ params.handler = DEFAULT_INTERACTION_HANDLER;
+ }
if (mActions != null) {
- handler = handler == null ? DEFAULT_INTERACTION_HANDLER : handler;
final int count = mActions.size();
for (int i = 0; i < count; i++) {
- Action a = mActions.get(i);
- a.apply(v, parent, handler, colorResources);
+ mActions.get(i).apply(v, parent, params);
}
}
}
@@ -6043,6 +6016,47 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Utility class to hold all the options when applying the remote views
+ * @hide
+ */
+ public class ActionApplyParams {
+
+ public InteractionHandler handler;
+ public ColorResources colorResources;
+ public Executor executor;
+ @StyleRes public int applyThemeResId;
+
+ @Override
+ public ActionApplyParams clone() {
+ return new ActionApplyParams()
+ .withInteractionHandler(handler)
+ .withColorResources(colorResources)
+ .withExecutor(executor)
+ .withThemeResId(applyThemeResId);
+ }
+
+ public ActionApplyParams withInteractionHandler(InteractionHandler handler) {
+ this.handler = handler;
+ return this;
+ }
+
+ public ActionApplyParams withColorResources(ColorResources colorResources) {
+ this.colorResources = colorResources;
+ return this;
+ }
+
+ public ActionApplyParams withThemeResId(@StyleRes int themeResId) {
+ this.applyThemeResId = themeResId;
+ return this;
+ }
+
+ public ActionApplyParams withExecutor(Executor executor) {
+ this.executor = executor;
+ return this;
+ }
+ }
+
+ /**
* Object allowing the modification of a context to overload the system's dynamic colors.
*
* Only colors from {@link android.R.color#system_accent1_0} to
@@ -6056,10 +6070,12 @@ public class RemoteViews implements Parcelable, Filter {
// Size, in bytes, of an entry in the array of colors in an ARSC file.
private static final int ARSC_ENTRY_SIZE = 16;
- private ResourcesLoader mLoader;
+ private final ResourcesLoader mLoader;
+ private final SparseIntArray mColorMapping;
- private ColorResources(ResourcesLoader loader) {
+ private ColorResources(ResourcesLoader loader, SparseIntArray colorMapping) {
mLoader = loader;
+ mColorMapping = colorMapping;
}
/**
@@ -6071,6 +6087,10 @@ public class RemoteViews implements Parcelable, Filter {
context.getResources().addLoaders(mLoader);
}
+ public SparseIntArray getColorMapping() {
+ return mColorMapping;
+ }
+
private static ByteArrayOutputStream readFileContent(InputStream input) throws IOException {
ByteArrayOutputStream content = new ByteArrayOutputStream(2048);
byte[] buffer = new byte[4096];
@@ -6145,7 +6165,7 @@ public class RemoteViews implements Parcelable, Filter {
ResourcesLoader colorsLoader = new ResourcesLoader();
colorsLoader.addProvider(ResourcesProvider
.loadFromTable(pfd, null /* assetsProvider */));
- return new ColorResources(colorsLoader);
+ return new ColorResources(colorsLoader, colorMapping.clone());
}
}
} finally {
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 1d396be6b478..0730f3ddf8ac 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -325,7 +325,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
public boolean checkApplicationCallbackRegistration(int priority,
OnBackInvokedCallback callback) {
if (!mApplicationCallBackEnabled
- && !(callback instanceof CompatOnBackInvokedCallback)) {
+ && !(callback instanceof CompatOnBackInvokedCallback)
+ && !ALWAYS_ENFORCE_PREDICTIVE_BACK) {
Log.w("OnBackInvokedCallback",
"OnBackInvokedCallback is not enabled for the application."
+ "\nSet 'android:enableOnBackInvokedCallback=\"true\"' in the"
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
index c484525dbe20..f7af67b3b2a8 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.graphics.Rect;
import android.view.MenuItem;
import android.view.View;
+import android.view.selectiontoolbar.SelectionToolbarManager;
import android.widget.PopupWindow;
import java.util.List;
@@ -92,7 +93,10 @@ public interface FloatingToolbarPopup {
* enabled, otherwise returns {@link LocalFloatingToolbarPopup} implementation.
*/
static FloatingToolbarPopup createInstance(Context context, View parent) {
- return new LocalFloatingToolbarPopup(context, parent);
+ boolean enabled = SelectionToolbarManager.isRemoteSelectionToolbarEnabled(context);
+ return enabled
+ ? new RemoteFloatingToolbarPopup(context, parent)
+ : new LocalFloatingToolbarPopup(context, parent);
}
}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index f54ab08d8a8a..918e514f4c89 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 5cba3b4ec130..6ae0f9ba0485 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -55,6 +55,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -72,6 +73,7 @@ import java.util.function.Consumer;
*/
public class ShellTaskOrganizer extends TaskOrganizer implements
CompatUIController.CompatUICallback {
+ private static final String TAG = "ShellTaskOrganizer";
// Intentionally using negative numbers here so the positive numbers can be used
// for task id specific listeners that will be added later.
@@ -90,8 +92,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
})
public @interface TaskListenerType {}
- private static final String TAG = "ShellTaskOrganizer";
-
/**
* Callbacks for when the tasks change in the system.
*/
@@ -177,6 +177,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
@Nullable
private final CompatUIController mCompatUI;
+ @NonNull
+ private final ShellCommandHandler mShellCommandHandler;
+
@Nullable
private final Optional<RecentTasksController> mRecentTasks;
@@ -187,29 +190,33 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
private RunningTaskInfo mLastFocusedTaskInfo;
public ShellTaskOrganizer(ShellExecutor mainExecutor) {
- this(null /* shellInit */, null /* taskOrganizerController */, null /* compatUI */,
+ this(null /* shellInit */, null /* shellCommandHandler */,
+ null /* taskOrganizerController */, null /* compatUI */,
Optional.empty() /* unfoldAnimationController */,
Optional.empty() /* recentTasksController */,
mainExecutor);
}
public ShellTaskOrganizer(ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
@Nullable CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
- this(shellInit, null /* taskOrganizerController */, compatUI,
+ this(shellInit, shellCommandHandler, null /* taskOrganizerController */, compatUI,
unfoldAnimationController, recentTasks, mainExecutor);
}
@VisibleForTesting
protected ShellTaskOrganizer(ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ITaskOrganizerController taskOrganizerController,
@Nullable CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
super(taskOrganizerController, mainExecutor);
+ mShellCommandHandler = shellCommandHandler;
mCompatUI = compatUI;
mRecentTasks = recentTasks;
mUnfoldAnimationController = unfoldAnimationController.orElse(null);
@@ -219,6 +226,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
if (mCompatUI != null) {
mCompatUI.setCompatUICallback(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 05fafc54c273..131afafc32c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -57,6 +57,7 @@ import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -90,7 +91,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
* Raw delta between {@link #mInitTouchLocation} and the last touch location.
*/
private final Point mTouchEventDelta = new Point();
- private final ShellExecutor mShellExecutor;
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
@@ -105,6 +105,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private final SurfaceControl.Transaction mTransaction;
private final IActivityTaskManager mActivityTaskManager;
private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private final ShellExecutor mShellExecutor;
+ private final Handler mBgHandler;
@Nullable
private IOnBackInvokedCallback mBackToLauncherCallback;
private float mTriggerThreshold;
@@ -133,16 +136,19 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
};
public BackAnimationController(
+ @NonNull ShellInit shellInit,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context) {
- this(shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
+ this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
ActivityTaskManager.getService(), context, context.getContentResolver());
}
@VisibleForTesting
- BackAnimationController(@NonNull @ShellMainThread ShellExecutor shellExecutor,
- @NonNull @ShellBackgroundThread Handler handler,
+ BackAnimationController(
+ @NonNull ShellInit shellInit,
+ @NonNull @ShellMainThread ShellExecutor shellExecutor,
+ @NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull SurfaceControl.Transaction transaction,
@NonNull IActivityTaskManager activityTaskManager,
Context context, ContentResolver contentResolver) {
@@ -150,7 +156,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mTransaction = transaction;
mActivityTaskManager = activityTaskManager;
mContext = context;
- setupAnimationDeveloperSettingsObserver(contentResolver, handler);
+ mContentResolver = contentResolver;
+ mBgHandler = bgHandler;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
}
private void setupAnimationDeveloperSettingsObserver(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 31fc6a5be589..2c02006c8ca5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -452,6 +452,7 @@ public class Bubble implements BubbleViewProvider {
*/
void setEntry(@NonNull final BubbleEntry entry) {
Objects.requireNonNull(entry);
+ boolean showingDotPreviously = showDot();
mLastUpdated = entry.getStatusBarNotification().getPostTime();
mIsBubble = entry.getStatusBarNotification().getNotification().isBubbleNotification();
mPackageName = entry.getStatusBarNotification().getPackageName();
@@ -498,6 +499,10 @@ public class Bubble implements BubbleViewProvider {
mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
mShouldSuppressPeek = entry.shouldSuppressPeek();
+ if (showingDotPreviously != showDot()) {
+ // This will update the UI if needed
+ setShowDot(showDot());
+ }
}
@Nullable
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 398261600dc7..8771ceb71d98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1055,18 +1055,28 @@ public class BubbleController implements ConfigurationChangeListener {
public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) {
// If this is an interruptive notif, mark that it's interrupted
mSysuiProxy.setNotificationInterruption(notif.getKey());
- if (!notif.getRanking().isTextChanged()
+ boolean isNonInterruptiveNotExpanding = !notif.getRanking().isTextChanged()
&& (notif.getBubbleMetadata() != null
- && !notif.getBubbleMetadata().getAutoExpandBubble())
+ && !notif.getBubbleMetadata().getAutoExpandBubble());
+ if (isNonInterruptiveNotExpanding
&& mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
// Update the bubble but don't promote it out of overflow
Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey());
- b.setEntry(notif);
+ if (notif.isBubble()) {
+ notif.setFlagBubble(false);
+ }
+ updateNotNotifyingEntry(b, notif, showInShade);
+ } else if (mBubbleData.hasAnyBubbleWithKey(notif.getKey())
+ && isNonInterruptiveNotExpanding) {
+ Bubble b = mBubbleData.getAnyBubbleWithkey(notif.getKey());
+ if (b != null) {
+ updateNotNotifyingEntry(b, notif, showInShade);
+ }
} else if (mBubbleData.isSuppressedWithLocusId(notif.getLocusId())) {
// Update the bubble but don't promote it out of overflow
Bubble b = mBubbleData.getSuppressedBubbleWithKey(notif.getKey());
if (b != null) {
- b.setEntry(notif);
+ updateNotNotifyingEntry(b, notif, showInShade);
}
} else {
Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
@@ -1076,13 +1086,25 @@ public class BubbleController implements ConfigurationChangeListener {
if (bubble.shouldAutoExpand()) {
bubble.setShouldAutoExpand(false);
}
+ mImpl.mCachedState.updateBubbleSuppressedState(bubble);
} else {
inflateAndAdd(bubble, suppressFlyout, showInShade);
}
}
}
- void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
+ void updateNotNotifyingEntry(Bubble b, BubbleEntry entry, boolean showInShade) {
+ boolean isBubbleSelected = Objects.equals(b, mBubbleData.getSelectedBubble());
+ boolean isBubbleExpandedAndSelected = isStackExpanded() && isBubbleSelected;
+ b.setEntry(entry);
+ boolean suppress = isBubbleExpandedAndSelected || !showInShade || !b.showInShade();
+ b.setSuppressNotification(suppress);
+ b.setShowDot(!isBubbleExpandedAndSelected);
+ mImpl.mCachedState.updateBubbleSuppressedState(b);
+ }
+
+ @VisibleForTesting
+ public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
// Lazy init stack view when a bubble is created
ensureStackViewCreated();
bubble.setInflateSynchronously(mInflateSynchronously);
@@ -1111,7 +1133,10 @@ public class BubbleController implements ConfigurationChangeListener {
}
@VisibleForTesting
- public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
+ public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem) {
+ if (!fromSystem) {
+ return;
+ }
// shouldBubbleUp checks canBubble & for bubble metadata
boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry);
if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
@@ -1172,9 +1197,9 @@ public class BubbleController implements ConfigurationChangeListener {
// notification, so that the bubble will be re-created if shouldBubbleUp returns
// true.
mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP);
- } else if (entry != null && mTmpRanking.isBubble() && !isActive) {
+ } else if (entry != null && mTmpRanking.isBubble() && !isActiveOrInOverflow) {
entry.setFlagBubble(true);
- onEntryUpdated(entry, shouldBubbleUp);
+ onEntryUpdated(entry, shouldBubbleUp, /* fromSystem= */ true);
}
}
}
@@ -1773,9 +1798,9 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
- public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
+ public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem) {
mMainExecutor.execute(() -> {
- BubbleController.this.onEntryUpdated(entry, shouldBubbleUp);
+ BubbleController.this.onEntryUpdated(entry, shouldBubbleUp, fromSystem);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index cf792cda91b5..37b96ffe5cd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -171,8 +171,9 @@ public interface Bubbles {
*
* @param entry the {@link BubbleEntry} by the notification.
* @param shouldBubbleUp {@code true} if this notification should bubble up.
+ * @param fromSystem {@code true} if this update is from NotificationManagerService.
*/
- void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp);
+ void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem);
/**
* Called when new notification entry removed.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index db8d9d423aca..235fd9c469ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -43,6 +43,7 @@ import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.lang.ref.WeakReference;
@@ -119,6 +120,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
private boolean mKeyguardShowing;
public CompatUIController(Context context,
+ ShellInit shellInit,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -134,10 +136,14 @@ public class CompatUIController implements OnDisplaysChangedListener,
mSyncQueue = syncQueue;
mMainExecutor = mainExecutor;
mTransitionsLazy = transitionsLazy;
+ mCompatUIHintsState = new CompatUIHintsState();
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellController.addKeyguardChangeListener(this);
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
- mCompatUIHintsState = new CompatUIHintsState();
- shellController.addKeyguardChangeListener(this);
}
/** Sets the callback for UI interactions. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 3add4179ef82..bbaf51f7c54c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -174,13 +174,14 @@ public abstract class WMShellBaseModule {
@Provides
static ShellTaskOrganizer provideShellTaskOrganizer(
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new ShellTaskOrganizer(shellInit, compatUI, unfoldAnimationController,
- recentTasksOptional, mainExecutor);
+ return new ShellTaskOrganizer(shellInit, shellCommandHandler, compatUI,
+ unfoldAnimationController, recentTasksOptional, mainExecutor);
}
@WMSingleton
@@ -188,6 +189,7 @@ public abstract class WMShellBaseModule {
static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -196,19 +198,20 @@ public abstract class WMShellBaseModule {
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler
) {
- return new KidsModeTaskOrganizer(context, shellInit, syncTransactionQueue,
- displayController, displayInsetsController, unfoldAnimationController,
- recentTasksOptional, mainExecutor, mainHandler);
+ return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
+ syncTransactionQueue, displayController, displayInsetsController,
+ unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
}
@WMSingleton
@Provides
static CompatUIController provideCompatUIController(Context context,
+ ShellInit shellInit,
ShellController shellController,
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
@ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
- return new CompatUIController(context, shellController, displayController,
+ return new CompatUIController(context, shellInit, shellController, displayController,
displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy);
}
@@ -268,12 +271,14 @@ public abstract class WMShellBaseModule {
@Provides
static Optional<BackAnimationController> provideBackAnimationController(
Context context,
+ ShellInit shellInit,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
- new BackAnimationController(shellExecutor, backgroundHandler, context));
+ new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
+ context));
}
return Optional.empty();
}
@@ -384,11 +389,14 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
- ShellController shellController, DisplayController displayController,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(
- HideDisplayCutoutController.create(context, shellController, displayController,
- mainExecutor));
+ HideDisplayCutoutController.create(context, shellInit, shellCommandHandler,
+ shellController, displayController, mainExecutor));
}
//
@@ -466,12 +474,13 @@ public abstract class WMShellBaseModule {
static Optional<RecentTasksController> provideRecentTasksController(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
- RecentTasksController.create(context, shellInit, taskStackListener,
- mainExecutor));
+ RecentTasksController.create(context, shellInit, shellCommandHandler,
+ taskStackListener, mainExecutor));
}
//
@@ -649,8 +658,9 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static ShellController provideShellController(ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellController(shellInit, mainExecutor);
+ return new ShellController(shellInit, shellCommandHandler, mainExecutor);
}
//
@@ -676,12 +686,15 @@ public abstract class WMShellBaseModule {
KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
+ Optional<Pip> pipOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformComponents> freeformComponents,
Optional<RecentTasksController> recentTasksOptional,
+ Optional<OneHandedController> oneHandedControllerOptional,
+ Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional,
ActivityEmbeddingController activityEmbeddingOptional,
Transitions transitions,
StartingWindowController startingWindow,
@@ -697,18 +710,7 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static ShellCommandHandler provideShellCommandHandlerImpl(
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
- Optional<OneHandedController> oneHandedOptional,
- Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<RecentTasksController> recentTasksOptional,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new ShellCommandHandler(shellController, shellTaskOrganizer,
- kidsModeTaskOrganizer, splitScreenOptional, pipOptional, oneHandedOptional,
- hideDisplayCutout, recentTasksOptional, mainExecutor);
+ static ShellCommandHandler provideShellCommandHandler() {
+ return new ShellCommandHandler();
}
}
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 d53451aa6034..2ca9c3be8a69 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
@@ -74,6 +74,7 @@ import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.SplitscreenPipMixedHandler;
@@ -248,14 +249,20 @@ public abstract class WMShellModule {
@Provides
@DynamicOverride
static OneHandedController provideOneHandedController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
- WindowManager windowManager, DisplayController displayController,
- DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
- UiEventLogger uiEventLogger, InteractionJankMonitor jankMonitor,
- @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) {
- return OneHandedController.create(context, shellController, windowManager,
- displayController, displayLayout, taskStackListener, jankMonitor, uiEventLogger,
- mainExecutor, mainHandler);
+ WindowManager windowManager,
+ DisplayController displayController,
+ DisplayLayout displayLayout,
+ TaskStackListenerImpl taskStackListener,
+ UiEventLogger uiEventLogger,
+ InteractionJankMonitor jankMonitor,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
+ return OneHandedController.create(context, shellInit, shellCommandHandler, shellController,
+ windowManager, displayController, displayLayout, taskStackListener, jankMonitor,
+ uiEventLogger, mainExecutor, mainHandler);
}
//
@@ -268,6 +275,7 @@ public abstract class WMShellModule {
static SplitScreenController provideSplitScreenController(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
@@ -281,10 +289,10 @@ public abstract class WMShellModule {
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
@ShellMainThread ShellExecutor mainExecutor) {
- return new SplitScreenController(context, shellInit, shellController, shellTaskOrganizer,
- syncQueue, rootTaskDisplayAreaOrganizer, displayController, displayImeController,
- displayInsetsController, dragAndDropController, transitions, transactionPool,
- iconProvider, recentTasks, mainExecutor);
+ return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
+ shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
+ displayImeController, displayInsetsController, dragAndDropController, transitions,
+ transactionPool, iconProvider, recentTasks, mainExecutor);
}
//
@@ -294,24 +302,33 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static Optional<Pip> providePip(Context context,
- ShellController shellController, DisplayController displayController,
- PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
- PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
- PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ DisplayController displayController,
+ PipAppOpsListener pipAppOpsListener,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper,
+ PipMediaController pipMediaController,
+ PhonePipMenuController phonePipMenuController,
+ PipTaskOrganizer pipTaskOrganizer,
PipTransitionState pipTransitionState,
- PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
+ PipTouchHandler pipTouchHandler,
+ PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(PipController.create(context, shellController, displayController,
- pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
- pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
- pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
- taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor));
+ return Optional.ofNullable(PipController.create(
+ context, shellInit, shellCommandHandler, shellController,
+ displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
+ pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
+ pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ oneHandedController, mainExecutor));
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 52f0c4222b64..99922fbc2d95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -59,9 +59,9 @@ WMShell SysUI service:
adb shell dumpsys activity service SystemUIService WMShell
```
-If information should be added to the dump, make updates to:
-- `WMShell` if you are dumping SysUI state
-- `ShellCommandHandler` if you are dumping Shell state
+If information should be added to the dump, either:
+- Update `WMShell` if you are dumping SysUI state
+- Inject `ShellCommandHandler` into your Shell class, and add a dump callback
## Debugging in Android Studio
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
index 0dd50b1bee68..d6302e640ba7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -32,7 +32,7 @@ interfaces provided by the Shell and the rest of SystemUI.
More detail can be found in [go/wm-sysui-dagger](http://go/wm-sysui-dagger).
-## Interfaces to Shell components
+## Interfaces from SysUI to Shell components
Within the same process, the WM Shell components can be running on a different thread than the main
SysUI thread (disabled on certain products). This introduces challenges where we have to be
@@ -54,12 +54,30 @@ For example, you might have:
Adding an interface to a Shell component may seem like a lot of boiler plate, but is currently
necessary to maintain proper threading and logic isolation.
-## Configuration changes & other SysUI events
+## Listening for Configuration changes & other SysUI events
-Aside from direct calls into Shell controllers for exposed features, the Shell also receives
+Aside from direct calls into Shell controllers for exposed features, the Shell also receives
common event callbacks from SysUI via the `ShellController`. This includes things like:
- Configuration changes
-- TODO: Shell init
-- TODO: Shell command
-- TODO: Keyguard events \ No newline at end of file
+- Keyguard events
+- Shell init
+- Shell dumps & commands
+
+For other events which are specific to the Shell feature, then you can add callback methods on
+the Shell feature interface. Any such calls should <u>**never**</u> be synchronous calls as
+they will need to post to the Shell main thread to run.
+
+## Shell commands & Dumps
+
+Since the Shell library is a part of the SysUI process, it relies on SysUI to trigger commands
+on individual Shell components, or to dump individual shell components.
+
+```shell
+# Dump everything
+adb shell dumpsys activity service SystemUIService WMShell
+
+# Run a specific command
+adb shell dumpsys activity service SystemUIService WMShell help
+adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
+``` \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index 665b035bc41c..32125fa44148 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -27,7 +27,9 @@ import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
@@ -38,6 +40,7 @@ public class HideDisplayCutoutController implements ConfigurationChangeListener
private static final String TAG = "HideDisplayCutoutController";
private final Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final HideDisplayCutoutOrganizer mOrganizer;
@VisibleForTesting
@@ -49,7 +52,10 @@ public class HideDisplayCutoutController implements ConfigurationChangeListener
*/
@Nullable
public static HideDisplayCutoutController create(Context context,
- ShellController shellController, DisplayController displayController,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ DisplayController displayController,
ShellExecutor mainExecutor) {
// The SystemProperty is set for devices that support this feature and is used to control
// whether to create the HideDisplayCutout instance.
@@ -60,14 +66,24 @@ public class HideDisplayCutoutController implements ConfigurationChangeListener
HideDisplayCutoutOrganizer organizer =
new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
- return new HideDisplayCutoutController(context, shellController, organizer);
+ return new HideDisplayCutoutController(context, shellInit, shellCommandHandler,
+ shellController, organizer);
}
- HideDisplayCutoutController(Context context, ShellController shellController,
+ HideDisplayCutoutController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
HideDisplayCutoutOrganizer organizer) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mOrganizer = organizer;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
updateStatus();
mShellController.addConfigurationChangeListener(this);
}
@@ -96,11 +112,11 @@ public class HideDisplayCutoutController implements ConfigurationChangeListener
updateStatus();
}
- public void dump(@NonNull PrintWriter pw) {
- final String prefix = " ";
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = " ";
pw.print(TAG);
pw.println(" states: ");
- pw.print(prefix);
+ pw.print(innerPrefix);
pw.print("mEnabled=");
pw.println(mEnabled);
mOrganizer.dump(pw);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 73b9b89e6993..2fdd12185551 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -50,6 +50,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -73,6 +74,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
private final Handler mMainHandler;
private final Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final SyncTransactionQueue mSyncQueue;
private final DisplayController mDisplayController;
private final DisplayInsetsController mDisplayInsetsController;
@@ -141,6 +143,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
@VisibleForTesting
KidsModeTaskOrganizer(
Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ITaskOrganizerController taskOrganizerController,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
@@ -150,19 +154,23 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
KidsModeSettingsObserver kidsModeSettingsObserver,
ShellExecutor mainExecutor,
Handler mainHandler) {
- super(/* shellInit= */ null, taskOrganizerController, /* compatUI= */ null,
- unfoldAnimationController, recentTasks, mainExecutor);
+ // Note: we don't call super with the shell init because we will be initializing manually
+ super(/* shellInit= */ null, /* shellCommandHandler= */ null, taskOrganizerController,
+ /* compatUI= */ null, unfoldAnimationController, recentTasks, mainExecutor);
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
mKidsModeSettingsObserver = kidsModeSettingsObserver;
+ shellInit.addInitCallback(this::onInit, this);
}
public KidsModeTaskOrganizer(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -171,9 +179,10 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
ShellExecutor mainExecutor,
Handler mainHandler) {
// Note: we don't call super with the shell init because we will be initializing manually
- super(/* shellInit= */ null, /* compatUI= */ null, unfoldAnimationController, recentTasks,
- mainExecutor);
+ super(/* shellInit= */ null, /* taskOrganizerController= */ null, /* compatUI= */ null,
+ unfoldAnimationController, recentTasks, mainExecutor);
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
mDisplayController = displayController;
@@ -185,6 +194,9 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
* Initializes kids mode status.
*/
public void onInit() {
+ if (mShellCommandHandler != null) {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ }
if (mKidsModeSettingsObserver == null) {
mKidsModeSettingsObserver = new KidsModeSettingsObserver(mMainHandler, mContext);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 24f02ac1a6cf..9149204b94ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -56,7 +56,9 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
@@ -85,6 +87,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
private Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final AccessibilityManager mAccessibilityManager;
private final DisplayController mDisplayController;
@@ -192,8 +195,9 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
/**
* Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
*/
- public static OneHandedController create(
- Context context, ShellController shellController, WindowManager windowManager,
+ public static OneHandedController create(Context context,
+ ShellInit shellInit, ShellCommandHandler shellCommandHandler,
+ ShellController shellController, WindowManager windowManager,
DisplayController displayController, DisplayLayout displayLayout,
TaskStackListenerImpl taskStackListener,
InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
@@ -213,14 +217,16 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
context, displayLayout, settingsUtil, animationController, tutorialHandler,
jankMonitor, mainExecutor);
OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
- return new OneHandedController(context, shellController, displayController, organizer,
- touchHandler, tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler,
- oneHandedState, oneHandedUiEventsLogger, taskStackListener,
- mainExecutor, mainHandler);
+ return new OneHandedController(context, shellInit, shellCommandHandler, shellController,
+ displayController, organizer, touchHandler, tutorialHandler, settingsUtil,
+ accessibilityUtil, timeoutHandler, oneHandedState, oneHandedUiEventsLogger,
+ taskStackListener, mainExecutor, mainHandler);
}
@VisibleForTesting
OneHandedController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
OneHandedDisplayAreaOrganizer displayAreaOrganizer,
@@ -235,6 +241,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
ShellExecutor mainExecutor,
Handler mainHandler) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mOneHandedSettingsUtil = settingsUtil;
mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil;
@@ -247,8 +254,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mMainHandler = mainHandler;
mOneHandedUiEventLogger = uiEventsLogger;
mTaskStackListener = taskStackListener;
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
- mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
final float offsetPercentageConfig = context.getResources().getFraction(
R.fraction.config_one_handed_offset, 1, 1);
final int sysPropPercentageConfig = SystemProperties.getInt(
@@ -268,6 +275,12 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
getObserver(this::onSwipeToNotificationEnabledChanged);
mShortcutEnabledObserver = getObserver(this::onShortcutEnabledChanged);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
mDisplayController.addDisplayChangingController(this);
setupCallback();
registerSettingObservers(mUserId);
@@ -275,7 +288,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
updateSettings();
updateDisplayLayout(mContext.getDisplayId());
- mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityStateChangeListener);
@@ -623,7 +635,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
updateOneHandedEnabled();
}
- public void dump(@NonNull PrintWriter pw) {
+ public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = " ";
pw.println();
pw.println(TAG);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 38631ce26cd1..93172f82edd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -20,7 +20,6 @@ import android.graphics.Rect;
import com.android.wm.shell.common.annotations.ExternalThread;
-import java.io.PrintWriter;
import java.util.function.Consumer;
/**
@@ -99,12 +98,4 @@ public interface Pip {
* view hierarchy or destroyed.
*/
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
-
- /**
- * Dump the current state and information if need.
- *
- * @param pw The stream to dump information to.
- */
- default void dump(PrintWriter pw) {
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 586e3a014506..fc97f310ad4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -89,7 +89,9 @@ import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
@@ -122,6 +124,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private TaskStackListenerImpl mTaskStackListener;
private PipParamsChangedForwarder mPipParamsChangedForwarder;
private Optional<OneHandedController> mOneHandedController;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
protected final PipImpl mImpl;
@@ -295,6 +298,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
*/
@Nullable
public static Pip create(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
@@ -319,16 +324,18 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return null;
}
- return new PipController(context, shellController, displayController, pipAppOpsListener,
- pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
- pipTouchHandler, pipTransitionController,
+ return new PipController(context, shellInit, shellCommandHandler, shellController,
+ displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
+ pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
+ pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
oneHandedController, mainExecutor)
.mImpl;
}
protected PipController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
@@ -355,6 +362,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mImpl = new PipImpl();
mWindowManagerShellWrapper = windowManagerShellWrapper;
@@ -378,11 +386,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
.getInteger(R.integer.config_pipEnterAnimationDuration);
mPipParamsChangedForwarder = pipParamsChangedForwarder;
- //TODO: move this to ShellInit when PipController can be injected
- mMainExecutor.execute(this::init);
+ shellInit.addInitCallback(this::onInit, this);
}
- public void init() {
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mMainExecutor);
mPipTransitionController.registerPipTransitionCallback(this);
@@ -503,6 +511,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
updateMovementBounds(null /* toBounds */, false /* fromRotation */,
false /* fromImeAdjustment */, false /* fromShelfAdjustment */,
null /* windowContainerTransaction */);
+ } else {
+ // when we enter pip for the first time, the destination bounds and pip
+ // bounds will already match, since they are calculated prior to
+ // starting the animation, so we only need to update the min/max size
+ // that is used for e.g. double tap to maximized state
+ mTouchHandler.updateMinMaxSize(ratio);
}
}
@@ -622,7 +636,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipTaskOrganizer.scheduleAnimateResizePip(
postChangeBounds, duration, null /* updateBoundsCallback */);
} else {
- mTouchHandler.getMotionHelper().movePip(postChangeBounds);
+ // Directly move PiP to its final destination bounds without animation.
+ mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds);
}
} else {
updateDisplayLayout.run();
@@ -912,7 +927,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return true;
}
- private void dump(PrintWriter pw) {
+ private void dump(PrintWriter pw, String prefix) {
final String innerPrefix = " ";
pw.println(TAG);
mMenuController.dump(pw, innerPrefix);
@@ -1000,18 +1015,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipController.this.showPictureInPictureMenu();
});
}
-
- @Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> {
- PipController.this.dump(pw);
- });
- } catch (InterruptedException e) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Failed to dump PipController in 2s", TAG);
- }
- }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index c86c1368b88e..a2fa058e97b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -412,13 +412,7 @@ public class PipTouchHandler {
mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
bottomOffset);
- if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
- updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio);
- } else {
- mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
- mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
- mPipBoundsState.getExpandedBounds().height());
- }
+ updatePipSizeConstraints(insetBounds, normalBounds, aspectRatio);
// The extra offset does not really affect the movement bounds, but are applied based on the
// current state (ime showing, or shelf offset) when we need to actually shift
@@ -487,6 +481,27 @@ public class PipTouchHandler {
}
}
+ /**
+ * Update the values for min/max allowed size of picture in picture window based on the aspect
+ * ratio.
+ * @param aspectRatio aspect ratio to use for the calculation of min/max size
+ */
+ public void updateMinMaxSize(float aspectRatio) {
+ updatePipSizeConstraints(mInsetBounds, mPipBoundsState.getNormalBounds(),
+ aspectRatio);
+ }
+
+ private void updatePipSizeConstraints(Rect insetBounds, Rect normalBounds,
+ float aspectRatio) {
+ if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+ updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio);
+ } else {
+ mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
+ mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
+ mPipBoundsState.getExpandedBounds().height());
+ }
+ }
+
private void updatePinchResizeSizeConstraints(Rect insetBounds, Rect normalBounds,
float aspectRatio) {
final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 3d1a7e98e20d..7b42350b1365 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -44,6 +44,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -62,6 +63,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasks mImpl = new RecentTasksImpl();
@@ -87,20 +89,24 @@ public class RecentTasksController implements TaskStackListenerCallback,
public static RecentTasksController create(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
@ShellMainThread ShellExecutor mainExecutor
) {
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
return null;
}
- return new RecentTasksController(context, shellInit, taskStackListener, mainExecutor);
+ return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
+ mainExecutor);
}
RecentTasksController(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ShellExecutor mainExecutor) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
mMainExecutor = mainExecutor;
@@ -112,6 +118,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
}
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 d6120c409506..2117b69ebc2e 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
@@ -84,6 +84,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -131,6 +132,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final ShellTaskOrganizer mTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
@@ -147,6 +149,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final SplitscreenEventLogger mLogger;
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
+ private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
private StageCoordinator mStageCoordinator;
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
@@ -155,6 +158,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public SplitScreenController(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
@@ -168,6 +172,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
@@ -183,6 +188,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
+ mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
@@ -200,6 +206,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
*/
@VisibleForTesting
void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
+ this);
mShellController.addKeyguardChangeListener(this);
if (mStageCoordinator == null) {
// TODO: Multi-display
@@ -337,17 +346,39 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ final int[] result = new int[1];
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to invoke onAnimationFinished", e);
+ }
+ if (result[0] == START_SUCCESS || result[0] == START_TASK_TO_FRONT) {
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ mStageCoordinator.prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+ }
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ }
+ };
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
null /* wct */);
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
+ 0 /* duration */, 0 /* statusBarTransitionDelay */);
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
try {
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- mStageCoordinator.prepareEvictChildTasks(position, evictWct);
- final int result =
- ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
- if (result == START_SUCCESS || result == START_TASK_TO_FRONT) {
- mSyncQueue.queue(evictWct);
- }
+ result[0] = ActivityTaskManager.getService().startActivityFromRecents(taskId,
+ activityOptions.toBundle());
} catch (RemoteException e) {
Slog.e(TAG, "Failed to launch task", e);
}
@@ -403,7 +434,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
// split.
- if (isLaunchingAdjacently(intent.getIntent(), position)) {
+ if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
}
@@ -418,8 +449,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
/** Returns {@code true} if it's launching the same component on both sides of the split. */
@VisibleForTesting
- boolean isLaunchingAdjacently(@Nullable Intent startIntent,
- @SplitPosition int position) {
+ boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) {
if (startIntent == null) {
return false;
}
@@ -430,6 +460,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
if (isSplitScreenVisible()) {
+ // To prevent users from constantly dropping the same app to the same side resulting in
+ // a large number of instances in the background.
+ final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position);
+ final ComponentName targetActivity = targetTaskInfo != null
+ ? targetTaskInfo.baseIntent.getComponent() : null;
+ if (Objects.equals(launchingActivity, targetActivity)) {
+ return false;
+ }
+
+ // Allow users to start a new instance the same to adjacent side.
final ActivityManager.RunningTaskInfo pairedTaskInfo =
getTaskInfo(SplitLayout.reversePosition(position));
final ComponentName pairedActivity = pairedTaskInfo != null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
new file mode 100644
index 000000000000..681d9647d154
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+
+import com.android.wm.shell.sysui.ShellCommandHandler;
+
+import java.io.PrintWriter;
+
+/**
+ * Handles the shell commands for the SplitscreenController.
+ */
+public class SplitScreenShellCommandHandler implements
+ ShellCommandHandler.ShellCommandActionHandler {
+
+ private final SplitScreenController mController;
+
+ public SplitScreenShellCommandHandler(SplitScreenController controller) {
+ mController = controller;
+ }
+
+ @Override
+ public boolean onShellCommand(String[] args, PrintWriter pw) {
+ switch (args[0]) {
+ case "moveToSideStage":
+ return runMoveToSideStage(args, pw);
+ case "removeFromSideStage":
+ return runRemoveFromSideStage(args, pw);
+ case "setSideStagePosition":
+ return runSetSideStagePosition(args, pw);
+ default:
+ pw.println("Invalid command: " + args[0]);
+ return false;
+ }
+ }
+
+ private boolean runMoveToSideStage(String[] args, PrintWriter pw) {
+ if (args.length < 3) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = new Integer(args[1]);
+ final int sideStagePosition = args.length > 3
+ ? new Integer(args[2]) : SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ mController.moveToSideStage(taskId, sideStagePosition);
+ return true;
+ }
+
+ private boolean runRemoveFromSideStage(String[] args, PrintWriter pw) {
+ if (args.length < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = new Integer(args[1]);
+ mController.removeFromSideStage(taskId);
+ return true;
+ }
+
+ private boolean runSetSideStagePosition(String[] args, PrintWriter pw) {
+ if (args.length < 2) {
+ // First argument is the action name.
+ pw.println("Error: side stage position should be provided as arguments");
+ return false;
+ }
+ final int position = new Integer(args[1]);
+ mController.setSideStagePosition(position);
+ return true;
+ }
+
+ @Override
+ public void printShellCommandHelp(PrintWriter pw, String prefix) {
+ pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
+ pw.println(prefix + " Move a task with given id in split-screen mode.");
+ pw.println(prefix + "removeFromSideStage <taskId>");
+ pw.println(prefix + " Remove a task with given id in split-screen mode.");
+ pw.println(prefix + "setSideStagePosition <SideStagePosition>");
+ pw.println(prefix + " Sets the position of the side-stage.");
+ }
+}
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 80ef74e63940..8e1ae397ea64 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
@@ -452,10 +452,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
IRemoteAnimationFinishedCallback finishedCallback,
SurfaceControl.Transaction t) {
if (apps == null || apps.length == 0) {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePosition(SplitLayout.reversePosition(
- getSideStagePosition()), null);
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ mMainExecutor.execute(() ->
+ exitSplitScreen(mMainStage.getChildCount() == 0
+ ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ } else {
+ // Switch the split position if launching as MULTIPLE_TASK failed.
+ if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+ setSideStagePosition(SplitLayout.reversePosition(
+ getSideStagePosition()), null);
+ }
}
// Do nothing when the animation was cancelled.
@@ -651,7 +657,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mShouldUpdateRecents = true;
// If any stage has no child after animation finished, it means that split will display
// nothing, such status will happen if task and intent is same app but not support
- // multi-instagce, we should exit split and expand that app as full screen.
+ // multi-instance, we should exit split and expand that app as full screen.
if (!cancel && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
mMainExecutor.execute(() ->
exitSplitScreen(mMainStage.getChildCount() == 0
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
index f4fc0c4c36bc..2e6ddc363906 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
@@ -16,19 +16,14 @@
package com.android.wm.shell.sysui;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter;
-import java.util.Optional;
+import java.util.Arrays;
+import java.util.TreeMap;
+import java.util.function.BiConsumer;
/**
* An entry point into the shell for dumping shell internal state and running adb commands.
@@ -38,52 +33,61 @@ import java.util.Optional;
public final class ShellCommandHandler {
private static final String TAG = ShellCommandHandler.class.getSimpleName();
- private final Optional<SplitScreenController> mSplitScreenOptional;
- private final Optional<Pip> mPipOptional;
- private final Optional<OneHandedController> mOneHandedOptional;
- private final Optional<HideDisplayCutoutController> mHideDisplayCutout;
- private final Optional<RecentTasksController> mRecentTasks;
- private final ShellTaskOrganizer mShellTaskOrganizer;
- private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
-
- public ShellCommandHandler(
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
- Optional<OneHandedController> oneHandedOptional,
- Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<RecentTasksController> recentTasks,
- ShellExecutor mainExecutor) {
- mShellTaskOrganizer = shellTaskOrganizer;
- mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
- mRecentTasks = recentTasks;
- mSplitScreenOptional = splitScreenOptional;
- mPipOptional = pipOptional;
- mOneHandedOptional = oneHandedOptional;
- mHideDisplayCutout = hideDisplayCutout;
- // TODO(238217847): To be removed once the command handler dependencies are inverted
- shellController.setShellCommandHandler(this);
+ // We're using a TreeMap to keep them sorted by command name
+ private final TreeMap<String, BiConsumer<PrintWriter, String>> mDumpables = new TreeMap<>();
+ private final TreeMap<String, ShellCommandActionHandler> mCommands = new TreeMap<>();
+
+ public interface ShellCommandActionHandler {
+ /**
+ * Handles the given command.
+ *
+ * @param args the arguments starting with the action name, then the action arguments
+ * @param pw the write to print output to
+ */
+ boolean onShellCommand(String[] args, PrintWriter pw);
+
+ /**
+ * Prints the help for this class of commands. Implementations do not need to print the
+ * command class.
+ */
+ void printShellCommandHelp(PrintWriter pw, String prefix);
+ }
+
+
+ /**
+ * Adds a callback to run when the Shell is being dumped.
+ *
+ * @param callback the callback to be made when Shell is dumped, takes the print writer and
+ * a prefix
+ * @param instance used for debugging only
+ */
+ public <T> void addDumpCallback(BiConsumer<PrintWriter, String> callback, T instance) {
+ mDumpables.put(instance.getClass().getSimpleName(), callback);
+ ProtoLog.v(WM_SHELL_INIT, "Adding dump callback for %s",
+ instance.getClass().getSimpleName());
+ }
+
+ /**
+ * Adds an action callback to be invoked when the user runs that particular command from adb.
+ *
+ * @param commandClass the top level class of command to invoke
+ * @param actions the interface to callback when an action of this class is invoked
+ * @param instance used for debugging only
+ */
+ public <T> void addCommandCallback(String commandClass, ShellCommandActionHandler actions,
+ T instance) {
+ mCommands.put(commandClass, actions);
+ ProtoLog.v(WM_SHELL_INIT, "Adding command class %s for %s", commandClass,
+ instance.getClass().getSimpleName());
}
/** Dumps WM Shell internal state. */
public void dump(PrintWriter pw) {
- mShellTaskOrganizer.dump(pw, "");
- pw.println();
- pw.println();
- mPipOptional.ifPresent(pip -> pip.dump(pw));
- mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
- mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
- pw.println();
- pw.println();
- mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw, ""));
- pw.println();
- pw.println();
- mRecentTasks.ifPresent(recentTasks -> recentTasks.dump(pw, ""));
- pw.println();
- pw.println();
- mKidsModeTaskOrganizer.dump(pw, "");
+ for (String key : mDumpables.keySet()) {
+ final BiConsumer<PrintWriter, String> r = mDumpables.get(key);
+ r.accept(pw, "");
+ pw.println();
+ }
}
@@ -93,72 +97,32 @@ public final class ShellCommandHandler {
// Argument at position 0 is "WMShell".
return false;
}
- switch (args[1]) {
- case "moveToSideStage":
- return runMoveToSideStage(args, pw);
- case "removeFromSideStage":
- return runRemoveFromSideStage(args, pw);
- case "setSideStagePosition":
- return runSetSideStagePosition(args, pw);
- case "help":
- return runHelp(pw);
- default:
- return false;
- }
- }
- private boolean runMoveToSideStage(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First arguments are "WMShell" and command name.
- pw.println("Error: task id should be provided as arguments");
- return false;
+ final String cmdClass = args[1];
+ if (cmdClass.toLowerCase().equals("help")) {
+ return runHelp(pw);
}
- final int taskId = new Integer(args[2]);
- final int sideStagePosition = args.length > 3
- ? new Integer(args[3]) : SPLIT_POSITION_BOTTOM_OR_RIGHT;
- mSplitScreenOptional.ifPresent(split -> split.moveToSideStage(taskId, sideStagePosition));
- return true;
- }
-
- private boolean runRemoveFromSideStage(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First arguments are "WMShell" and command name.
- pw.println("Error: task id should be provided as arguments");
+ if (!mCommands.containsKey(cmdClass)) {
return false;
}
- final int taskId = new Integer(args[2]);
- mSplitScreenOptional.ifPresent(split -> split.removeFromSideStage(taskId));
- return true;
- }
- private boolean runSetSideStagePosition(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First arguments are "WMShell" and command name.
- pw.println("Error: side stage position should be provided as arguments");
- return false;
- }
- final int position = new Integer(args[2]);
- mSplitScreenOptional.ifPresent(split -> split.setSideStagePosition(position));
+ // Only pass the actions onwards as arguments to the callback
+ final ShellCommandActionHandler actions = mCommands.get(args[1]);
+ final String[] cmdClassArgs = Arrays.copyOfRange(args, 2, args.length);
+ actions.onShellCommand(cmdClassArgs, pw);
return true;
}
private boolean runHelp(PrintWriter pw) {
pw.println("Window Manager Shell commands:");
+ for (String commandClass : mCommands.keySet()) {
+ pw.println(" " + commandClass);
+ mCommands.get(commandClass).printShellCommandHelp(pw, " ");
+ }
pw.println(" help");
pw.println(" Print this help text.");
pw.println(" <no arguments provided>");
- pw.println(" Dump Window Manager Shell internal state");
- pw.println(" pair <taskId1> <taskId2>");
- pw.println(" unpair <taskId>");
- pw.println(" Pairs/unpairs tasks with given ids.");
- pw.println(" moveToSideStage <taskId> <SideStagePosition>");
- pw.println(" Move a task with given id in split-screen mode.");
- pw.println(" removeFromSideStage <taskId>");
- pw.println(" Remove a task with given id in split-screen mode.");
- pw.println(" setSideStageOutline <true/false>");
- pw.println(" Enable/Disable outline on the side-stage.");
- pw.println(" setSideStagePosition <SideStagePosition>");
- pw.println(" Sets the position of the side-stage.");
+ pw.println(" Dump all Window Manager Shell internal state");
return true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index f1f317f65ba9..52ffb46bb39c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -45,11 +45,10 @@ public class ShellController {
private static final String TAG = ShellController.class.getSimpleName();
private final ShellInit mShellInit;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mMainExecutor;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
- private ShellCommandHandler mShellCommandHandler;
-
private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
@@ -57,8 +56,10 @@ public class ShellController {
private Configuration mLastConfiguration;
- public ShellController(ShellInit shellInit, ShellExecutor mainExecutor) {
+ public ShellController(ShellInit shellInit, ShellCommandHandler shellCommandHandler,
+ ShellExecutor mainExecutor) {
mShellInit = shellInit;
+ mShellCommandHandler = shellCommandHandler;
mMainExecutor = mainExecutor;
}
@@ -70,15 +71,6 @@ public class ShellController {
}
/**
- * Sets the command handler to call back to.
- * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
- * command handler to other classes.
- */
- public void setShellCommandHandler(ShellCommandHandler shellCommandHandler) {
- mShellCommandHandler = shellCommandHandler;
- }
-
- /**
* Adds a new configuration listener. The configuration change callbacks are not made in any
* particular order.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index c250e0313255..ac52235375c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -52,6 +52,9 @@ public class ShellInit {
* Adds a callback to the ordered list of callbacks be made when Shell is first started. This
* can be used in class constructors when dagger is used to ensure that the initialization order
* matches the dependency order.
+ *
+ * @param r the callback to be made when Shell is initialized
+ * @param instance used for debugging only
*/
public <T extends Object> void addInitCallback(Runnable r, T instance) {
if (mHasInitialized) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 087304b0d00b..506a4c0f90f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -233,7 +233,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
- .setColor(mTaskBackgroundSurface, mTmpColor);
+ .setColor(mTaskBackgroundSurface, mTmpColor)
+ .setLayer(mTaskBackgroundSurface, -1)
+ .show(mTaskBackgroundSurface);
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7517e8ab6826..f865649b6bbc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -59,6 +59,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
@@ -85,10 +86,12 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
@Mock
private CompatUIController mCompatUI;
@Mock
- private ShellInit mShellInit;
+ private ShellExecutor mTestExecutor;
+ @Mock
+ private ShellCommandHandler mShellCommandHandler;
- ShellTaskOrganizer mOrganizer;
- private final ShellExecutor mTestExecutor = mock(ShellExecutor.class);
+ private ShellTaskOrganizer mOrganizer;
+ private ShellInit mShellInit;
private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener {
final ArrayList<RunningTaskInfo> appeared = new ArrayList<>();
@@ -132,8 +135,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
- mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mTaskOrganizerController,
- mCompatUI, Optional.empty(), Optional.empty(), mTestExecutor));
+ mShellInit = spy(new ShellInit(mTestExecutor));
+ mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
+ mTaskOrganizerController, mCompatUI, Optional.empty(), Optional.empty(),
+ mTestExecutor));
+ mShellInit.init();
}
@Test
@@ -142,9 +148,12 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
}
@Test
- public void testRegisterOrganizer_sendRegisterTaskOrganizer() throws RemoteException {
- mOrganizer.registerOrganizer();
+ public void instantiate_addDumpCallback() {
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ }
+ @Test
+ public void testInit_sendRegisterTaskOrganizer() throws RemoteException {
verify(mTaskOrganizerController).registerTaskOrganizer(any(ITaskOrganizer.class));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index ba81602054a8..b0dd7811c53c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -61,6 +62,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Ignore;
@@ -81,6 +83,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
private static final String ANIMATION_ENABLED = "1";
private final TestShellExecutor mShellExecutor = new TestShellExecutor();
+ private ShellInit mShellInit;
@Rule
public TestableContext mContext =
@@ -110,10 +113,12 @@ public class BackAnimationControllerTest extends ShellTestCase {
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION,
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
- mController = new BackAnimationController(
+ mShellInit = spy(new ShellInit(mShellExecutor));
+ mController = new BackAnimationController(mShellInit,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
+ mShellInit.init();
mEventTime = 0;
mShellExecutor.flushAll();
}
@@ -160,6 +165,11 @@ public class BackAnimationControllerTest extends ShellTestCase {
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
@Ignore("b/207481538")
public void crossActivity_screenshotAttachedAndVisible() {
SurfaceControl screenshotSurface = new SurfaceControl();
@@ -233,10 +243,12 @@ public class BackAnimationControllerTest extends ShellTestCase {
public void animationDisabledFromSettings() throws RemoteException {
// Toggle the setting off
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
- mController = new BackAnimationController(
+ ShellInit shellInit = new ShellInit(mShellExecutor);
+ mController = new BackAnimationController(shellInit,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
+ shellInit.init();
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
RemoteAnimationTarget animationTarget = createAnimationTarget();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 828c13ecfda6..6292130ddec9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -54,6 +55,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -79,6 +81,7 @@ public class CompatUIControllerTest extends ShellTestCase {
private static final int TASK_ID = 12;
private CompatUIController mController;
+ private ShellInit mShellInit;
private @Mock ShellController mMockShellController;
private @Mock DisplayController mMockDisplayController;
private @Mock DisplayInsetsController mMockDisplayInsetsController;
@@ -107,9 +110,10 @@ public class CompatUIControllerTest extends ShellTestCase {
doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
- mController = new CompatUIController(mContext, mMockShellController, mMockDisplayController,
- mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor,
- mMockTransitionsLazy) {
+ mShellInit = spy(new ShellInit(mMockExecutor));
+ mController = new CompatUIController(mContext, mShellInit, mMockShellController,
+ mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
+ mMockSyncQueue, mMockExecutor, mMockTransitionsLazy) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
@@ -122,10 +126,16 @@ public class CompatUIControllerTest extends ShellTestCase {
return mMockLetterboxEduLayout;
}
};
+ mShellInit.init();
spyOn(mController);
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void instantiateController_registerKeyguardChangeListener() {
verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
index dcc504ad0cdb..6c301bbbc7f1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -17,7 +17,9 @@
package com.android.wm.shell.hidedisplaycutout;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -29,7 +31,10 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -45,17 +50,32 @@ public class HideDisplayCutoutControllerTest extends ShellTestCase {
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Mock
+ private ShellCommandHandler mShellCommandHandler;
+ @Mock
private ShellController mShellController;
@Mock
private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
private HideDisplayCutoutController mHideDisplayCutoutController;
+ private ShellInit mShellInit;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mHideDisplayCutoutController = new HideDisplayCutoutController(
- mContext, mShellController, mMockDisplayAreaOrganizer);
+ mShellInit = spy(new ShellInit(mock(ShellExecutor.class)));
+ mHideDisplayCutoutController = new HideDisplayCutoutController(mContext, mShellInit,
+ mShellCommandHandler, mShellController, mMockDisplayAreaOrganizer);
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerDumpCallback() {
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index a919ad0b4765..ecfb427dbced 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -49,6 +49,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
@@ -74,6 +75,7 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
@Mock private WindowContainerTransaction mTransaction;
@Mock private KidsModeSettingsObserver mObserver;
@Mock private ShellInit mShellInit;
+ @Mock private ShellCommandHandler mShellCommandHandler;
@Mock private DisplayInsetsController mDisplayInsetsController;
KidsModeTaskOrganizer mOrganizer;
@@ -87,14 +89,20 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
} catch (RemoteException e) {
}
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
- mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mTaskOrganizerController,
- mSyncTransactionQueue, mDisplayController, mDisplayInsetsController,
- Optional.empty(), Optional.empty(), mObserver, mTestExecutor, mHandler));
+ mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mShellInit, mShellCommandHandler,
+ mTaskOrganizerController, mSyncTransactionQueue, mDisplayController,
+ mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver,
+ mTestExecutor, mHandler));
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void testKidsModeOn() {
doReturn(true).when(mObserver).isEnabled();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index dbf93ae35c18..90645ce4747d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -36,7 +36,6 @@ import static org.mockito.Mockito.when;
import android.graphics.Rect;
import android.os.Handler;
-import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.util.ArrayMap;
import android.view.Display;
@@ -49,7 +48,9 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -61,18 +62,20 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class OneHandedControllerTest extends OneHandedTestCase {
- private int mCurrentUser = UserHandle.myUserId();
Display mDisplay;
OneHandedAccessibilityUtil mOneHandedAccessibilityUtil;
OneHandedController mSpiedOneHandedController;
OneHandedTimeoutHandler mSpiedTimeoutHandler;
OneHandedState mSpiedTransitionState;
+ ShellInit mShellInit;
@Mock
+ ShellCommandHandler mMockShellCommandHandler;
+ @Mock
ShellController mMockShellController;
@Mock
- DisplayLayout mDisplayLayout;
+ DisplayLayout mMockDisplayLayout;
@Mock
DisplayController mMockDisplayController;
@Mock
@@ -102,7 +105,7 @@ public class OneHandedControllerTest extends OneHandedTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
mDisplay = mContext.getDisplay();
- mDisplayLayout = Mockito.mock(DisplayLayout.class);
+ mMockDisplayLayout = Mockito.mock(DisplayLayout.class);
mSpiedTimeoutHandler = spy(new OneHandedTimeoutHandler(mMockShellMainExecutor));
mSpiedTransitionState = spy(new OneHandedState());
@@ -122,11 +125,14 @@ public class OneHandedControllerTest extends OneHandedTestCase {
when(mMockDisplayAreaOrganizer.getLastDisplayBounds()).thenReturn(
new Rect(0, 0, 1080, 2400));
- when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(mDisplayLayout);
+ when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(mMockDisplayLayout);
+ mShellInit = spy(new ShellInit(mMockShellMainExecutor));
mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext);
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
+ mShellInit,
+ mMockShellCommandHandler,
mMockShellController,
mMockDisplayController,
mMockDisplayAreaOrganizer,
@@ -141,6 +147,17 @@ public class OneHandedControllerTest extends OneHandedTestCase {
mMockShellMainExecutor,
mMockShellMainHandler)
);
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerDumpCallback() {
+ verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any());
}
@Test
@@ -308,9 +325,9 @@ public class OneHandedControllerTest extends OneHandedTestCase {
@Test
public void testRotation90CanNotStartOneHanded() {
- mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
+ mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
mSpiedTransitionState.setState(STATE_NONE);
- when(mDisplayLayout.isLandscape()).thenReturn(true);
+ when(mMockDisplayLayout.isLandscape()).thenReturn(true);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
@@ -320,10 +337,10 @@ public class OneHandedControllerTest extends OneHandedTestCase {
@Test
public void testRotation180CanStartOneHanded() {
- mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
+ mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
mSpiedTransitionState.setState(STATE_NONE);
when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true);
- when(mDisplayLayout.isLandscape()).thenReturn(false);
+ when(mMockDisplayLayout.isLandscape()).thenReturn(false);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
@@ -333,9 +350,9 @@ public class OneHandedControllerTest extends OneHandedTestCase {
@Test
public void testRotation270CanNotStartOneHanded() {
- mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
+ mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
mSpiedTransitionState.setState(STATE_NONE);
- when(mDisplayLayout.isLandscape()).thenReturn(true);
+ when(mMockDisplayLayout.isLandscape()).thenReturn(true);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
index e6a8220e081b..a39bdf04bf56 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
@@ -41,7 +41,9 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -61,6 +63,10 @@ public class OneHandedStateTest extends OneHandedTestCase {
OneHandedState mSpiedState;
@Mock
+ ShellInit mMockShellInit;
+ @Mock
+ ShellCommandHandler mMockShellCommandHandler;
+ @Mock
ShellController mMockShellController;
@Mock
DisplayController mMockDisplayController;
@@ -111,6 +117,8 @@ public class OneHandedStateTest extends OneHandedTestCase {
mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext);
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
+ mMockShellInit,
+ mMockShellCommandHandler,
mMockShellController,
mMockDisplayController,
mMockDisplayAreaOrganizer,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index f192514c37ab..9ed8d84d665f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -55,7 +55,9 @@ import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -74,7 +76,9 @@ import java.util.Set;
@TestableLooper.RunWithLooper
public class PipControllerTest extends ShellTestCase {
private PipController mPipController;
+ private ShellInit mShellInit;
+ @Mock private ShellCommandHandler mMockShellCommandHandler;
@Mock private ShellController mMockShellController;
@Mock private DisplayController mMockDisplayController;
@Mock private PhonePipMenuController mMockPhonePipMenuController;
@@ -105,19 +109,31 @@ public class PipControllerTest extends ShellTestCase {
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(mMockExecutor).execute(any());
- mPipController = new PipController(mContext, mMockShellController, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
- mMockPipKeepClearAlgorithm,
+ mShellInit = spy(new ShellInit(mMockExecutor));
+ mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
+ mMockShellController, mMockDisplayController, mMockPipAppOpsListener,
+ mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mPipParamsChangedForwarder,
mMockOneHandedController, mMockExecutor);
+ mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
}
@Test
+ public void instantiatePipController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerDumpCallback() {
+ verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ }
+
+ @Test
public void instantiatePipController_registerConfigChangeListener() {
verify(mMockShellController, times(1)).addConfigurationChangeListener(any());
}
@@ -149,9 +165,10 @@ public class PipControllerTest extends ShellTestCase {
when(mockPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
when(spyContext.getPackageManager()).thenReturn(mockPackageManager);
- assertNull(PipController.create(spyContext, mMockShellController, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
- mMockPipKeepClearAlgorithm,
+ ShellInit shellInit = new ShellInit(mMockExecutor);
+ assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
+ mMockShellController, mMockDisplayController, mMockPipAppOpsListener,
+ mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
@@ -217,7 +234,7 @@ public class PipControllerTest extends ShellTestCase {
mPipController.mDisplaysChangedListener.onDisplayConfigurationChanged(
displayId, new Configuration());
- verify(mMockPipMotionHelper).movePip(any(Rect.class));
+ verify(mMockPipTaskOrganizer).scheduleFinishResizePip(any(Rect.class));
}
@Test
@@ -233,7 +250,7 @@ public class PipControllerTest extends ShellTestCase {
mPipController.mDisplaysChangedListener.onDisplayConfigurationChanged(
displayId, new Configuration());
- verify(mMockPipMotionHelper, never()).movePip(any(Rect.class));
+ verify(mMockPipTaskOrganizer, never()).scheduleFinishResizePip(any(Rect.class));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index d406a4ed3fd7..81bb609cc711 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -23,7 +23,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@@ -48,6 +50,7 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -73,21 +76,35 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Mock
private TaskStackListenerImpl mTaskStackListener;
@Mock
- private ShellInit mShellInit;
+ private ShellCommandHandler mShellCommandHandler;
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
+ private ShellInit mShellInit;
private ShellExecutor mMainExecutor;
@Before
public void setUp() {
mMainExecutor = new TestShellExecutor();
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+ mShellInit = spy(new ShellInit(mMainExecutor));
mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
- mTaskStackListener, mMainExecutor));
- mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit,
+ mShellCommandHandler, mTaskStackListener, mMainExecutor));
+ mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
mMainExecutor);
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), isA(RecentTasksController.class));
+ }
+
+ @Test
+ public void instantiateController_addDumpCallback() {
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(),
+ isA(RecentTasksController.class));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 10788f9df32f..5a68361c595c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -20,12 +20,14 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+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 static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -53,6 +55,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -72,8 +75,9 @@ import java.util.Optional;
@RunWith(AndroidJUnit4.class)
public class SplitScreenControllerTests extends ShellTestCase {
- @Mock ShellController mShellController;
@Mock ShellInit mShellInit;
+ @Mock ShellController mShellController;
+ @Mock ShellCommandHandler mShellCommandHandler;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock SyncTransactionQueue mSyncQueue;
@Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@@ -93,9 +97,10 @@ public class SplitScreenControllerTests extends ShellTestCase {
public void setup() {
MockitoAnnotations.initMocks(this);
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
- mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController,
- mDisplayImeController, mDisplayInsetsController, mDragAndDropController,
- mTransitions, mTransactionPool, mIconProvider, mRecentTasks, mMainExecutor));
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mMainExecutor));
}
@Test
@@ -104,14 +109,31 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
+ public void instantiateController_registerDumpCallback() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onInit();
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerCommandCallback() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onInit();
+ verify(mShellCommandHandler, times(1)).addCommandCallback(eq("splitscreen"), any(), any());
+ }
+
+ @Test
public void testControllerRegistersKeyguardChangeListener() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
mSplitScreenController.onInit();
verify(mShellController, times(1)).addKeyguardChangeListener(any());
}
@Test
- public void testIsLaunchingAdjacently_notInSplitScreen() {
+ public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
@@ -120,7 +142,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
ActivityManager.RunningTaskInfo focusTaskInfo =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertTrue(mSplitScreenController.isLaunchingAdjacently(
+ assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
// Verify launching different activity returns false.
@@ -128,28 +150,40 @@ public class SplitScreenControllerTests extends ShellTestCase {
focusTaskInfo =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertFalse(mSplitScreenController.isLaunchingAdjacently(
+ assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
}
@Test
- public void testIsLaunchingAdjacently_inSplitScreen() {
+ public void testShouldAddMultipleTaskFlag_inSplitScreen() {
doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
-
- // Verify launching the same activity returns true.
Intent startIntent = createStartIntent("startActivity");
- ActivityManager.RunningTaskInfo pairingTaskInfo =
+ ActivityManager.RunningTaskInfo sameTaskInfo =
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt());
- assertTrue(mSplitScreenController.isLaunchingAdjacently(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
-
- // Verify launching different activity returns false.
Intent diffIntent = createStartIntent("diffActivity");
- pairingTaskInfo =
+ ActivityManager.RunningTaskInfo differentTaskInfo =
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
- doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt());
- assertFalse(mSplitScreenController.isLaunchingAdjacently(
+
+ // Verify launching the same activity return false.
+ doReturn(sameTaskInfo).when(mSplitScreenController)
+ .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+ assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
+ startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+
+ // Verify launching the same activity as adjacent returns true.
+ doReturn(differentTaskInfo).when(mSplitScreenController)
+ .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+ doReturn(sameTaskInfo).when(mSplitScreenController)
+ .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
+ startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+
+ // Verify launching different activity from adjacent returns false.
+ doReturn(differentTaskInfo).when(mSplitScreenController)
+ .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+ doReturn(differentTaskInfo).when(mSplitScreenController)
+ .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 02311bab2e4d..39e58ffcf9c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -45,6 +45,8 @@ public class ShellControllerTest extends ShellTestCase {
@Mock
private ShellInit mShellInit;
@Mock
+ private ShellCommandHandler mShellCommandHandler;
+ @Mock
private ShellExecutor mExecutor;
private ShellController mController;
@@ -56,7 +58,7 @@ public class ShellControllerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
- mController = new ShellController(mShellInit, mExecutor);
+ mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 1e7d5fe95229..226843eca64e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -204,6 +204,8 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlStartT)
.setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f});
verify(mMockSurfaceControlStartT).setShadowRadius(taskBackgroundSurface, 10);
+ verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1);
+ verify(mMockSurfaceControlStartT).show(taskBackgroundSurface);
verify(mMockSurfaceControlViewHostFactory)
.create(any(), eq(defaultDisplay), any(), anyBoolean());
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 0ef80ee10708..132234b38003 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -407,14 +407,28 @@ static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle,
indices = (const uint16_t*)(indexA.ptr() + indexIndex);
}
- SkVertices::VertexMode mode = static_cast<SkVertices::VertexMode>(modeHandle);
+ SkVertices::VertexMode vertexMode = static_cast<SkVertices::VertexMode>(modeHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- get_canvas(canvasHandle)->drawVertices(SkVertices::MakeCopy(mode, vertexCount,
- reinterpret_cast<const SkPoint*>(verts),
- reinterpret_cast<const SkPoint*>(texs),
- reinterpret_cast<const SkColor*>(colors),
- indexCount, indices).get(),
- SkBlendMode::kModulate, *paint);
+
+ // Preserve legacy Skia behavior: ignore the shader if there are no texs set.
+ Paint noShaderPaint;
+ if (jtexs == NULL) {
+ noShaderPaint = Paint(*paint);
+ noShaderPaint.setShader(nullptr);
+ paint = &noShaderPaint;
+ }
+ // Since https://skia-review.googlesource.com/c/skia/+/473676, Skia will blend paint and vertex
+ // colors when no shader is provided. This ternary uses kDst to mimic the old behavior of
+ // ignoring the paint and using the vertex colors directly when no shader is provided.
+ SkBlendMode blendMode = paint->getShader() ? SkBlendMode::kModulate : SkBlendMode::kDst;
+
+ get_canvas(canvasHandle)
+ ->drawVertices(SkVertices::MakeCopy(
+ vertexMode, vertexCount, reinterpret_cast<const SkPoint*>(verts),
+ reinterpret_cast<const SkPoint*>(texs),
+ reinterpret_cast<const SkColor*>(colors), indexCount, indices)
+ .get(),
+ blendMode, *paint);
}
static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 322e1be250a6..5795fd439fe2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1610,6 +1610,9 @@
<string name="dream_complication_title_cast_info">Cast Info</string>
<!-- Screensaver overlay which displays home controls. [CHAR LIMIT=20] -->
<string name="dream_complication_title_home_controls">Home Controls</string>
+ <!-- Screensaver overlay which displays smartspace. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_smartspace">Smartspace</string>
+
<!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] -->
<string name="avatar_picker_title">Choose a profile picture</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index a46e23235843..22586171e5cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -92,7 +92,8 @@ public class DreamBackend {
COMPLICATION_TYPE_WEATHER,
COMPLICATION_TYPE_AIR_QUALITY,
COMPLICATION_TYPE_CAST_INFO,
- COMPLICATION_TYPE_HOME_CONTROLS
+ COMPLICATION_TYPE_HOME_CONTROLS,
+ COMPLICATION_TYPE_SMARTSPACE
})
@Retention(RetentionPolicy.SOURCE)
public @interface ComplicationType {}
@@ -103,6 +104,7 @@ public class DreamBackend {
public static final int COMPLICATION_TYPE_AIR_QUALITY = 4;
public static final int COMPLICATION_TYPE_CAST_INFO = 5;
public static final int COMPLICATION_TYPE_HOME_CONTROLS = 6;
+ public static final int COMPLICATION_TYPE_SMARTSPACE = 7;
private final Context mContext;
private final IDreamManager mDreamManager;
@@ -351,6 +353,9 @@ public class DreamBackend {
case COMPLICATION_TYPE_HOME_CONTROLS:
res = R.string.dream_complication_title_home_controls;
break;
+ case COMPLICATION_TYPE_SMARTSPACE:
+ res = R.string.dream_complication_title_smartspace;
+ break;
default:
return null;
}
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
new file mode 100644
index 000000000000..4cfe39225a9b
--- /dev/null
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUIComposeCore",
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.material3_material3",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/compose/core/AndroidManifest.xml b/packages/SystemUI/compose/core/AndroidManifest.xml
new file mode 100644
index 000000000000..83c442d2d6f2
--- /dev/null
+++ b/packages/SystemUI/compose/core/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.compose.core">
+
+
+</manifest>
diff --git a/packages/SystemUI/compose/core/TEST_MAPPING b/packages/SystemUI/compose/core/TEST_MAPPING
new file mode 100644
index 000000000000..dc243d2fd8f5
--- /dev/null
+++ b/packages/SystemUI/compose/core/TEST_MAPPING
@@ -0,0 +1,37 @@
+{
+ "presubmit": [
+ {
+ "name": "SystemUIComposeCoreTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "SystemUIComposeFeaturesTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "SystemUIComposeGalleryTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiController.kt
new file mode 100644
index 000000000000..c9470c8dbe3a
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiController.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2022 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.compose
+
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Build
+import android.view.View
+import android.view.Window
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.graphics.luminance
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.window.DialogWindowProvider
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+
+/**
+ * *************************************************************************************************
+ * This file was forked from
+ * https://github.com/google/accompanist/blob/main/systemuicontroller/src/main/java/com/google/accompanist/systemuicontroller/SystemUiController.kt
+ * and will be removed once it lands in AndroidX.
+ */
+
+/**
+ * A class which provides easy-to-use utilities for updating the System UI bar colors within Jetpack
+ * Compose.
+ *
+ * @sample com.google.accompanist.sample.systemuicontroller.SystemUiControllerSample
+ */
+@Stable
+interface SystemUiController {
+
+ /**
+ * Property which holds the status bar visibility. If set to true, show the status bar,
+ * otherwise hide the status bar.
+ */
+ var isStatusBarVisible: Boolean
+
+ /**
+ * Property which holds the navigation bar visibility. If set to true, show the navigation bar,
+ * otherwise hide the navigation bar.
+ */
+ var isNavigationBarVisible: Boolean
+
+ /**
+ * Property which holds the status & navigation bar visibility. If set to true, show both bars,
+ * otherwise hide both bars.
+ */
+ var isSystemBarsVisible: Boolean
+ get() = isNavigationBarVisible && isStatusBarVisible
+ set(value) {
+ isStatusBarVisible = value
+ isNavigationBarVisible = value
+ }
+
+ /**
+ * Set the status bar color.
+ *
+ * @param color The **desired** [Color] to set. This may require modification if running on an
+ * API level that only supports white status bar icons.
+ * @param darkIcons Whether dark status bar icons would be preferable.
+ * @param transformColorForLightContent A lambda which will be invoked to transform [color] if
+ * dark icons were requested but are not available. Defaults to applying a black scrim.
+ *
+ * @see statusBarDarkContentEnabled
+ */
+ fun setStatusBarColor(
+ color: Color,
+ darkIcons: Boolean = color.luminance() > 0.5f,
+ transformColorForLightContent: (Color) -> Color = BlackScrimmed
+ )
+
+ /**
+ * Set the navigation bar color.
+ *
+ * @param color The **desired** [Color] to set. This may require modification if running on an
+ * API level that only supports white navigation bar icons. Additionally this will be ignored
+ * and [Color.Transparent] will be used on API 29+ where gesture navigation is preferred or the
+ * system UI automatically applies background protection in other navigation modes.
+ * @param darkIcons Whether dark navigation bar icons would be preferable.
+ * @param navigationBarContrastEnforced Whether the system should ensure that the navigation bar
+ * has enough contrast when a fully transparent background is requested. Only supported on API
+ * 29+.
+ * @param transformColorForLightContent A lambda which will be invoked to transform [color] if
+ * dark icons were requested but are not available. Defaults to applying a black scrim.
+ *
+ * @see navigationBarDarkContentEnabled
+ * @see navigationBarContrastEnforced
+ */
+ fun setNavigationBarColor(
+ color: Color,
+ darkIcons: Boolean = color.luminance() > 0.5f,
+ navigationBarContrastEnforced: Boolean = true,
+ transformColorForLightContent: (Color) -> Color = BlackScrimmed
+ )
+
+ /**
+ * Set the status and navigation bars to [color].
+ *
+ * @see setStatusBarColor
+ * @see setNavigationBarColor
+ */
+ fun setSystemBarsColor(
+ color: Color,
+ darkIcons: Boolean = color.luminance() > 0.5f,
+ isNavigationBarContrastEnforced: Boolean = true,
+ transformColorForLightContent: (Color) -> Color = BlackScrimmed
+ ) {
+ setStatusBarColor(color, darkIcons, transformColorForLightContent)
+ setNavigationBarColor(
+ color,
+ darkIcons,
+ isNavigationBarContrastEnforced,
+ transformColorForLightContent
+ )
+ }
+
+ /** Property which holds whether the status bar icons + content are 'dark' or not. */
+ var statusBarDarkContentEnabled: Boolean
+
+ /** Property which holds whether the navigation bar icons + content are 'dark' or not. */
+ var navigationBarDarkContentEnabled: Boolean
+
+ /**
+ * Property which holds whether the status & navigation bar icons + content are 'dark' or not.
+ */
+ var systemBarsDarkContentEnabled: Boolean
+ get() = statusBarDarkContentEnabled && navigationBarDarkContentEnabled
+ set(value) {
+ statusBarDarkContentEnabled = value
+ navigationBarDarkContentEnabled = value
+ }
+
+ /**
+ * Property which holds whether the system is ensuring that the navigation bar has enough
+ * contrast when a fully transparent background is requested. Only has an affect when running on
+ * Android API 29+ devices.
+ */
+ var isNavigationBarContrastEnforced: Boolean
+}
+
+/**
+ * Remembers a [SystemUiController] for the given [window].
+ *
+ * If no [window] is provided, an attempt to find the correct [Window] is made.
+ *
+ * First, if the [LocalView]'s parent is a [DialogWindowProvider], then that dialog's [Window] will
+ * be used.
+ *
+ * Second, we attempt to find [Window] for the [Activity] containing the [LocalView].
+ *
+ * If none of these are found (such as may happen in a preview), then the functionality of the
+ * returned [SystemUiController] will be degraded, but won't throw an exception.
+ */
+@Composable
+fun rememberSystemUiController(
+ window: Window? = findWindow(),
+): SystemUiController {
+ val view = LocalView.current
+ return remember(view, window) { AndroidSystemUiController(view, window) }
+}
+
+@Composable
+private fun findWindow(): Window? =
+ (LocalView.current.parent as? DialogWindowProvider)?.window
+ ?: LocalView.current.context.findWindow()
+
+private tailrec fun Context.findWindow(): Window? =
+ when (this) {
+ is Activity -> window
+ is ContextWrapper -> baseContext.findWindow()
+ else -> null
+ }
+
+/**
+ * A helper class for setting the navigation and status bar colors for a [View], gracefully
+ * degrading behavior based upon API level.
+ *
+ * Typically you would use [rememberSystemUiController] to remember an instance of this.
+ */
+internal class AndroidSystemUiController(private val view: View, private val window: Window?) :
+ SystemUiController {
+ private val windowInsetsController = window?.let { WindowCompat.getInsetsController(it, view) }
+
+ override fun setStatusBarColor(
+ color: Color,
+ darkIcons: Boolean,
+ transformColorForLightContent: (Color) -> Color
+ ) {
+ statusBarDarkContentEnabled = darkIcons
+
+ window?.statusBarColor =
+ when {
+ darkIcons && windowInsetsController?.isAppearanceLightStatusBars != true -> {
+ // If we're set to use dark icons, but our windowInsetsController call didn't
+ // succeed (usually due to API level), we instead transform the color to
+ // maintain contrast
+ transformColorForLightContent(color)
+ }
+ else -> color
+ }.toArgb()
+ }
+
+ override fun setNavigationBarColor(
+ color: Color,
+ darkIcons: Boolean,
+ navigationBarContrastEnforced: Boolean,
+ transformColorForLightContent: (Color) -> Color
+ ) {
+ navigationBarDarkContentEnabled = darkIcons
+ isNavigationBarContrastEnforced = navigationBarContrastEnforced
+
+ window?.navigationBarColor =
+ when {
+ darkIcons && windowInsetsController?.isAppearanceLightNavigationBars != true -> {
+ // If we're set to use dark icons, but our windowInsetsController call didn't
+ // succeed (usually due to API level), we instead transform the color to
+ // maintain contrast
+ transformColorForLightContent(color)
+ }
+ else -> color
+ }.toArgb()
+ }
+
+ override var isStatusBarVisible: Boolean
+ get() {
+ return ViewCompat.getRootWindowInsets(view)
+ ?.isVisible(WindowInsetsCompat.Type.statusBars()) == true
+ }
+ set(value) {
+ if (value) {
+ windowInsetsController?.show(WindowInsetsCompat.Type.statusBars())
+ } else {
+ windowInsetsController?.hide(WindowInsetsCompat.Type.statusBars())
+ }
+ }
+
+ override var isNavigationBarVisible: Boolean
+ get() {
+ return ViewCompat.getRootWindowInsets(view)
+ ?.isVisible(WindowInsetsCompat.Type.navigationBars()) == true
+ }
+ set(value) {
+ if (value) {
+ windowInsetsController?.show(WindowInsetsCompat.Type.navigationBars())
+ } else {
+ windowInsetsController?.hide(WindowInsetsCompat.Type.navigationBars())
+ }
+ }
+
+ override var statusBarDarkContentEnabled: Boolean
+ get() = windowInsetsController?.isAppearanceLightStatusBars == true
+ set(value) {
+ windowInsetsController?.isAppearanceLightStatusBars = value
+ }
+
+ override var navigationBarDarkContentEnabled: Boolean
+ get() = windowInsetsController?.isAppearanceLightNavigationBars == true
+ set(value) {
+ windowInsetsController?.isAppearanceLightNavigationBars = value
+ }
+
+ override var isNavigationBarContrastEnforced: Boolean
+ get() = Build.VERSION.SDK_INT >= 29 && window?.isNavigationBarContrastEnforced == true
+ set(value) {
+ if (Build.VERSION.SDK_INT >= 29) {
+ window?.isNavigationBarContrastEnforced = value
+ }
+ }
+}
+
+private val BlackScrim = Color(0f, 0f, 0f, 0.3f) // 30% opaque black
+private val BlackScrimmed: (Color) -> Color = { original -> BlackScrim.compositeOver(original) }
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt
new file mode 100644
index 000000000000..b8639e64e002
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 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.compose.theme
+
+import android.annotation.ColorInt
+import android.content.Context
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import com.android.internal.R
+
+/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
+val LocalAndroidColorScheme =
+ staticCompositionLocalOf<AndroidColorScheme> {
+ throw IllegalStateException(
+ "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
+ "Composable surrounded by a SystemUITheme {}."
+ )
+ }
+
+/**
+ * The Android color scheme.
+ *
+ * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
+ * most of the colors in this class will be removed in favor of their M3 counterpart.
+ */
+class AndroidColorScheme internal constructor(context: Context) {
+ val colorPrimary = getColor(context, R.attr.colorPrimary)
+ val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
+ val colorAccent = getColor(context, R.attr.colorAccent)
+ val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary)
+ val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary)
+ val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary)
+ val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant)
+ val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant)
+ val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant)
+ val colorSurface = getColor(context, R.attr.colorSurface)
+ val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight)
+ val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant)
+ val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader)
+ val colorError = getColor(context, R.attr.colorError)
+ val colorBackground = getColor(context, R.attr.colorBackground)
+ val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating)
+ val panelColorBackground = getColor(context, R.attr.panelColorBackground)
+ val textColorPrimary = getColor(context, R.attr.textColorPrimary)
+ val textColorSecondary = getColor(context, R.attr.textColorSecondary)
+ val textColorTertiary = getColor(context, R.attr.textColorTertiary)
+ val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse)
+ val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse)
+ val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse)
+ val textColorOnAccent = getColor(context, R.attr.textColorOnAccent)
+ val colorForeground = getColor(context, R.attr.colorForeground)
+ val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
+
+ private fun getColor(context: Context, attr: Int): Color {
+ val ta = context.obtainStyledAttributes(intArrayOf(attr))
+ @ColorInt val color = ta.getColor(0, 0)
+ ta.recycle()
+ return Color(color)
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt
new file mode 100644
index 000000000000..79e3d3d475a8
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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.compose.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Typography
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+
+/** The Material 3 theme that should wrap all SystemUI Composables. */
+@Composable
+fun SystemUITheme(
+ isDarkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit,
+) {
+ val context = LocalContext.current
+
+ // TODO(b/230605885): Define our typography and color scheme.
+ val colorScheme =
+ if (isDarkTheme) {
+ dynamicDarkColorScheme(context)
+ } else {
+ dynamicLightColorScheme(context)
+ }
+ val androidColorScheme = AndroidColorScheme(context)
+ val typography = Typography()
+
+ MaterialTheme(colorScheme, typography = typography) {
+ CompositionLocalProvider(
+ LocalAndroidColorScheme provides androidColorScheme,
+ ) {
+ content()
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/core/tests/Android.bp b/packages/SystemUI/compose/core/tests/Android.bp
new file mode 100644
index 000000000000..f8023e2519f8
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2022 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+// TODO(b/230606318): Make those host tests instead of device tests.
+android_test {
+ name: "SystemUIComposeCoreTests",
+ manifest: "AndroidManifest.xml",
+ test_suites: ["device-tests"],
+ sdk_version: "current",
+ certificate: "platform",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "SystemUIComposeCore",
+
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.ui_ui-test-junit4",
+ "androidx.compose.ui_ui-test-manifest",
+ ],
+
+ kotlincflags: ["-Xjvm-default=enable"],
+}
diff --git a/packages/SystemUI/compose/core/tests/AndroidManifest.xml b/packages/SystemUI/compose/core/tests/AndroidManifest.xml
new file mode 100644
index 000000000000..729ab989cdc0
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.compose.core.tests" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.systemui.compose.core.tests"
+ android:label="Tests for SystemUIComposeCore"/>
+
+</manifest> \ No newline at end of file
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/systemui/compose/theme/SystemUIThemeTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/systemui/compose/theme/SystemUIThemeTest.kt
new file mode 100644
index 000000000000..20249f66d0d0
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/systemui/compose/theme/SystemUIThemeTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.compose.theme
+
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.assertThrows
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SystemUIThemeTest {
+ @get:Rule val composeRule = createComposeRule()
+
+ @Test
+ fun testThemeShowsContent() {
+ composeRule.setContent { SystemUITheme { Text("foo") } }
+
+ composeRule.onNodeWithText("foo").assertIsDisplayed()
+ }
+
+ @Test
+ fun testAndroidColorsAreAvailableInsideTheme() {
+ composeRule.setContent {
+ SystemUITheme { Text("foo", color = LocalAndroidColorScheme.current.colorAccent) }
+ }
+
+ composeRule.onNodeWithText("foo").assertIsDisplayed()
+ }
+
+ @Test
+ fun testAccessingAndroidColorsWithoutThemeThrows() {
+ assertThrows(IllegalStateException::class.java) {
+ composeRule.setContent {
+ Text("foo", color = LocalAndroidColorScheme.current.colorAccent)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
new file mode 100644
index 000000000000..40218de94258
--- /dev/null
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2022 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUIComposeFeatures",
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "SystemUIComposeCore",
+
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.material3_material3",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml
new file mode 100644
index 000000000000..0aea99d4e960
--- /dev/null
+++ b/packages/SystemUI/compose/features/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.compose.features">
+
+
+</manifest>
diff --git a/packages/SystemUI/compose/features/TEST_MAPPING b/packages/SystemUI/compose/features/TEST_MAPPING
new file mode 100644
index 000000000000..7430acb2e900
--- /dev/null
+++ b/packages/SystemUI/compose/features/TEST_MAPPING
@@ -0,0 +1,26 @@
+{
+ "presubmit": [
+ {
+ "name": "SystemUIComposeFeaturesTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "SystemUIComposeGalleryTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
new file mode 100644
index 000000000000..c58c16259abe
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import kotlin.math.roundToInt
+
+/**
+ * This is an example Compose feature, which shows a text and a count that is incremented when
+ * clicked. We also show the max width available to this component, which is displayed either next
+ * to or below the text depending on that max width.
+ */
+@Composable
+fun ExampleFeature(text: String, modifier: Modifier = Modifier) {
+ BoxWithConstraints(modifier) {
+ val maxWidth = maxWidth
+ if (maxWidth < 600.dp) {
+ Column {
+ CounterTile(text)
+ Spacer(Modifier.size(16.dp))
+ MaxWidthTile(maxWidth)
+ }
+ } else {
+ Row {
+ CounterTile(text)
+ Spacer(Modifier.size(16.dp))
+ MaxWidthTile(maxWidth)
+ }
+ }
+ }
+}
+
+@Composable
+private fun CounterTile(text: String, modifier: Modifier = Modifier) {
+ Surface(
+ modifier,
+ color = MaterialTheme.colorScheme.primaryContainer,
+ shape = RoundedCornerShape(28.dp),
+ ) {
+ var count by remember { mutableStateOf(0) }
+ Column(
+ Modifier.clickable { count++ }.padding(16.dp),
+ ) {
+ Text(text)
+ Text("I was clicked $count times.")
+ }
+ }
+}
+
+@Composable
+private fun MaxWidthTile(maxWidth: Dp, modifier: Modifier = Modifier) {
+ Surface(
+ modifier,
+ color = MaterialTheme.colorScheme.tertiaryContainer,
+ shape = RoundedCornerShape(28.dp),
+ ) {
+ Text(
+ "The max available width to me is: ${maxWidth.value.roundToInt()}dp",
+ Modifier.padding(16.dp)
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/features/tests/Android.bp b/packages/SystemUI/compose/features/tests/Android.bp
new file mode 100644
index 000000000000..ff534bd01fd3
--- /dev/null
+++ b/packages/SystemUI/compose/features/tests/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2022 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+// TODO(b/230606318): Make those host tests instead of device tests.
+android_test {
+ name: "SystemUIComposeFeaturesTests",
+ manifest: "AndroidManifest.xml",
+ test_suites: ["device-tests"],
+ sdk_version: "current",
+ certificate: "platform",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "SystemUIComposeFeatures",
+
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.ui_ui-test-junit4",
+ "androidx.compose.ui_ui-test-manifest",
+ ],
+
+ kotlincflags: ["-Xjvm-default=enable"],
+}
diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
new file mode 100644
index 000000000000..5e54c1f353d2
--- /dev/null
+++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.compose.features.tests" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.systemui.compose.features.tests"
+ android:label="Tests for SystemUIComposeFeatures"/>
+
+</manifest> \ No newline at end of file
diff --git a/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt b/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt
new file mode 100644
index 000000000000..1c2e8fab0337
--- /dev/null
+++ b/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ExampleFeatureTest {
+ @get:Rule val composeRule = createComposeRule()
+
+ @Test
+ fun testProvidedTextIsDisplayed() {
+ composeRule.setContent { ExampleFeature("foo") }
+
+ composeRule.onNodeWithText("foo").assertIsDisplayed()
+ }
+
+ @Test
+ fun testCountIsIncreasedWhenClicking() {
+ composeRule.setContent { ExampleFeature("foo") }
+
+ composeRule.onNodeWithText("I was clicked 0 times.").assertIsDisplayed().performClick()
+ composeRule.onNodeWithText("I was clicked 1 times.").assertIsDisplayed()
+ }
+}
diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp
new file mode 100644
index 000000000000..40504dc30c33
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/Android.bp
@@ -0,0 +1,72 @@
+// Copyright (C) 2022 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUIComposeGalleryLib",
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ resource_dirs: [
+ "res",
+ ],
+
+ static_libs: [
+ "SystemUI-core",
+ "SystemUIComposeCore",
+ "SystemUIComposeFeatures",
+
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.material3_material3",
+ "androidx.compose.material_material-icons-extended",
+ "androidx.activity_activity-compose",
+ "androidx.navigation_navigation-compose",
+
+ "androidx.appcompat_appcompat",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+}
+
+android_app {
+ name: "SystemUIComposeGallery",
+ defaults: ["platform_app_defaults"],
+ manifest: "app/AndroidManifest.xml",
+
+ static_libs: [
+ "SystemUIComposeGalleryLib",
+ ],
+
+ platform_apis: true,
+ system_ext_specific: true,
+ certificate: "platform",
+ privileged: true,
+
+ optimize: {
+ proguard_flags_files: ["proguard-rules.pro"],
+ },
+
+ dxflags: ["--multi-dex"],
+}
diff --git a/packages/SystemUI/compose/gallery/AndroidManifest.xml b/packages/SystemUI/compose/gallery/AndroidManifest.xml
new file mode 100644
index 000000000000..2f30651a6acf
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/AndroidManifest.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.systemui.compose.gallery">
+ <!-- To emulate a display size and density. -->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+ <application
+ android:name="android.app.Application"
+ android:appComponentFactory="androidx.core.app.AppComponentFactory"
+ tools:replace="android:name,android:appComponentFactory">
+ <!-- Disable providers from SystemUI -->
+ <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider"
+ android:authorities="com.android.systemui.test.keyguard.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
+ android:authorities="com.android.systemui.test.keyguard.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
+ android:authorities="com.android.systemui.test.keyguard.clock.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.android.systemui.people.PeopleProvider"
+ android:authorities="com.android.systemui.test.people.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="androidx.core.content.FileProvider"
+ android:authorities="com.android.systemui.test.fileprovider.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove"/>
+ </application>
+</manifest>
diff --git a/packages/SystemUI/compose/gallery/TEST_MAPPING b/packages/SystemUI/compose/gallery/TEST_MAPPING
new file mode 100644
index 000000000000..c7f8a9216418
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "presubmit": [
+ {
+ "name": "SystemUIComposeGalleryTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
new file mode 100644
index 000000000000..1f3fd8c312d9
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.systemui.compose.gallery.app">
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.SystemUI.Gallery"
+ tools:replace="android:icon,android:theme,android:label">
+ <activity
+ android:name="com.android.systemui.compose.gallery.GalleryActivity"
+ android:exported="true"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/packages/SystemUI/compose/gallery/proguard-rules.pro b/packages/SystemUI/compose/gallery/proguard-rules.pro
new file mode 100644
index 000000000000..481bb4348141
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile \ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml b/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 000000000000..966abaff2074
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportHeight="108"
+ android:viewportWidth="108">
+ <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="85.84757"
+ android:endY="92.4963"
+ android:startX="42.9492"
+ android:startY="49.59793"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1" />
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml b/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000000..61bb79edb709
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportHeight="108"
+ android:viewportWidth="108">
+ <path
+ android:fillColor="#3DDC84"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8" />
+</vector>
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000000..03eed2533da2
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000000..03eed2533da2
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 000000000000..c209e78ecd37
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 000000000000..b2dfe3d1ba5c
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 000000000000..4f0f1d64e58b
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 000000000000..62b611da0816
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 000000000000..948a3070fe34
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000000..1b9a6956b3ac
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 000000000000..28d4b77f9f03
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000000..9287f5083623
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 000000000000..aa7d6427e6fa
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000000..9126ae37cbc3
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/values/colors.xml b/packages/SystemUI/compose/gallery/res/values/colors.xml
new file mode 100644
index 000000000000..a2fcbffc26c0
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <color name="ic_launcher_background">#FFFFFF</color>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/res/values/strings.xml b/packages/SystemUI/compose/gallery/res/values/strings.xml
new file mode 100644
index 000000000000..86bdb0568837
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <!-- Application name [CHAR LIMIT=NONE] -->
+ <string name="app_name">SystemUI Gallery</string>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/res/values/themes.xml b/packages/SystemUI/compose/gallery/res/values/themes.xml
new file mode 100644
index 000000000000..45fa1f5dfb5c
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/res/values/themes.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <style name="Theme.SystemUI.Gallery">
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+
+ <item name="android:statusBarColor" tools:targetApi="l">
+ @android:color/transparent
+ </item>
+ <item name="android:navigationBarColor" tools:targetApi="l">
+ @android:color/transparent
+ </item>
+ <item name="android:windowLightStatusBar">true</item>
+ </style>
+</resources>
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt
new file mode 100644
index 000000000000..dfa1b26f464e
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 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.compose.gallery
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.android.systemui.compose.theme.LocalAndroidColorScheme
+
+/** The screen that shows all the Material 3 colors. */
+@Composable
+fun MaterialColorsScreen() {
+ val colors = MaterialTheme.colorScheme
+ ColorsScreen(
+ listOf(
+ "primary" to colors.primary,
+ "onPrimary" to colors.onPrimary,
+ "primaryContainer" to colors.primaryContainer,
+ "onPrimaryContainer" to colors.onPrimaryContainer,
+ "inversePrimary" to colors.inversePrimary,
+ "secondary" to colors.secondary,
+ "onSecondary" to colors.onSecondary,
+ "secondaryContainer" to colors.secondaryContainer,
+ "onSecondaryContainer" to colors.onSecondaryContainer,
+ "tertiary" to colors.tertiary,
+ "onTertiary" to colors.onTertiary,
+ "tertiaryContainer" to colors.tertiaryContainer,
+ "onTertiaryContainer" to colors.onTertiaryContainer,
+ "background" to colors.background,
+ "onBackground" to colors.onBackground,
+ "surface" to colors.surface,
+ "onSurface" to colors.onSurface,
+ "surfaceVariant" to colors.surfaceVariant,
+ "onSurfaceVariant" to colors.onSurfaceVariant,
+ "inverseSurface" to colors.inverseSurface,
+ "inverseOnSurface" to colors.inverseOnSurface,
+ "error" to colors.error,
+ "onError" to colors.onError,
+ "errorContainer" to colors.errorContainer,
+ "onErrorContainer" to colors.onErrorContainer,
+ "outline" to colors.outline,
+ )
+ )
+}
+
+/** The screen that shows all the Android colors. */
+@Composable
+fun AndroidColorsScreen() {
+ val colors = LocalAndroidColorScheme.current
+ ColorsScreen(
+ listOf(
+ "colorPrimary" to colors.colorPrimary,
+ "colorPrimaryDark" to colors.colorPrimaryDark,
+ "colorAccent" to colors.colorAccent,
+ "colorAccentPrimary" to colors.colorAccentPrimary,
+ "colorAccentSecondary" to colors.colorAccentSecondary,
+ "colorAccentTertiary" to colors.colorAccentTertiary,
+ "colorAccentPrimaryVariant" to colors.colorAccentPrimaryVariant,
+ "colorAccentSecondaryVariant" to colors.colorAccentSecondaryVariant,
+ "colorAccentTertiaryVariant" to colors.colorAccentTertiaryVariant,
+ "colorSurface" to colors.colorSurface,
+ "colorSurfaceHighlight" to colors.colorSurfaceHighlight,
+ "colorSurfaceVariant" to colors.colorSurfaceVariant,
+ "colorSurfaceHeader" to colors.colorSurfaceHeader,
+ "colorError" to colors.colorError,
+ "colorBackground" to colors.colorBackground,
+ "colorBackgroundFloating" to colors.colorBackgroundFloating,
+ "panelColorBackground" to colors.panelColorBackground,
+ "textColorPrimary" to colors.textColorPrimary,
+ "textColorSecondary" to colors.textColorSecondary,
+ "textColorTertiary" to colors.textColorTertiary,
+ "textColorPrimaryInverse" to colors.textColorPrimaryInverse,
+ "textColorSecondaryInverse" to colors.textColorSecondaryInverse,
+ "textColorTertiaryInverse" to colors.textColorTertiaryInverse,
+ "textColorOnAccent" to colors.textColorOnAccent,
+ "colorForeground" to colors.colorForeground,
+ "colorForegroundInverse" to colors.colorForegroundInverse,
+ )
+ )
+}
+
+@Composable
+private fun ColorsScreen(
+ colors: List<Pair<String, Color>>,
+) {
+ LazyColumn(
+ Modifier.fillMaxWidth(),
+ ) {
+ colors.forEach { (name, color) -> item { ColorTile(color, name) } }
+ }
+}
+
+@Composable
+private fun ColorTile(
+ color: Color,
+ name: String,
+) {
+ Row(
+ Modifier.padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ val shape = RoundedCornerShape(16.dp)
+ Spacer(
+ Modifier.border(1.dp, MaterialTheme.colorScheme.onBackground, shape)
+ .background(color, shape)
+ .size(64.dp)
+ )
+ Spacer(Modifier.width(16.dp))
+ Text(name)
+ }
+}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt
new file mode 100644
index 000000000000..990d060207df
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt
@@ -0,0 +1,210 @@
+package com.android.systemui.compose.gallery
+
+import android.graphics.Point
+import android.os.UserHandle
+import android.view.Display
+import android.view.WindowManagerGlobal
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.DarkMode
+import androidx.compose.material.icons.filled.FormatSize
+import androidx.compose.material.icons.filled.FormatTextdirectionLToR
+import androidx.compose.material.icons.filled.FormatTextdirectionRToL
+import androidx.compose.material.icons.filled.InvertColors
+import androidx.compose.material.icons.filled.LightMode
+import androidx.compose.material.icons.filled.Smartphone
+import androidx.compose.material.icons.filled.Tablet
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import kotlin.math.max
+import kotlin.math.min
+
+enum class FontScale(val scale: Float) {
+ Small(0.85f),
+ Normal(1f),
+ Big(1.15f),
+ Bigger(1.30f),
+}
+
+/** A configuration panel that allows to toggle the theme, font scale and layout direction. */
+@Composable
+fun ConfigurationControls(
+ theme: Theme,
+ fontScale: FontScale,
+ layoutDirection: LayoutDirection,
+ onChangeTheme: () -> Unit,
+ onChangeLayoutDirection: () -> Unit,
+ onChangeFontScale: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ // The display we are emulating, if any.
+ var emulatedDisplayName by rememberSaveable { mutableStateOf<String?>(null) }
+ val emulatedDisplay =
+ emulatedDisplayName?.let { name -> EmulatedDisplays.firstOrNull { it.name == name } }
+
+ LaunchedEffect(emulatedDisplay) {
+ val wm = WindowManagerGlobal.getWindowManagerService()
+
+ val defaultDisplayId = Display.DEFAULT_DISPLAY
+ if (emulatedDisplay == null) {
+ wm.clearForcedDisplayDensityForUser(defaultDisplayId, UserHandle.myUserId())
+ wm.clearForcedDisplaySize(defaultDisplayId)
+ } else {
+ val density = emulatedDisplay.densityDpi
+
+ // Emulate the display and make sure that we use the maximum available space possible.
+ val initialSize = Point()
+ wm.getInitialDisplaySize(defaultDisplayId, initialSize)
+ val width = emulatedDisplay.width
+ val height = emulatedDisplay.height
+ val minOfSize = min(width, height)
+ val maxOfSize = max(width, height)
+ if (initialSize.x < initialSize.y) {
+ wm.setForcedDisplaySize(defaultDisplayId, minOfSize, maxOfSize)
+ } else {
+ wm.setForcedDisplaySize(defaultDisplayId, maxOfSize, minOfSize)
+ }
+ wm.setForcedDisplayDensityForUser(defaultDisplayId, density, UserHandle.myUserId())
+ }
+ }
+
+ // TODO(b/231131244): Fork FlowRow from Accompanist and use that instead to make sure that users
+ // don't miss any available configuration.
+ LazyRow(modifier) {
+ // Dark/light theme.
+ item {
+ TextButton(onChangeTheme) {
+ val text: String
+ val icon: ImageVector
+
+ when (theme) {
+ Theme.System -> {
+ icon = Icons.Default.InvertColors
+ text = "System"
+ }
+ Theme.Dark -> {
+ icon = Icons.Default.DarkMode
+ text = "Dark"
+ }
+ Theme.Light -> {
+ icon = Icons.Default.LightMode
+ text = "Light"
+ }
+ }
+
+ Icon(icon, null)
+ Spacer(Modifier.width(8.dp))
+ Text(text)
+ }
+ }
+
+ // Font scale.
+ item {
+ TextButton(onChangeFontScale) {
+ Icon(Icons.Default.FormatSize, null)
+ Spacer(Modifier.width(8.dp))
+
+ Text(fontScale.name)
+ }
+ }
+
+ // Layout direction.
+ item {
+ TextButton(onChangeLayoutDirection) {
+ when (layoutDirection) {
+ LayoutDirection.Ltr -> {
+ Icon(Icons.Default.FormatTextdirectionLToR, null)
+ Spacer(Modifier.width(8.dp))
+ Text("LTR")
+ }
+ LayoutDirection.Rtl -> {
+ Icon(Icons.Default.FormatTextdirectionRToL, null)
+ Spacer(Modifier.width(8.dp))
+ Text("RTL")
+ }
+ }
+ }
+ }
+
+ // Display emulation.
+ EmulatedDisplays.forEach { display ->
+ item {
+ DisplayButton(
+ display,
+ emulatedDisplay == display,
+ { emulatedDisplayName = it?.name },
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun DisplayButton(
+ display: EmulatedDisplay,
+ selected: Boolean,
+ onChangeEmulatedDisplay: (EmulatedDisplay?) -> Unit,
+) {
+ val onClick = {
+ if (selected) {
+ onChangeEmulatedDisplay(null)
+ } else {
+ onChangeEmulatedDisplay(display)
+ }
+ }
+
+ val content: @Composable RowScope.() -> Unit = {
+ Icon(display.icon, null)
+ Spacer(Modifier.width(8.dp))
+ Text(display.name)
+ }
+
+ if (selected) {
+ Button(onClick, contentPadding = ButtonDefaults.TextButtonContentPadding, content = content)
+ } else {
+ TextButton(onClick, content = content)
+ }
+}
+
+/** The displays that can be emulated from this Gallery app. */
+private val EmulatedDisplays =
+ listOf(
+ EmulatedDisplay(
+ "Phone",
+ Icons.Default.Smartphone,
+ width = 1440,
+ height = 3120,
+ densityDpi = 560,
+ ),
+ EmulatedDisplay(
+ "Tablet",
+ Icons.Default.Tablet,
+ width = 2560,
+ height = 1600,
+ densityDpi = 320,
+ ),
+ )
+
+private data class EmulatedDisplay(
+ val name: String,
+ val icon: ImageVector,
+ val width: Int,
+ val height: Int,
+ val densityDpi: Int,
+)
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
new file mode 100644
index 000000000000..6e1721490f98
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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.compose.gallery
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.systemui.ExampleFeature
+
+/** The screen that shows ExampleFeature. */
+@Composable
+fun ExampleFeatureScreen(modifier: Modifier = Modifier) {
+ Column(modifier) { ExampleFeature("This is an example feature!") }
+}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt
new file mode 100644
index 000000000000..bb2d2feba39f
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 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.compose.gallery
+
+import android.app.UiModeManager
+import android.content.Context
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.graphics.Color
+import androidx.core.view.WindowCompat
+import com.android.systemui.compose.rememberSystemUiController
+
+class GalleryActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
+
+ setContent {
+ var theme by rememberSaveable { mutableStateOf(Theme.System) }
+ val onChangeTheme = {
+ // Change to the next theme for a toggle behavior.
+ theme =
+ when (theme) {
+ Theme.System -> Theme.Dark
+ Theme.Dark -> Theme.Light
+ Theme.Light -> Theme.System
+ }
+ }
+
+ val isSystemInDarkTheme = isSystemInDarkTheme()
+ val isDark = theme == Theme.Dark || (theme == Theme.System && isSystemInDarkTheme)
+ val useDarkIcons = !isDark
+ val systemUiController = rememberSystemUiController()
+ SideEffect {
+ systemUiController.setSystemBarsColor(
+ color = Color.Transparent,
+ darkIcons = useDarkIcons,
+ )
+
+ uiModeManager.setApplicationNightMode(
+ when (theme) {
+ Theme.System -> UiModeManager.MODE_NIGHT_AUTO
+ Theme.Dark -> UiModeManager.MODE_NIGHT_YES
+ Theme.Light -> UiModeManager.MODE_NIGHT_NO
+ }
+ )
+ }
+
+ GalleryApp(theme, onChangeTheme)
+ }
+ }
+}
+
+enum class Theme {
+ System,
+ Dark,
+ Light,
+}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
new file mode 100644
index 000000000000..c341867bfb59
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
@@ -0,0 +1,125 @@
+package com.android.systemui.compose.gallery
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.rememberNavController
+import com.android.systemui.compose.theme.SystemUITheme
+
+/** The gallery app screens. */
+object GalleryAppScreens {
+ val Typography = ChildScreen("typography") { TypographyScreen() }
+ val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() }
+ val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() }
+ val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() }
+
+ val Home =
+ ParentScreen(
+ "home",
+ mapOf(
+ "Typography" to Typography,
+ "Material colors" to MaterialColors,
+ "Android colors" to AndroidColors,
+ "Example feature" to ExampleFeature,
+ )
+ )
+}
+
+/** The main content of the app, that shows [GalleryAppScreens.Home] by default. */
+@Composable
+private fun MainContent() {
+ Box(Modifier.fillMaxSize()) {
+ val navController = rememberNavController()
+ NavHost(
+ navController = navController,
+ startDestination = GalleryAppScreens.Home.identifier,
+ ) {
+ screen(GalleryAppScreens.Home, navController)
+ }
+ }
+}
+
+/**
+ * The top-level composable shown when starting the app. This composable always shows a
+ * [ConfigurationControls] at the top of the screen, above the [MainContent].
+ */
+@Composable
+fun GalleryApp(
+ theme: Theme,
+ onChangeTheme: () -> Unit,
+) {
+ val systemFontScale = LocalDensity.current.fontScale
+ var fontScale: FontScale by remember {
+ mutableStateOf(
+ FontScale.values().firstOrNull { it.scale == systemFontScale } ?: FontScale.Normal
+ )
+ }
+ val context = LocalContext.current
+ val density = Density(context.resources.displayMetrics.density, fontScale.scale)
+ val onChangeFontScale = {
+ fontScale =
+ when (fontScale) {
+ FontScale.Small -> FontScale.Normal
+ FontScale.Normal -> FontScale.Big
+ FontScale.Big -> FontScale.Bigger
+ FontScale.Bigger -> FontScale.Small
+ }
+ }
+
+ val systemLayoutDirection = LocalLayoutDirection.current
+ var layoutDirection by remember { mutableStateOf(systemLayoutDirection) }
+ val onChangeLayoutDirection = {
+ layoutDirection =
+ when (layoutDirection) {
+ LayoutDirection.Ltr -> LayoutDirection.Rtl
+ LayoutDirection.Rtl -> LayoutDirection.Ltr
+ }
+ }
+
+ CompositionLocalProvider(
+ LocalDensity provides density,
+ LocalLayoutDirection provides layoutDirection,
+ ) {
+ SystemUITheme {
+ Surface(
+ Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.background,
+ ) {
+ Column(Modifier.fillMaxSize().systemBarsPadding().padding(16.dp)) {
+ ConfigurationControls(
+ theme,
+ fontScale,
+ layoutDirection,
+ onChangeTheme,
+ onChangeLayoutDirection,
+ onChangeFontScale,
+ )
+
+ Spacer(Modifier.height(4.dp))
+
+ MainContent()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
new file mode 100644
index 000000000000..467dac044b79
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 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.compose.gallery
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.navigation
+
+/**
+ * A screen in an app. It is either an [ParentScreen] which lists its child screens to navigate to
+ * them or a [ChildScreen] which shows some content.
+ */
+sealed class Screen(val identifier: String)
+
+class ParentScreen(
+ identifier: String,
+ val children: Map<String, Screen>,
+) : Screen(identifier)
+
+class ChildScreen(
+ identifier: String,
+ val content: @Composable (NavController) -> Unit,
+) : Screen(identifier)
+
+/** Create the navigation graph for [screen]. */
+fun NavGraphBuilder.screen(screen: Screen, navController: NavController) {
+ when (screen) {
+ is ChildScreen -> composable(screen.identifier) { screen.content(navController) }
+ is ParentScreen -> {
+ val menuRoute = "${screen.identifier}_menu"
+ navigation(startDestination = menuRoute, route = screen.identifier) {
+ // The menu to navigate to one of the children screens.
+ composable(menuRoute) { ScreenMenu(screen, navController) }
+
+ // The content of the child screens.
+ screen.children.forEach { (_, child) -> screen(child, navController) }
+ }
+ }
+ }
+}
+
+@Composable
+private fun ScreenMenu(
+ screen: ParentScreen,
+ navController: NavController,
+) {
+ LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ screen.children.forEach { (name, child) ->
+ item {
+ Surface(
+ Modifier.fillMaxWidth(),
+ color = MaterialTheme.colorScheme.secondaryContainer,
+ shape = CircleShape,
+ ) {
+ Column(
+ Modifier.clickable { navController.navigate(child.identifier) }
+ .padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(name)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt
new file mode 100644
index 000000000000..147025ed1d60
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.compose.gallery
+
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextOverflow
+
+/** The screen that shows the Material text styles. */
+@Composable
+fun TypographyScreen() {
+ val typography = MaterialTheme.typography
+
+ Column(
+ Modifier.fillMaxSize()
+ .horizontalScroll(rememberScrollState())
+ .verticalScroll(rememberScrollState()),
+ ) {
+ FontLine("displayLarge", typography.displayLarge)
+ FontLine("displayMedium", typography.displayMedium)
+ FontLine("displaySmall", typography.displaySmall)
+ FontLine("headlineLarge", typography.headlineLarge)
+ FontLine("headlineMedium", typography.headlineMedium)
+ FontLine("headlineSmall", typography.headlineSmall)
+ FontLine("titleLarge", typography.titleLarge)
+ FontLine("titleMedium", typography.titleMedium)
+ FontLine("titleSmall", typography.titleSmall)
+ FontLine("bodyLarge", typography.bodyLarge)
+ FontLine("bodyMedium", typography.bodyMedium)
+ FontLine("bodySmall", typography.bodySmall)
+ FontLine("labelLarge", typography.labelLarge)
+ FontLine("labelMedium", typography.labelMedium)
+ FontLine("labelSmall", typography.labelSmall)
+ }
+}
+
+@Composable
+private fun FontLine(name: String, style: TextStyle) {
+ Text(
+ "$name (${style.fontSize}/${style.lineHeight}, W${style.fontWeight?.weight})",
+ style = style,
+ maxLines = 1,
+ overflow = TextOverflow.Visible,
+ )
+}
diff --git a/packages/SystemUI/compose/gallery/tests/Android.bp b/packages/SystemUI/compose/gallery/tests/Android.bp
new file mode 100644
index 000000000000..3e01f7d2c431
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/tests/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2022 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+ name: "SystemUIComposeGalleryTests",
+ manifest: "AndroidManifest.xml",
+ test_suites: ["device-tests"],
+ sdk_version: "current",
+ certificate: "platform",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "SystemUIComposeGalleryLib",
+
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.ui_ui-test-junit4",
+ "androidx.compose.ui_ui-test-manifest",
+ ],
+
+ kotlincflags: ["-Xjvm-default=enable"],
+}
diff --git a/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml b/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml
new file mode 100644
index 000000000000..5eeb3ad24e5a
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.compose.gallery.tests" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.systemui.compose.gallery.tests"
+ android:label="Tests for SystemUIComposeGallery"/>
+
+</manifest> \ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt b/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt
new file mode 100644
index 000000000000..66ecc8d4fde5
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.compose.gallery
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.systemui.compose.theme.SystemUITheme
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ScreenshotsTests {
+ @get:Rule val composeRule = createComposeRule()
+
+ @Test
+ fun exampleFeatureScreenshotTest() {
+ // TODO(b/230832101): Wire this with the screenshot diff testing infra. We should reuse the
+ // configuration of the features in the gallery app to populate the UIs.
+ composeRule.setContent { SystemUITheme { ExampleFeatureScreen() } }
+ }
+}
diff --git a/packages/SystemUI/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp
new file mode 100644
index 000000000000..293e51f68b98
--- /dev/null
+++ b/packages/SystemUI/compose/testing/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2022 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUIComposeTesting",
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "SystemUIComposeCore",
+ "SystemUIScreenshotLib",
+
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.material3_material3",
+ "androidx.compose.ui_ui-test-junit4",
+ "androidx.compose.ui_ui-test-manifest",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/compose/testing/AndroidManifest.xml b/packages/SystemUI/compose/testing/AndroidManifest.xml
new file mode 100644
index 000000000000..b1f7c3be2796
--- /dev/null
+++ b/packages/SystemUI/compose/testing/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.systemui.testing.compose">
+ <application
+ android:appComponentFactory="androidx.core.app.AppComponentFactory"
+ tools:replace="android:appComponentFactory">
+ </application>
+</manifest>
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
new file mode 100644
index 000000000000..e611e8bf0068
--- /dev/null
+++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 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.testing.compose
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onRoot
+import com.android.systemui.compose.theme.SystemUITheme
+import com.android.systemui.testing.screenshot.ScreenshotActivity
+import com.android.systemui.testing.screenshot.SystemUIGoldenImagePathManager
+import com.android.systemui.testing.screenshot.UnitTestBitmapMatcher
+import com.android.systemui.testing.screenshot.drawIntoBitmap
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.MaterialYouColorsRule
+import platform.test.screenshot.ScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** A rule for Compose screenshot diff tests. */
+class ComposeScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+ private val colorsRule = MaterialYouColorsRule()
+ private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
+ private val screenshotRule =
+ ScreenshotTestRule(
+ SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ )
+ private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
+ private val delegateRule =
+ RuleChain.outerRule(colorsRule)
+ .around(deviceEmulationRule)
+ .around(screenshotRule)
+ .around(composeRule)
+ private val matcher = UnitTestBitmapMatcher
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return delegateRule.apply(base, description)
+ }
+
+ /**
+ * Compare [content] with the golden image identified by [goldenIdentifier] in the context of
+ * [testSpec].
+ */
+ fun screenshotTest(
+ goldenIdentifier: String,
+ content: @Composable () -> Unit,
+ ) {
+ // Make sure that the activity draws full screen and fits the whole display instead of the
+ // system bars.
+ val activity = composeRule.activity
+ activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
+
+ // Set the content using the AndroidComposeRule to make sure that the Activity is set up
+ // correctly.
+ composeRule.setContent {
+ SystemUITheme {
+ Surface(
+ color = MaterialTheme.colorScheme.background,
+ ) {
+ content()
+ }
+ }
+ }
+ composeRule.waitForIdle()
+
+ val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
+ screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index 89d6fb5f062f..acbea1beeae3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -29,7 +29,7 @@ import javax.inject.Inject
/**
* Translates items away/towards the hinge when the device is opened/closed. This is controlled by
- * the set of ids, which also dictact which direction to move and when, via a filter function.
+ * the set of ids, which also dictate which direction to move and when, via a filter function.
*/
@SysUIUnfoldScope
class KeyguardUnfoldTransition
@@ -55,7 +55,9 @@ constructor(
ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever),
ViewIdToTranslate(
R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
- ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever)),
+ ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever),
+ ViewIdToTranslate(R.id.start_button, LEFT, filterNever),
+ ViewIdToTranslate(R.id.end_button, RIGHT, filterNever)),
progressProvider = unfoldProgressProvider)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f07d9ce21563..98946ac5b0ee 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -214,7 +214,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* If no cancel signal has been received after this amount of time, set the biometric running
* state to stopped to allow Keyguard to retry authentication.
*/
- private static final int DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000;
+ @VisibleForTesting
+ protected static final int DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000;
private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName(
"com.android.settings", "com.android.settings.FallbackHome");
@@ -332,10 +333,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final int HAL_ERROR_RETRY_TIMEOUT = 500; // ms
private static final int HAL_ERROR_RETRY_MAX = 20;
- private final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived;
+ @VisibleForTesting
+ protected final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived;
private final Runnable mFaceCancelNotReceived = this::onFaceCancelNotReceived;
+ @VisibleForTesting
+ protected Handler getHandler() {
+ return mHandler;
+ }
private final Handler mHandler;
private SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray();
@@ -723,6 +729,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void handleFingerprintAuthFailed() {
Assert.isMainThread();
+ if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
+ Log.d(TAG, "handleFingerprintAuthFailed()"
+ + " triggered while waiting for cancellation, removing watchdog");
+ mHandler.removeCallbacks(mFpCancelNotReceived);
+ }
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -753,6 +764,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
+ if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
+ Log.d(TAG, "handleFingerprintAuthenticated()"
+ + " triggered while waiting for cancellation, removing watchdog");
+ mHandler.removeCallbacks(mFpCancelNotReceived);
+ }
try {
final int userId;
try {
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index 835025bbfc88..e82d0ea85490 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -16,8 +16,6 @@
package com.android.systemui.charging;
-import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTERY_LEVEL;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -32,13 +30,14 @@ import android.view.WindowManager;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.ripple.RippleShader.RippleShape;
/**
* A WirelessChargingAnimation is a view containing view + animation for wireless charging.
* @hide
*/
public class WirelessChargingAnimation {
-
+ public static final int UNKNOWN_BATTERY_LEVEL = -1;
public static final long DURATION = 1500;
private static final String TAG = "WirelessChargingView";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -58,11 +57,12 @@ public class WirelessChargingAnimation {
* before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}.
* @hide
*/
- public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper,
+ private WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper,
int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing,
- UiEventLogger uiEventLogger) {
+ RippleShape rippleShape, UiEventLogger uiEventLogger) {
mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
- transmittingBatteryLevel, batteryLevel, callback, isDozing, uiEventLogger);
+ transmittingBatteryLevel, batteryLevel, callback, isDozing,
+ rippleShape, uiEventLogger);
}
/**
@@ -72,9 +72,10 @@ public class WirelessChargingAnimation {
*/
public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
@Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel,
- Callback callback, boolean isDozing, UiEventLogger uiEventLogger) {
+ Callback callback, boolean isDozing, RippleShape rippleShape,
+ UiEventLogger uiEventLogger) {
return new WirelessChargingAnimation(context, looper, transmittingBatteryLevel,
- batteryLevel, callback, isDozing, uiEventLogger);
+ batteryLevel, callback, isDozing, rippleShape, uiEventLogger);
}
/**
@@ -82,9 +83,10 @@ public class WirelessChargingAnimation {
* battery level without charging number shown.
*/
public static WirelessChargingAnimation makeChargingAnimationWithNoBatteryLevel(
- @NonNull Context context, UiEventLogger uiEventLogger) {
+ @NonNull Context context, RippleShape rippleShape, UiEventLogger uiEventLogger) {
return makeWirelessChargingAnimation(context, null,
- UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, null, false, uiEventLogger);
+ UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, null, false,
+ rippleShape, uiEventLogger);
}
/**
@@ -121,10 +123,10 @@ public class WirelessChargingAnimation {
public WirelessChargingView(Context context, @Nullable Looper looper,
int transmittingBatteryLevel, int batteryLevel, Callback callback,
- boolean isDozing, UiEventLogger uiEventLogger) {
+ boolean isDozing, RippleShape rippleShape, UiEventLogger uiEventLogger) {
mCallback = callback;
mNextView = new WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel,
- isDozing);
+ isDozing, rippleShape);
mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
mUiEventLogger = uiEventLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 65400c22ebd7..47ea27ff8ccb 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -33,7 +33,7 @@ import android.widget.TextView;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.ripple.RippleShader;
+import com.android.systemui.ripple.RippleShader.RippleShape;
import com.android.systemui.ripple.RippleView;
import java.text.NumberFormat;
@@ -41,37 +41,36 @@ import java.text.NumberFormat;
/**
* @hide
*/
-public class WirelessChargingLayout extends FrameLayout {
- public static final int UNKNOWN_BATTERY_LEVEL = -1;
+final class WirelessChargingLayout extends FrameLayout {
private static final long RIPPLE_ANIMATION_DURATION = 1500;
private static final int SCRIM_COLOR = 0x4C000000;
private static final int SCRIM_FADE_DURATION = 300;
private RippleView mRippleView;
- public WirelessChargingLayout(Context context) {
+ WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel,
+ boolean isDozing, RippleShape rippleShape) {
super(context);
- init(context, null, false);
+ init(context, null, transmittingBatteryLevel, batteryLevel, isDozing, rippleShape);
}
- public WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel,
- boolean isDozing) {
+ private WirelessChargingLayout(Context context) {
super(context);
- init(context, null, transmittingBatteryLevel, batteryLevel, isDozing);
+ init(context, null, /* isDozing= */ false, RippleShape.CIRCLE);
}
- public WirelessChargingLayout(Context context, AttributeSet attrs) {
+ private WirelessChargingLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- init(context, attrs, false);
+ init(context, attrs, /* isDozing= */false, RippleShape.CIRCLE);
}
- private void init(Context c, AttributeSet attrs, boolean isDozing) {
- init(c, attrs, -1, -1, false);
+ private void init(Context c, AttributeSet attrs, boolean isDozing, RippleShape rippleShape) {
+ init(c, attrs, -1, -1, isDozing, rippleShape);
}
private void init(Context context, AttributeSet attrs, int transmittingBatteryLevel,
- int batteryLevel, boolean isDozing) {
+ int batteryLevel, boolean isDozing, RippleShape rippleShape) {
final boolean showTransmittingBatteryLevel =
- (transmittingBatteryLevel != UNKNOWN_BATTERY_LEVEL);
+ (transmittingBatteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL);
// set style based on background
int style = R.style.ChargingAnim_WallpaperBackground;
@@ -84,7 +83,7 @@ public class WirelessChargingLayout extends FrameLayout {
// amount of battery:
final TextView percentage = findViewById(R.id.wireless_charging_percentage);
- if (batteryLevel != UNKNOWN_BATTERY_LEVEL) {
+ if (batteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL) {
percentage.setText(NumberFormat.getPercentInstance().format(batteryLevel / 100f));
percentage.setAlpha(0);
}
@@ -138,8 +137,7 @@ public class WirelessChargingLayout extends FrameLayout {
animatorSetScrim.start();
mRippleView = findViewById(R.id.wireless_charging_ripple);
- // TODO: Make rounded box shape if the device is tablet.
- mRippleView.setupShader(RippleShader.RippleShape.CIRCLE);
+ mRippleView.setupShader(rippleShape);
OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
@@ -233,8 +231,12 @@ public class WirelessChargingLayout extends FrameLayout {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
mRippleView.setCenter(width * 0.5f, height * 0.5f);
- float maxSize = Math.max(width, height);
- mRippleView.setMaxSize(maxSize, maxSize);
+ if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
+ mRippleView.setMaxSize(width * 1.5f, height * 1.5f);
+ } else {
+ float maxSize = Math.max(width, height);
+ mRippleView.setMaxSize(maxSize, maxSize);
+ }
mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
android.R.attr.colorAccent).getDefaultColor());
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index e16ac08c88f8..c21e36ab6ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -97,6 +97,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -132,7 +133,7 @@ public class ClipboardOverlayController {
private static final int FONT_SEARCH_STEP_PX = 4;
private final Context mContext;
- private final UiEventLogger mUiEventLogger;
+ private final ClipboardLogger mClipboardLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DisplayManager mDisplayManager;
private final DisplayMetrics mDisplayMetrics;
@@ -181,7 +182,7 @@ public class ClipboardOverlayController {
final Context displayContext = context.createDisplayContext(getDefaultDisplay());
mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
- mUiEventLogger = uiEventLogger;
+ mClipboardLogger = new ClipboardLogger(uiEventLogger);
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
@@ -231,7 +232,7 @@ public class ClipboardOverlayController {
@Override
public void onSwipeDismissInitiated(Animator animator) {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
mExitAnimator = animator;
}
@@ -249,7 +250,7 @@ public class ClipboardOverlayController {
});
mDismissButton.setOnClickListener(view -> {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
animateOut();
});
@@ -285,7 +286,8 @@ public class ClipboardOverlayController {
int newDisplayId) {
if (mContext.getResources().getConfiguration().orientation
!= mOrientation) {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+ mClipboardLogger.logSessionComplete(
+ CLIPBOARD_OVERLAY_DISMISSED_OTHER);
hideImmediate();
}
}
@@ -300,7 +302,7 @@ public class ClipboardOverlayController {
});
mTimeoutHandler.setOnTimeoutRunnable(() -> {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_TIMED_OUT);
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
animateOut();
});
@@ -308,7 +310,7 @@ public class ClipboardOverlayController {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
animateOut();
}
}
@@ -320,7 +322,7 @@ public class ClipboardOverlayController {
@Override
public void onReceive(Context context, Intent intent) {
if (SCREENSHOT_ACTION.equals(intent.getAction())) {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
animateOut();
}
}
@@ -390,7 +392,7 @@ public class ClipboardOverlayController {
mContext.getString(R.string.clipboard_send_nearby_description));
mRemoteCopyChip.setVisibility(View.VISIBLE);
mRemoteCopyChip.setOnClickListener((v) -> {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
mContext.startActivity(remoteCopyIntent);
animateOut();
});
@@ -450,7 +452,7 @@ public class ClipboardOverlayController {
chip.setContentDescription(action.getTitle());
chip.setIcon(action.getIcon(), false);
chip.setPendingIntent(action.getActionIntent(), () -> {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
animateOut();
});
chip.setAlpha(1);
@@ -486,7 +488,7 @@ public class ClipboardOverlayController {
touchRegion.op(tmpRect, Region.Op.UNION);
if (!touchRegion.contains(
(int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
animateOut();
}
}
@@ -497,7 +499,7 @@ public class ClipboardOverlayController {
}
private void editImage(Uri uri) {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_EDIT_TAPPED);
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
String editorPackage = mContext.getString(R.string.config_screenshotEditor);
Intent editIntent = new Intent(Intent.ACTION_EDIT);
if (!TextUtils.isEmpty(editorPackage)) {
@@ -512,7 +514,7 @@ public class ClipboardOverlayController {
}
private void editText() {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_EDIT_TAPPED);
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
Intent editIntent = new Intent(mContext, EditTextActivity.class);
editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivity(editIntent);
@@ -520,13 +522,15 @@ public class ClipboardOverlayController {
}
private void shareContent(ClipData clip) {
- mUiEventLogger.log(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
- shareIntent.putExtra(Intent.EXTRA_TEXT, clip.getItemAt(0).getText().toString());
shareIntent.setDataAndType(
clip.getItemAt(0).getUri(), clip.getDescription().getMimeType(0));
- shareIntent.putExtra(Intent.EXTRA_STREAM, clip.getItemAt(0).getUri());
- shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ shareIntent.putExtra(Intent.EXTRA_TEXT, clip.getItemAt(0).getText().toString());
+ if (clip.getItemAt(0).getUri() != null) {
+ shareIntent.putExtra(Intent.EXTRA_STREAM, clip.getItemAt(0).getUri());
+ shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
Intent chooserIntent = Intent.createChooser(shareIntent, null)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -864,6 +868,7 @@ public class ClipboardOverlayController {
mRemoteCopyChip.setVisibility(View.GONE);
resetActionChips();
mTimeoutHandler.cancelTimeout();
+ mClipboardLogger.reset();
}
@MainThread
@@ -969,4 +974,24 @@ public class ClipboardOverlayController {
mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
}
}
+
+ static class ClipboardLogger {
+ private final UiEventLogger mUiEventLogger;
+ private boolean mGuarded = false;
+
+ ClipboardLogger(UiEventLogger uiEventLogger) {
+ mUiEventLogger = uiEventLogger;
+ }
+
+ void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
+ if (!mGuarded) {
+ mGuarded = true;
+ mUiEventLogger.log(event);
+ }
+ }
+
+ void reset() {
+ mGuarded = false;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index de7bf28c01d6..55c1806e1899 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -215,16 +215,15 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
final boolean cameraBlocked = mSensorPrivacyController
.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA);
@DreamOverlayStatusBarView.StatusIconType int iconType = Resources.ID_NULL;
- if (micBlocked && cameraBlocked) {
- iconType = DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED;
- } else if (!micBlocked && cameraBlocked) {
- iconType = DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED;
- } else if (micBlocked && !cameraBlocked) {
- iconType = DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED;
- }
- if (iconType != Resources.ID_NULL) {
- showIcon(iconType, true);
- }
+ showIcon(
+ DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED,
+ !micBlocked && cameraBlocked);
+ showIcon(
+ DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED,
+ micBlocked && !cameraBlocked);
+ showIcon(
+ DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED,
+ micBlocked && cameraBlocked);
}
private String buildNotificationsContentDescription(int notificationCount) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 54571448c981..29bb2f42cca5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -163,7 +163,8 @@ public interface Complication {
COMPLICATION_TYPE_WEATHER,
COMPLICATION_TYPE_AIR_QUALITY,
COMPLICATION_TYPE_CAST_INFO,
- COMPLICATION_TYPE_HOME_CONTROLS
+ COMPLICATION_TYPE_HOME_CONTROLS,
+ COMPLICATION_TYPE_SMARTSPACE
})
@Retention(RetentionPolicy.SOURCE)
@interface ComplicationType {}
@@ -175,6 +176,7 @@ public interface Complication {
int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3;
int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
int COMPLICATION_TYPE_HOME_CONTROLS = 1 << 5;
+ int COMPLICATION_TYPE_SMARTSPACE = 1 << 6;
/**
* The {@link Host} interface specifies a way a {@link Complication} to communicate with its
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
index dcab90fe7ab9..d5db63dc9093 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
@@ -21,6 +21,7 @@ import static com.android.systemui.dreams.complication.Complication.COMPLICATION
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_SMARTSPACE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
@@ -51,6 +52,8 @@ public class ComplicationUtils {
return COMPLICATION_TYPE_CAST_INFO;
case DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS:
return COMPLICATION_TYPE_HOME_CONTROLS;
+ case DreamBackend.COMPLICATION_TYPE_SMARTSPACE:
+ return COMPLICATION_TYPE_SMARTSPACE;
default:
return COMPLICATION_TYPE_NONE;
}
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 ac6edba6b3fa..567bdbc01170 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
@@ -52,6 +52,11 @@ public class SmartSpaceComplication implements Complication {
return mViewHolderProvider.get();
}
+ @Override
+ public int getRequiredTypeAvailability() {
+ return COMPLICATION_TYPE_SMARTSPACE;
+ }
+
/**
* {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with
* SystemUI.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 0dc07ac35e3d..a65aed23f5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -185,6 +185,7 @@ public class Flags {
new ReleasedFlag(1000);
public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001);
+ public static final UnreleasedFlag ROUNDED_BOX_RIPPLE = new UnreleasedFlag(1002, false);
// 1100 - windowing
@Keep
@@ -207,6 +208,14 @@ public class Flags {
public static final SysPropBooleanFlag HIDE_NAVBAR_WINDOW =
new SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false);
+ @Keep
+ public static final SysPropBooleanFlag WM_DESKTOP_WINDOWING =
+ new SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false);
+
+ @Keep
+ public static final SysPropBooleanFlag WM_CAPTION_ON_SHELL =
+ new SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false);
+
// 1200 - predictive back
@Keep
public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 3202ecb9a287..df44957ec591 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -36,6 +36,7 @@ import com.android.systemui.util.kotlin.getOrNull
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
/** Home controls quick affordance data source. */
@@ -50,7 +51,13 @@ constructor(
private val appContext = context.applicationContext
override val state: Flow<KeyguardQuickAffordanceConfig.State> =
- stateInternal(component.getControlsListingController().getOrNull())
+ component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
+ if (canShowWhileLocked) {
+ stateInternal(component.getControlsListingController().getOrNull())
+ } else {
+ flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ }
+ }
override fun onQuickAffordanceClicked(
animationController: ActivityLaunchAnimator.Controller?,
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index dc23684dd517..6124e10144f2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -22,17 +22,11 @@ import com.android.systemui.log.dagger.LogModule
import com.android.systemui.util.collection.RingBuffer
import com.google.errorprone.annotations.CompileTimeConstant
import java.io.PrintWriter
-import java.text.SimpleDateFormat
-import java.util.Arrays.stream
-import java.util.Locale
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.BlockingQueue
import kotlin.concurrent.thread
import kotlin.math.max
-const val UNBOUNDED_STACK_TRACE = -1
-const val NESTED_TRACE_DEPTH = 10
-
/**
* A simple ring buffer of recyclable log messages
*
@@ -74,18 +68,12 @@ const val NESTED_TRACE_DEPTH = 10
* @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
* out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
* the maximum, it behaves like a ring buffer.
- * @param rootStackTraceDepth The number of stack trace elements to be logged for an exception when
- * the logBuffer is dumped. Defaulted to -1 [UNBOUNDED_STACK_TRACE] to print the entire stack trace.
- * @param nestedStackTraceDepth The number of stack trace elements to be logged for any nested
- * exceptions present in [Throwable.cause] or [Throwable.suppressedExceptions].
*/
class LogBuffer @JvmOverloads constructor(
private val name: String,
private val maxSize: Int,
private val logcatEchoTracker: LogcatEchoTracker,
private val systrace: Boolean = true,
- private val rootStackTraceDepth: Int = UNBOUNDED_STACK_TRACE,
- private val nestedStackTraceDepth: Int = NESTED_TRACE_DEPTH,
) {
private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
@@ -236,7 +224,7 @@ class LogBuffer @JvmOverloads constructor(
val iterationStart = if (tailLength <= 0) { 0 } else { max(0, buffer.size - tailLength) }
for (i in iterationStart until buffer.size) {
- dumpMessage(buffer[i], pw)
+ buffer[i].dump(pw)
}
}
@@ -264,76 +252,6 @@ class LogBuffer @JvmOverloads constructor(
}
}
- private fun dumpMessage(
- message: LogMessage,
- pw: PrintWriter
- ) {
- val formattedTimestamp = DATE_FORMAT.format(message.timestamp)
- val shortLevel = message.level.shortString
- val messageToPrint = message.messagePrinter(message)
- val tag = message.tag
- printLikeLogcat(pw, formattedTimestamp, shortLevel, tag, messageToPrint)
- message.exception?.let { ex ->
- printException(
- pw,
- formattedTimestamp,
- shortLevel,
- ex,
- tag,
- stackTraceDepth = rootStackTraceDepth)
- }
- }
-
- private fun printException(
- pw: PrintWriter,
- timestamp: String,
- level: String,
- exception: Throwable,
- tag: String,
- exceptionMessagePrefix: String = "",
- stackTraceDepth: Int = UNBOUNDED_STACK_TRACE
- ) {
- val message = "$exceptionMessagePrefix$exception"
- printLikeLogcat(pw, timestamp, level, tag, message)
- var stacktraceStream = stream(exception.stackTrace)
- if (stackTraceDepth != UNBOUNDED_STACK_TRACE) {
- stacktraceStream = stacktraceStream.limit(stackTraceDepth.toLong())
- }
- stacktraceStream.forEach { line ->
- printLikeLogcat(pw, timestamp, level, tag, "\tat $line")
- }
- exception.cause?.let { cause ->
- printException(pw, timestamp, level, cause, tag, "Caused by: ", nestedStackTraceDepth)
- }
- exception.suppressedExceptions.forEach { suppressed ->
- printException(
- pw,
- timestamp,
- level,
- suppressed,
- tag,
- "Suppressed: ",
- nestedStackTraceDepth
- )
- }
- }
-
- private fun printLikeLogcat(
- pw: PrintWriter,
- formattedTimestamp: String,
- shortLogLevel: String,
- tag: String,
- message: String
- ) {
- pw.print(formattedTimestamp)
- pw.print(" ")
- pw.print(shortLogLevel)
- pw.print(" ")
- pw.print(tag)
- pw.print(": ")
- pw.println(message)
- }
-
private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) {
if (toLogcat || toSystrace) {
val strMessage = message.messagePrinter(message)
@@ -370,5 +288,4 @@ class LogBuffer @JvmOverloads constructor(
typealias MessageInitializer = LogMessage.() -> Unit
private const val TAG = "LogBuffer"
-private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
private val FROZEN_MESSAGE = LogMessageImpl.create()
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
index 987aea8bff08..dae2592e116c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
@@ -16,6 +16,10 @@
package com.android.systemui.log
+import java.io.PrintWriter
+import java.text.SimpleDateFormat
+import java.util.Locale
+
/**
* Generic data class for storing messages logged to a [LogBuffer]
*
@@ -50,6 +54,17 @@ interface LogMessage {
var bool2: Boolean
var bool3: Boolean
var bool4: Boolean
+
+ /**
+ * Function that dumps the [LogMessage] to the provided [writer].
+ */
+ fun dump(writer: PrintWriter) {
+ val formattedTimestamp = DATE_FORMAT.format(timestamp)
+ val shortLevel = level.shortString
+ val messageToPrint = messagePrinter(this)
+ printLikeLogcat(writer, formattedTimestamp, shortLevel, tag, messageToPrint)
+ exception?.printStackTrace(writer)
+ }
}
/**
@@ -61,3 +76,21 @@ interface LogMessage {
* of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
*/
typealias MessagePrinter = LogMessage.() -> String
+
+private fun printLikeLogcat(
+ pw: PrintWriter,
+ formattedTimestamp: String,
+ shortLogLevel: String,
+ tag: String,
+ message: String
+) {
+ pw.print(formattedTimestamp)
+ pw.print(" ")
+ pw.print(shortLogLevel)
+ pw.print(" ")
+ pw.print(tag)
+ pw.print(": ")
+ pw.println(message)
+}
+
+private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index 81efdf591b41..e0c8d66cb6fd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -266,11 +266,11 @@ class MediaDataFilter @Inject constructor(
}
/**
- * Are there any media notifications active, including the recommendation?
+ * Are there any active media entries, including the recommendation?
*/
- fun hasActiveMediaOrRecommendation() =
- userEntries.any { it.value.active } ||
- (smartspaceMediaData.isActive && smartspaceMediaData.isValid())
+ fun hasActiveMediaOrRecommendation() = userEntries.any { it.value.active } ||
+ (smartspaceMediaData.isActive &&
+ (smartspaceMediaData.isValid() || reactivatedKey != null))
/**
* Are there any media entries we should display?
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index e9b6af44ddf3..e360d10d9362 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -263,6 +263,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
private void onGroupActionTriggered(boolean isChecked, MediaDevice device) {
+ disableSeekBar();
if (isChecked && isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
mController.addDeviceToPlayMedia(device);
} else if (!isChecked && isDeviceIncluded(mController.getDeselectableMediaDevice(),
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index bec67397a926..3b4ca48046eb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -273,6 +273,8 @@ public abstract class MediaOutputBaseAdapter extends
void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
if (!mController.isVolumeControlEnabled(device)) {
disableSeekBar();
+ } else {
+ enableSeekBar();
}
mSeekBar.setMaxVolume(device.getMaxVolume());
final int currentVolume = device.getCurrentVolume();
@@ -417,11 +419,16 @@ public abstract class MediaOutputBaseAdapter extends
return drawable;
}
- private void disableSeekBar() {
+ protected void disableSeekBar() {
mSeekBar.setEnabled(false);
mSeekBar.setOnTouchListener((v, event) -> true);
}
+ private void enableSeekBar() {
+ mSeekBar.setEnabled(true);
+ mSeekBar.setOnTouchListener((v, event) -> false);
+ }
+
protected void setUpDeviceIcon(MediaDevice device) {
ThreadUtils.postOnBackgroundThread(() -> {
Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 5d7af522176a..6fe06e085556 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -194,6 +194,11 @@ public class MediaOutputMetricLogger {
}
private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) {
+ if (device == null) {
+ return isSourceDevice
+ ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
+ : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE;
+ }
switch (device.getDeviceType()) {
case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
return isSourceDevice
@@ -229,6 +234,9 @@ public class MediaOutputMetricLogger {
}
private int getInteractionDeviceType(MediaDevice device) {
+ if (device == null) {
+ return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE;
+ }
switch (device.getDeviceType()) {
case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__BUILTIN_SPEAKER;
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 5f478ce32590..9ab83b84277e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -56,7 +56,7 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
internal val logger: MediaTttLogger,
internal val windowManager: WindowManager,
private val viewUtil: ViewUtil,
- @Main private val mainExecutor: DelayableExecutor,
+ @Main internal val mainExecutor: DelayableExecutor,
private val accessibilityManager: AccessibilityManager,
private val configurationController: ConfigurationController,
private val powerManager: PowerManager,
@@ -205,13 +205,15 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
*
* @param appPackageName the package name of the app playing the media. Will be used to fetch
* the app icon and app name if overrides aren't provided.
+ *
+ * @return the content description of the icon.
*/
internal fun setIcon(
currentChipView: ViewGroup,
appPackageName: String?,
appIconDrawableOverride: Drawable? = null,
appNameOverride: CharSequence? = null,
- ) {
+ ): CharSequence {
val appIconView = currentChipView.requireViewById<CachingIconView>(R.id.app_icon)
val iconInfo = getIconInfo(appPackageName)
@@ -224,6 +226,7 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
appIconView.contentDescription = appNameOverride ?: iconInfo.iconName
appIconView.setImageDrawable(appIconDrawableOverride ?: iconInfo.icon)
+ return appIconView.contentDescription.toString()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 3ea11b8aa4dd..b94b8bfabfc1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -122,13 +122,12 @@ class MediaTttChipControllerSender @Inject constructor(
val chipState = newChipInfo.state
// App icon
- setIcon(currentChipView, newChipInfo.routeInfo.packageName)
+ val iconName = setIcon(currentChipView, newChipInfo.routeInfo.packageName)
// Text
val otherDeviceName = newChipInfo.routeInfo.name.toString()
- currentChipView.requireViewById<TextView>(R.id.text).apply {
- text = chipState.getChipTextString(context, otherDeviceName)
- }
+ val chipText = chipState.getChipTextString(context, otherDeviceName)
+ currentChipView.requireViewById<TextView>(R.id.text).text = chipText
// Loading
currentChipView.requireViewById<View>(R.id.loading).visibility =
@@ -145,17 +144,29 @@ class MediaTttChipControllerSender @Inject constructor(
// Failure
currentChipView.requireViewById<View>(R.id.failure_icon).visibility =
chipState.isTransferFailure.visibleIfTrue()
+
+ // For accessibility
+ currentChipView.requireViewById<ViewGroup>(
+ R.id.media_ttt_sender_chip_inner
+ ).contentDescription = "$iconName $chipText"
}
override fun animateChipIn(chipView: ViewGroup) {
+ val chipInnerView = chipView.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner)
ViewHierarchyAnimator.animateAddition(
- chipView.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner),
+ chipInnerView,
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_DECELERATE,
- duration = 500L,
+ duration = ANIMATION_DURATION,
includeMargins = true,
includeFadeIn = true,
)
+
+ // We can only request focus once the animation finishes.
+ mainExecutor.executeDelayed(
+ { chipInnerView.requestAccessibilityFocus() },
+ ANIMATION_DURATION
+ )
}
override fun removeChip(removalReason: String) {
@@ -186,3 +197,4 @@ data class ChipSenderInfo(
}
const val SENDER_TAG = "MediaTapToTransferSender"
+private const val ANIMATION_DURATION = 500L
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 2d7a809644c0..3789cbb1fb65 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -302,10 +302,6 @@ public class NavigationBarController implements
*/
@VisibleForTesting
void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
- if (initializeTaskbarIfNecessary()) {
- return;
- }
-
if (display == null) {
return;
}
@@ -315,7 +311,7 @@ public class NavigationBarController implements
// We may show TaskBar on the default display for large screen device. Don't need to create
// navigation bar for this case.
- if (mIsTablet && isOnDefaultDisplay) {
+ if (isOnDefaultDisplay && initializeTaskbarIfNecessary()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 4552abd402b0..77652c9ddf2f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -110,6 +110,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
private Context mUserContext;
private UserTracker mUserTracker;
private SecureSettings mSecureSettings;
+ // Keep track of whether mTilesList contains the same information as the Settings value.
+ // This is a performance optimization to reduce the number of blocking calls to Settings from
+ // main thread.
+ // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged
+ private boolean mTilesListDirty = true;
private final TileServiceRequestController mTileServiceRequestController;
private TileLifecycleManager.Factory mTileLifeCycleManagerFactory;
@@ -374,6 +379,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
// the ones that are in the setting, update the Setting.
saveTilesToSettings(mTileSpecs);
}
+ mTilesListDirty = false;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onTilesChanged();
}
@@ -436,6 +442,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
);
}
+ // When calling this, you may want to modify mTilesListDirty accordingly.
@MainThread
private void saveTilesToSettings(List<String> tileSpecs) {
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
@@ -445,9 +452,15 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
@MainThread
private void changeTileSpecs(Predicate<List<String>> changeFunction) {
- final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser);
- final List<String> tileSpecs = loadTileSpecs(mContext, setting);
+ final List<String> tileSpecs;
+ if (!mTilesListDirty) {
+ tileSpecs = new ArrayList<>(mTileSpecs);
+ } else {
+ tileSpecs = loadTileSpecs(mContext,
+ mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser));
+ }
if (changeFunction.test(tileSpecs)) {
+ mTilesListDirty = true;
saveTilesToSettings(tileSpecs);
}
}
@@ -507,6 +520,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
}
}
if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles);
+ mTilesListDirty = true;
saveTilesToSettings(newTiles);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 86ef85824eb0..ab795faf57e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -69,36 +69,63 @@ class QSLogger @Inject constructor(
})
}
- fun logTileClick(tileSpec: String, statusBarState: Int, state: Int) {
+ fun logTileClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) {
log(DEBUG, {
str1 = tileSpec
- int1 = statusBarState
+ int1 = eventId
str2 = StatusBarState.toString(statusBarState)
str3 = toStateString(state)
}, {
- "[$str1] Tile clicked. StatusBarState=$str2. TileState=$str3"
+ "[$str1][$int1] Tile clicked. StatusBarState=$str2. TileState=$str3"
})
}
- fun logTileSecondaryClick(tileSpec: String, statusBarState: Int, state: Int) {
+ fun logHandleClick(tileSpec: String, eventId: Int) {
log(DEBUG, {
str1 = tileSpec
- int1 = statusBarState
+ int1 = eventId
+ }, {
+ "[$str1][$int1] Tile handling click."
+ })
+ }
+
+ fun logTileSecondaryClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) {
+ log(DEBUG, {
+ str1 = tileSpec
+ int1 = eventId
str2 = StatusBarState.toString(statusBarState)
str3 = toStateString(state)
}, {
- "[$str1] Tile long clicked. StatusBarState=$str2. TileState=$str3"
+ "[$str1][$int1] Tile secondary clicked. StatusBarState=$str2. TileState=$str3"
+ })
+ }
+
+ fun logHandleSecondaryClick(tileSpec: String, eventId: Int) {
+ log(DEBUG, {
+ str1 = tileSpec
+ int1 = eventId
+ }, {
+ "[$str1][$int1] Tile handling secondary click."
})
}
- fun logTileLongClick(tileSpec: String, statusBarState: Int, state: Int) {
+ fun logTileLongClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) {
log(DEBUG, {
str1 = tileSpec
- int1 = statusBarState
+ int1 = eventId
str2 = StatusBarState.toString(statusBarState)
str3 = toStateString(state)
}, {
- "[$str1] Tile long clicked. StatusBarState=$str2. TileState=$str3"
+ "[$str1][$int1] Tile long clicked. StatusBarState=$str2. TileState=$str3"
+ })
+ }
+
+ fun logHandleLongClick(tileSpec: String, eventId: Int) {
+ log(DEBUG, {
+ str1 = tileSpec
+ int1 = eventId
+ }, {
+ "[$str1][$int1] Tile handling long click."
})
}
@@ -144,4 +171,4 @@ class QSLogger @Inject constructor(
) {
buffer.log(TAG, logLevel, initializer, printer)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 740e12ab5839..2cffe8951b56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -105,6 +105,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
private final FalsingManager mFalsingManager;
protected final QSLogger mQSLogger;
private volatile int mReadyState;
+ // Keeps track of the click event, to match it with the handling in the background thread
+ // Only read and modified in main thread (where click events come through).
+ private int mClickEventId = 0;
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private final Object mStaleListener = new Object();
@@ -295,9 +298,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
mStatusBarStateController.getState())));
mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_CLICK, 0, getMetricsSpec(),
getInstanceId());
- mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state);
+ final int eventId = mClickEventId++;
+ mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
+ eventId);
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- mHandler.obtainMessage(H.CLICK, view).sendToTarget();
+ mHandler.obtainMessage(H.CLICK, eventId, 0, view).sendToTarget();
}
}
@@ -307,9 +312,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
mStatusBarStateController.getState())));
mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_SECONDARY_CLICK, 0, getMetricsSpec(),
getInstanceId());
+ final int eventId = mClickEventId++;
mQSLogger.logTileSecondaryClick(mTileSpec, mStatusBarStateController.getState(),
- mState.state);
- mHandler.obtainMessage(H.SECONDARY_CLICK, view).sendToTarget();
+ mState.state, eventId);
+ mHandler.obtainMessage(H.SECONDARY_CLICK, eventId, 0, view).sendToTarget();
}
@Override
@@ -319,8 +325,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
mStatusBarStateController.getState())));
mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_LONG_PRESS, 0, getMetricsSpec(),
getInstanceId());
- mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state);
- mHandler.obtainMessage(H.LONG_CLICK, view).sendToTarget();
+ final int eventId = mClickEventId++;
+ mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
+ eventId);
+ mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
}
public LogMaker populate(LogMaker logMaker) {
@@ -590,13 +598,16 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
mContext, mEnforcedAdmin);
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
} else {
+ mQSLogger.logHandleClick(mTileSpec, msg.arg1);
handleClick((View) msg.obj);
}
} else if (msg.what == SECONDARY_CLICK) {
name = "handleSecondaryClick";
+ mQSLogger.logHandleSecondaryClick(mTileSpec, msg.arg1);
handleSecondaryClick((View) msg.obj);
} else if (msg.what == LONG_CLICK) {
name = "handleLongClick";
+ mQSLogger.logHandleLongClick(mTileSpec, msg.arg1);
handleLongClick((View) msg.obj);
} else if (msg.what == REFRESH_STATE) {
name = "handleRefreshState";
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index 0a8e6e21d5b3..56a187429af6 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -39,7 +39,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
ROUNDED_BOX,
ELLIPSE
}
-
+ //language=AGSL
companion object {
private const val SHADER_UNIFORMS = """uniform vec2 in_center;
uniform vec2 in_size;
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
index 0cacbc2819c5..6de46483892b 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
@@ -17,6 +17,7 @@ package com.android.systemui.ripple
/** A common utility functions that are used for computing [RippleShader]. */
class RippleShaderUtilLibrary {
+ //language=AGSL
companion object {
const val SHADER_LIB = """
float triangleNoise(vec2 n) {
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index 83d9f2da1db1..8b0120177268 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -39,7 +39,9 @@ private const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt()
open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private lateinit var rippleShader: RippleShader
- private lateinit var rippleShape: RippleShape
+ lateinit var rippleShape: RippleShape
+ private set
+
private val ripplePaint = Paint()
var rippleInProgress: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
index 7f26146f541d..5e256c653992 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
@@ -17,6 +17,7 @@ package com.android.systemui.ripple
/** Library class that contains 2D signed distance functions. */
class SdfShaderLibrary {
+ //language=AGSL
companion object {
const val CIRCLE_SDF = """
float sdCircle(vec2 p, float r) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 68d35f9679ed..824d3a3f5af1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -34,6 +34,8 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
+import com.android.systemui.statusbar.notification.collection.PipelineDumper;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
import com.android.systemui.util.time.SystemClock;
@@ -52,7 +54,8 @@ import javax.inject.Inject;
*/
@SysUISingleton
@SuppressLint("OverrideAbstract")
-public class NotificationListener extends NotificationListenerWithPlugins {
+public class NotificationListener extends NotificationListenerWithPlugins implements
+ PipelineDumpable {
private static final String TAG = "NotificationListener";
private static final boolean DEBUG = CentralSurfaces.DEBUG;
private static final long MAX_RANKING_DELAY_MILLIS = 500L;
@@ -255,6 +258,11 @@ public class NotificationListener extends NotificationListenerWithPlugins {
}
}
+ @Override
+ public void dumpPipeline(@NonNull PipelineDumper d) {
+ d.dump("notificationHandlers", mNotificationHandlers);
+ }
+
private static Ranking getRankingOrTemporaryStandIn(RankingMap rankingMap, String key) {
Ranking ranking = new Ranking();
if (!rankingMap.getRanking(key, ranking)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 351a4bea2947..68bf69a3ea9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -142,7 +142,7 @@ import javax.inject.Inject;
*/
@MainThread
@SysUISingleton
-public class NotifCollection implements Dumpable {
+public class NotifCollection implements Dumpable, PipelineDumpable {
private final IStatusBarService mStatusBarService;
private final SystemClock mClock;
private final NotifPipelineFlags mNotifPipelineFlags;
@@ -870,6 +870,14 @@ public class NotifCollection implements Dumpable {
}
}
+ @Override
+ public void dumpPipeline(@NonNull PipelineDumper d) {
+ d.dump("notifCollectionListeners", mNotifCollectionListeners);
+ d.dump("lifetimeExtenders", mLifetimeExtenders);
+ d.dump("dismissInterceptors", mDismissInterceptors);
+ d.dump("buildListener", mBuildListener);
+ }
+
private final BatchableNotificationHandler mNotifHandler = new BatchableNotificationHandler() {
@Override
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumpable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumpable.kt
new file mode 100644
index 000000000000..a1aec3f382bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumpable.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.collection
+
+interface PipelineDumpable {
+ fun dumpPipeline(d: PipelineDumper)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt
new file mode 100644
index 000000000000..a10c74523015
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 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.collection
+
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.withIncreasedIndent
+import java.io.PrintWriter
+
+class PipelineDumper(pw: PrintWriter) {
+ private val ipw = pw.asIndenting()
+
+ fun print(a: Any?) = ipw.print(a)
+ fun println(a: Any?) = ipw.println(a)
+ fun withIncreasedIndent(b: () -> Unit) = ipw.withIncreasedIndent(b)
+ fun withIncreasedIndent(r: Runnable) = ipw.withIncreasedIndent(r)
+
+ fun dump(label: String, value: Any?) {
+ ipw.print("$label: ")
+ dump(value)
+ }
+
+ private fun dump(value: Any?) = when (value) {
+ null, is String, is Int -> println(value)
+ is Collection<*> -> dumpCollection(value)
+ else -> {
+ println(value.fullPipelineName)
+ withIncreasedIndent { (value as? PipelineDumpable)?.dumpPipeline(this) }
+ }
+ }
+
+ private fun dumpCollection(values: Collection<Any?>) {
+ println(values.size)
+ withIncreasedIndent { values.forEach { dump(it) } }
+ }
+}
+
+private val Any.bareClassName: String get() {
+ val className = javaClass.name
+ val packageName = javaClass.`package`.name
+ return className.substring(packageName.length + 1)
+}
+
+private val Any.barePipelineName: String? get() = when (this) {
+ is NotifLifetimeExtender -> name
+ is NotifDismissInterceptor -> name
+ is Pluggable<*> -> name
+ else -> null
+}
+
+private val Any.fullPipelineName: String get() =
+ barePipelineName?.let { "\"$it\" ($bareClassName)" } ?: bareClassName
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 075a0dc7555e..420f21db2c73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -87,7 +87,7 @@ import javax.inject.Inject;
*/
@MainThread
@SysUISingleton
-public class ShadeListBuilder implements Dumpable {
+public class ShadeListBuilder implements Dumpable, PipelineDumpable {
private final SystemClock mSystemClock;
private final ShadeListBuilderLogger mLogger;
private final NotificationInteractionTracker mInteractionTracker;
@@ -1396,6 +1396,21 @@ public class ShadeListBuilder implements Dumpable {
"\t\t"));
}
+ @Override
+ public void dumpPipeline(@NonNull PipelineDumper d) {
+ d.dump("choreographer", mChoreographer);
+ d.dump("notifPreGroupFilters", mNotifPreGroupFilters);
+ d.dump("onBeforeTransformGroupsListeners", mOnBeforeTransformGroupsListeners);
+ d.dump("notifPromoters", mNotifPromoters);
+ d.dump("onBeforeSortListeners", mOnBeforeSortListeners);
+ d.dump("notifSections", mNotifSections);
+ d.dump("notifComparators", mNotifComparators);
+ d.dump("onBeforeFinalizeFilterListeners", mOnBeforeFinalizeFilterListeners);
+ d.dump("notifFinalizeFilters", mNotifFinalizeFilters);
+ d.dump("onBeforeRenderListListeners", mOnBeforeRenderListListeners);
+ d.dump("onRenderListListener", mOnRenderListListener);
+ }
+
/** See {@link #setOnRenderListListener(OnRenderListListener)} */
public interface OnRenderListListener {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
index 050b4c113231..98f2167ebfa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
@@ -32,6 +32,8 @@ import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
+import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
+import com.android.systemui.statusbar.notification.collection.PipelineDumper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
@@ -63,7 +65,7 @@ import javax.inject.Inject;
* passed along to the NotifCollection.
*/
@MainThread
-public class GroupCoalescer implements Dumpable {
+public class GroupCoalescer implements Dumpable, PipelineDumpable {
private final DelayableExecutor mMainExecutor;
private final SystemClock mClock;
private final GroupCoalescerLogger mLogger;
@@ -314,6 +316,11 @@ public class GroupCoalescer implements Dumpable {
}
}
+ @Override
+ public void dumpPipeline(@NonNull PipelineDumper d) {
+ d.dump("handler", mHandler);
+ }
+
private final Comparator<CoalescedEvent> mEventComparator = (o1, o2) -> {
int cmp = Boolean.compare(
o2.getSbn().getNotification().isGroupSummary(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 891e25ef6c25..1399385e7654 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -15,24 +15,22 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
-import com.android.systemui.Dumpable
-import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.PipelineDumpable
+import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
-import java.io.PrintWriter
import javax.inject.Inject
/**
* Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the
* Coordinators can register their respective callbacks.
*/
-interface NotifCoordinators : Coordinator, Dumpable
+interface NotifCoordinators : Coordinator, PipelineDumpable
@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
- dumpManager: DumpManager,
notifPipelineFlags: NotifPipelineFlags,
dataStoreCoordinator: DataStoreCoordinator,
hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
@@ -66,8 +64,6 @@ class NotifCoordinatorsImpl @Inject constructor(
* Creates all the coordinators.
*/
init {
- dumpManager.registerDumpable(TAG, this)
-
// TODO(b/208866714): formalize the system by which some coordinators may be required by the
// pipeline, such as this DataStoreCoordinator which cannot be removed, as it's a critical
// glue between the pipeline and parts of SystemUI which depend on pipeline output via the
@@ -121,15 +117,12 @@ class NotifCoordinatorsImpl @Inject constructor(
pipeline.setSections(mOrderedSections)
}
- override fun dump(pw: PrintWriter, args: Array<String>) {
- pw.println()
- pw.println("$TAG:")
- for (c in mCoordinators) {
- pw.println("\t${c.javaClass}")
- }
- for (s in mOrderedSections) {
- pw.println("\t${s.name}")
- }
+ /*
+ * As part of the NotifPipeline dumpable, dumps the list of coordinators; sections are omitted
+ * as they are dumped in the RenderStageManager instead.
+ */
+ override fun dumpPipeline(d: PipelineDumper) = with(d) {
+ dump("coordinators", mCoordinators)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index 24ef5808b2e3..a34d033afcaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection.init;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
@@ -25,12 +27,15 @@ import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
+import com.android.systemui.statusbar.notification.collection.PipelineDumper;
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import com.android.systemui.statusbar.notification.collection.render.RenderStageManager;
+import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager;
import com.android.systemui.statusbar.notification.collection.render.ShadeViewManagerFactory;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -42,7 +47,7 @@ import javax.inject.Inject;
* Initialization code for the new notification pipeline.
*/
@SysUISingleton
-public class NotifPipelineInitializer implements Dumpable {
+public class NotifPipelineInitializer implements Dumpable, PipelineDumpable {
private final NotifPipeline mPipelineWrapper;
private final GroupCoalescer mGroupCoalescer;
private final NotifCollection mNotifCollection;
@@ -53,6 +58,9 @@ public class NotifPipelineInitializer implements Dumpable {
private final DumpManager mDumpManager;
private final ShadeViewManagerFactory mShadeViewManagerFactory;
+ /* These are saved just for dumping. */
+ private ShadeViewManager mShadeViewManager;
+ private NotificationListener mNotificationService;
@Inject
public NotifPipelineInitializer(
@@ -83,9 +91,10 @@ public class NotifPipelineInitializer implements Dumpable {
NotificationRowBinderImpl rowBinder,
NotificationListContainer listContainer,
NotifStackController stackController) {
-
mDumpManager.registerDumpable("NotifPipeline", this);
+ mNotificationService = notificationService;
+
// Setup inflation
mNotifInflater.setRowBinder(rowBinder);
@@ -93,13 +102,12 @@ public class NotifPipelineInitializer implements Dumpable {
mNotifPluggableCoordinators.attach(mPipelineWrapper);
// Wire up pipeline
- mShadeViewManagerFactory
- .create(listContainer, stackController)
- .attach(mRenderStageManager);
+ mShadeViewManager = mShadeViewManagerFactory.create(listContainer, stackController);
+ mShadeViewManager.attach(mRenderStageManager);
mRenderStageManager.attach(mListBuilder);
mListBuilder.attach(mNotifCollection);
mNotifCollection.attach(mGroupCoalescer);
- mGroupCoalescer.attach(notificationService);
+ mGroupCoalescer.attach(mNotificationService);
Log.d(TAG, "Notif pipeline initialized."
+ " rendering=" + true);
@@ -107,8 +115,37 @@ public class NotifPipelineInitializer implements Dumpable {
@Override
public void dump(PrintWriter pw, String[] args) {
- mNotifPluggableCoordinators.dump(pw, args);
- mGroupCoalescer.dump(pw, args);
+ dumpPipeline(new PipelineDumper(pw));
+ }
+
+ @Override
+ public void dumpPipeline(@NonNull PipelineDumper d) {
+ d.println("STAGE 0: SETUP");
+ d.dump("notifPluggableCoordinators", mNotifPluggableCoordinators);
+ d.println("");
+
+ d.println("STAGE 1: LISTEN");
+ d.dump("notificationService", mNotificationService);
+ d.println("");
+
+ d.println("STAGE 2: BATCH EVENTS");
+ d.dump("groupCoalescer", mGroupCoalescer);
+ d.println("");
+
+ d.println("STAGE 3: COLLECT");
+ d.dump("notifCollection", mNotifCollection);
+ d.println("");
+
+ d.println("STAGE 4: BUILD LIST");
+ d.dump("listBuilder", mListBuilder);
+ d.println("");
+
+ d.println("STAGE 5: DISPATCH RENDER");
+ d.dump("renderStageManager", mRenderStageManager);
+ d.println("");
+
+ d.println("STAGE 6: UPDATE SHADE");
+ d.dump("shadeViewManager", mShadeViewManager);
}
private static final String TAG = "NotifPipeline";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
index ea66f3b6dd42..9765e8f1e4fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.listbuilder
+import com.android.systemui.statusbar.notification.collection.PipelineDumpable
+import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.render.NodeController
@@ -24,10 +26,18 @@ import com.android.systemui.statusbar.notification.stack.PriorityBucket
data class NotifSection(
val sectioner: NotifSectioner,
val index: Int
-) {
+) : PipelineDumpable {
@PriorityBucket
val bucket: Int = sectioner.bucket
val label: String = "$index:$bucket:${sectioner.name}"
val headerController: NodeController? = sectioner.headerNodeController
val comparator: NotifComparator? = sectioner.comparator
+
+ override fun dumpPipeline(d: PipelineDumper) = with(d) {
+ dump("index", index)
+ dump("bucket", bucket)
+ dump("sectioner", sectioner)
+ dump("headerController", headerController)
+ dump("comparator", comparator)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
index a9c398726138..7a37846ac97b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
@@ -20,6 +20,8 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.PipelineDumpable
+import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
@@ -33,7 +35,7 @@ import javax.inject.Inject
* provided to [setViewRenderer].
*/
@SysUISingleton
-class RenderStageManager @Inject constructor() {
+class RenderStageManager @Inject constructor() : PipelineDumpable {
private val onAfterRenderListListeners = mutableListOf<OnAfterRenderListListener>()
private val onAfterRenderGroupListeners = mutableListOf<OnAfterRenderGroupListener>()
private val onAfterRenderEntryListeners = mutableListOf<OnAfterRenderEntryListener>()
@@ -75,6 +77,13 @@ class RenderStageManager @Inject constructor() {
onAfterRenderEntryListeners.add(listener)
}
+ override fun dumpPipeline(d: PipelineDumper) = with(d) {
+ dump("ViewRenderer", viewRenderer)
+ dump("OnAfterRenderListListeners", onAfterRenderListListeners)
+ dump("OnAfterRenderGroupListeners", onAfterRenderGroupListeners)
+ dump("OnAfterRenderEntryListeners", onAfterRenderEntryListeners)
+ }
+
private fun dispatchOnAfterRenderList(
viewRenderer: NotifViewRenderer,
entries: List<ListEntry>
@@ -139,4 +148,4 @@ class RenderStageManager @Inject constructor() {
}
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
index b76169f111db..2073e92cd45b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.collection.render
import android.view.View
+import com.android.systemui.statusbar.notification.collection.PipelineDumpable
+import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -28,7 +30,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain
class RootNodeController(
private val listContainer: NotificationListContainer,
override val view: View
-) : NodeController {
+) : NodeController, PipelineDumpable {
override val nodeLabel: String = "<root>"
override fun getChildAt(index: Int): View? {
@@ -59,4 +61,8 @@ class RootNodeController(
listContainer.setChildTransferInProgress(false)
}
}
+
+ override fun dumpPipeline(d: PipelineDumper) = with(d) {
+ dump("listContainer", listContainer)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 51dc72848d9e..df8e87fa413b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -22,6 +22,8 @@ import com.android.systemui.statusbar.notification.NotificationSectionsFeatureMa
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.PipelineDumpable
+import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.util.traceSection
@@ -43,12 +45,12 @@ class ShadeViewManager @AssistedInject constructor(
nodeSpecBuilderLogger: NodeSpecBuilderLogger,
shadeViewDifferLogger: ShadeViewDifferLogger,
private val viewBarn: NotifViewBarn
-) {
+) : PipelineDumpable {
// We pass a shim view here because the listContainer may not actually have a view associated
// with it and the differ never actually cares about the root node's view.
private val rootController = RootNodeController(listContainer, View(context))
private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager,
- sectionHeaderVisibilityProvider, viewBarn, nodeSpecBuilderLogger)
+ sectionHeaderVisibilityProvider, viewBarn, nodeSpecBuilderLogger)
private val viewDiffer = ShadeViewDiffer(rootController, shadeViewDifferLogger)
/** Method for attaching this manager to the pipeline. */
@@ -56,6 +58,12 @@ class ShadeViewManager @AssistedInject constructor(
renderStageManager.setViewRenderer(viewRenderer)
}
+ override fun dumpPipeline(d: PipelineDumper) = with(d) {
+ dump("rootController", rootController)
+ dump("specBuilder", specBuilder)
+ dump("viewDiffer", viewDiffer)
+ }
+
private val viewRenderer = object : NotifViewRenderer {
override fun onRenderList(notifList: List<ListEntry>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e5835a836d39..cc539b01b894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -86,6 +86,8 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
+import com.android.systemui.statusbar.notification.collection.PipelineDumper;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -1560,7 +1562,8 @@ public class NotificationStackScrollLayoutController {
}
}
- private class NotificationListContainerImpl implements NotificationListContainer {
+ private class NotificationListContainerImpl implements NotificationListContainer,
+ PipelineDumpable {
@Override
public void setChildTransferInProgress(boolean childTransferInProgress) {
@@ -1706,6 +1709,12 @@ public class NotificationStackScrollLayoutController {
public void setWillExpand(boolean willExpand) {
mView.setWillExpand(willExpand);
}
+
+ @Override
+ public void dumpPipeline(@NonNull PipelineDumper d) {
+ d.dump("NotificationStackScrollLayoutController.this",
+ NotificationStackScrollLayoutController.this);
+ }
}
class TouchHandler implements Gefingerpoken {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index ee45c42b74fc..896e3e53946a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -31,7 +31,7 @@ import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_
import static androidx.lifecycle.Lifecycle.State.RESUMED;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
-import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTERY_LEVEL;
+import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
@@ -172,6 +172,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.ripple.RippleShader.RippleShape;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -2211,7 +2212,8 @@ public class CentralSurfacesImpl extends CoreStartable implements
public void onAnimationEnded() {
mNotificationShadeWindowController.setRequestTopUi(false, TAG);
}
- }, false, sUiEventLogger).show(animationDelay);
+ }, /* isDozing= */ false, RippleShape.CIRCLE,
+ sUiEventLogger).show(animationDelay);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index e22a896227ef..4c762702892a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -405,8 +405,8 @@ public class BubblesManager implements Dumpable {
}
@Override
- public void onEntryUpdated(NotificationEntry entry) {
- BubblesManager.this.onEntryUpdated(entry);
+ public void onEntryUpdated(NotificationEntry entry, boolean fromSystem) {
+ BubblesManager.this.onEntryUpdated(entry, fromSystem);
}
@Override
@@ -444,9 +444,10 @@ public class BubblesManager implements Dumpable {
}
}
- void onEntryUpdated(NotificationEntry entry) {
+ void onEntryUpdated(NotificationEntry entry, boolean fromSystem) {
+ boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry);
mBubbles.onEntryUpdated(notifToBubbleEntry(entry),
- mNotificationInterruptStateProvider.shouldBubbleUp(entry));
+ shouldBubble, fromSystem);
}
void onEntryRemoved(NotificationEntry entry) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 8d27f2422f66..c67737136b3b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -22,6 +22,7 @@ import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
+import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.google.common.truth.Truth.assertThat;
@@ -109,6 +110,7 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
+import org.mockito.internal.util.reflection.FieldSetter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -200,9 +202,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
private ArgumentCaptor<CancellationSignal> mCancellationSignalCaptor;
// Direct executor
- private Executor mBackgroundExecutor = Runnable::run;
- private Executor mMainExecutor = Runnable::run;
+ private final Executor mBackgroundExecutor = Runnable::run;
+ private final Executor mMainExecutor = Runnable::run;
private TestableLooper mTestableLooper;
+ private Handler mHandler;
private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
private TestableContext mSpiedContext;
private MockitoSession mMockitoSession;
@@ -291,6 +294,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mBiometricEnabledOnKeyguardCallback = mBiometricEnabledCallbackArgCaptor.getValue();
biometricsEnabledForCurrentUser();
+ mHandler = spy(mKeyguardUpdateMonitor.getHandler());
+ try {
+ FieldSetter.setField(mKeyguardUpdateMonitor,
+ KeyguardUpdateMonitor.class.getDeclaredField("mHandler"), mHandler);
+ } catch (NoSuchFieldException e) {
+
+ }
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue();
mKeyguardUpdateMonitor.registerCallback(mTestCallback);
@@ -330,7 +340,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
when(mTelephonyManager.getSimState(anyInt())).thenReturn(state);
- when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[] { subId });
+ when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[]{subId});
KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mSpiedContext);
@@ -505,7 +515,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
// Even SimState Loaded, still need ACTION_SERVICE_STATE turn on mTelephonyCapable
assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isFalse();
- Intent intentServiceState = new Intent(Intent.ACTION_SERVICE_STATE);
+ Intent intentServiceState = new Intent(Intent.ACTION_SERVICE_STATE);
intentSimState.putExtra(Intent.EXTRA_SIM_STATE
, Intent.SIM_STATE_LOADED);
mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
@@ -520,7 +530,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mTestableLooper.processAllMessages();
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
- anyInt());
+ anyInt());
verify(mFingerprintManager, never()).detectFingerprint(any(), any(), anyInt());
}
@@ -791,7 +801,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
public void testBiometricsCleared_whenUserSwitches() throws Exception {
final IRemoteCallback reply = new IRemoteCallback.Stub() {
@Override
- public void sendResult(Bundle data) {} // do nothing
+ public void sendResult(Bundle data) {
+ } // do nothing
};
final BiometricAuthenticated dummyAuthentication =
new BiometricAuthenticated(true /* authenticated */, true /* strong */);
@@ -809,7 +820,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
public void testMultiUserJankMonitor_whenUserSwitches() throws Exception {
final IRemoteCallback reply = new IRemoteCallback.Stub() {
@Override
- public void sendResult(Bundle data) {} // do nothing
+ public void sendResult(Bundle data) {
+ } // do nothing
};
mKeyguardUpdateMonitor.handleUserSwitchComplete(10 /* user */);
verify(mInteractionJankMonitor).end(InteractionJankMonitor.CUJ_USER_SWITCH);
@@ -1499,6 +1511,34 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(cancelSignal.isCanceled()).isTrue();
}
+ @Test
+ public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mTestableLooper.processAllMessages();
+ mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
+ anyInt());
+
+ mKeyguardUpdateMonitor.onFaceAuthenticated(0, false);
+ // Make sure keyguard is going away after face auth attempt, and that it calls
+ // updateBiometricStateListeningState.
+ mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+ mTestableLooper.processAllMessages();
+
+ verify(mHandler).postDelayed(mKeyguardUpdateMonitor.mFpCancelNotReceived,
+ DEFAULT_CANCEL_SIGNAL_TIMEOUT);
+
+ mKeyguardUpdateMonitor.onFingerprintAuthenticated(0, true);
+ mTestableLooper.processAllMessages();
+
+ verify(mHandler, times(1)).removeCallbacks(mKeyguardUpdateMonitor.mFpCancelNotReceived);
+ mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
+ mTestableLooper.processAllMessages();
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(anyBoolean())).isEqualTo(true);
+ }
+
private void fingerprintIsNotEnrolled() {
when(mFingerprintManager.hasEnrolledTemplates(mCurrentUserId)).thenReturn(false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
index 2915f5a504d7..e099c9269d3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
@@ -20,6 +20,7 @@ import static com.android.systemui.dreams.complication.Complication.COMPLICATION
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_SMARTSPACE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
import static com.android.systemui.dreams.complication.ComplicationUtils.convertComplicationType;
@@ -60,6 +61,8 @@ public class ComplicationUtilsTest extends SysuiTestCase {
.isEqualTo(COMPLICATION_TYPE_CAST_INFO);
assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS))
.isEqualTo(COMPLICATION_TYPE_HOME_CONTROLS);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_SMARTSPACE))
+ .isEqualTo(COMPLICATION_TYPE_SMARTSPACE);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index bcc76abc89ba..810c6dc4776d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.data.repository
+package com.android.systemui.keyguard.data.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -22,11 +22,10 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
@@ -50,18 +49,19 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
companion object {
@Parameters(
name =
- "feature enabled = {0}, has favorites = {1}, has service infos = {2} - expected" +
- " visible = {3}"
+ "feature enabled = {0}, has favorites = {1}, has service infos = {2}, can show" +
+ " while locked = {3} - expected visible = {4}"
)
@JvmStatic
fun data() =
- (0 until 8)
+ (0 until 16)
.map { combination ->
arrayOf(
- /* isFeatureEnabled= */ combination and 0b100 != 0,
- /* hasFavorites= */ combination and 0b010 != 0,
- /* hasServiceInfos= */ combination and 0b001 != 0,
- /* isVisible= */ combination == 0b111,
+ /* isFeatureEnabled= */ combination and 0b1000 != 0,
+ /* hasFavorites= */ combination and 0b0100 != 0,
+ /* hasServiceInfos= */ combination and 0b0010 != 0,
+ /* canShowWhileLocked= */ combination and 0b0001 != 0,
+ /* isVisible= */ combination == 0b1111,
)
}
.toList()
@@ -79,7 +79,8 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
@JvmField @Parameter(0) var isFeatureEnabled: Boolean = false
@JvmField @Parameter(1) var hasFavorites: Boolean = false
@JvmField @Parameter(2) var hasServiceInfos: Boolean = false
- @JvmField @Parameter(3) var isVisible: Boolean = false
+ @JvmField @Parameter(3) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(4) var isVisible: Boolean = false
@Before
fun setUp() {
@@ -89,6 +90,8 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
whenever(component.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
+ whenever(component.canShowWhileLockedSetting)
+ .thenReturn(MutableStateFlow(canShowWhileLocked))
underTest =
HomeControlsKeyguardQuickAffordanceConfig(
@@ -111,14 +114,16 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
val job = underTest.state.onEach(values::add).launchIn(this)
- verify(controlsListingController).addCallback(callbackCaptor.capture())
- callbackCaptor.value.onServicesUpdated(
- if (hasServiceInfos) {
- listOf(mock())
- } else {
- emptyList()
- }
- )
+ if (canShowWhileLocked) {
+ verify(controlsListingController).addCallback(callbackCaptor.capture())
+ callbackCaptor.value.onServicesUpdated(
+ if (hasServiceInfos) {
+ listOf(mock())
+ } else {
+ emptyList()
+ }
+ )
+ }
assertThat(values.last())
.isInstanceOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index 592e80b9e7d9..ef588f5ce255 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -51,6 +51,7 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
underTest =
HomeControlsKeyguardQuickAffordanceConfig(
@@ -60,7 +61,26 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun `state - when listing controller is missing - returns None`() = runBlockingTest {
+ fun `state - when cannot show while locked - returns Hidden`() = runBlockingTest {
+ whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
+ whenever(component.isEnabled()).thenReturn(true)
+ whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
+ whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
+ val controlsController = mock<ControlsController>()
+ whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
+ whenever(component.getControlsListingController()).thenReturn(Optional.empty())
+ whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
+
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
+ val job = underTest.state.onEach(values::add).launchIn(this)
+
+ assertThat(values.last())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+ job.cancel()
+ }
+
+ @Test
+ fun `state - when listing controller is missing - returns Hidden`() = runBlockingTest {
whenever(component.isEnabled()).thenReturn(true)
whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
index 4abb973817b1..56aff3c2fc8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -24,16 +24,11 @@ class LogBufferTest : SysuiTestCase() {
@Before
fun setup() {
outputWriter = StringWriter()
- buffer = createBuffer(UNBOUNDED_STACK_TRACE, NESTED_TRACE_DEPTH)
+ buffer = createBuffer()
}
- private fun createBuffer(rootTraceDepth: Int, nestedTraceDepth: Int): LogBuffer {
- return LogBuffer("TestBuffer",
- 1,
- logcatEchoTracker,
- false,
- rootStackTraceDepth = rootTraceDepth,
- nestedStackTraceDepth = nestedTraceDepth)
+ private fun createBuffer(): LogBuffer {
+ return LogBuffer("TestBuffer", 1, logcatEchoTracker, false)
}
@Test
@@ -56,95 +51,83 @@ class LogBufferTest : SysuiTestCase() {
}
@Test
- fun dump_writesExceptionAndStacktraceLimitedToGivenDepth() {
- buffer = createBuffer(rootTraceDepth = 2, nestedTraceDepth = -1)
- // stack trace depth of 5
- val exception = createTestException("Exception message", "TestClass", 5)
+ fun dump_writesExceptionAndStacktrace() {
+ buffer = createBuffer()
+ val exception = createTestException("Exception message", "TestClass")
buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
val dumpedString = dumpBuffer()
- // logs are limited to depth 2
- assertThat(dumpedString).contains("E Tag: Extra message")
- assertThat(dumpedString).contains("E Tag: java.lang.RuntimeException: Exception message")
- assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)")
- assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)")
- assertThat(dumpedString)
- .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)")
+ assertThat(dumpedString).contains("Extra message")
+ assertThat(dumpedString).contains("java.lang.RuntimeException: Exception message")
+ assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:1)")
+ assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:2)")
}
@Test
- fun dump_writesCauseAndStacktraceLimitedToGivenDepth() {
- buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2)
+ fun dump_writesCauseAndStacktrace() {
+ buffer = createBuffer()
val exception = createTestException("Exception message",
"TestClass",
- 1,
- cause = createTestException("The real cause!", "TestClass", 5))
+ cause = createTestException("The real cause!", "TestClass"))
buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
val dumpedString = dumpBuffer()
- // logs are limited to depth 2
- assertThat(dumpedString)
- .contains("E Tag: Caused by: java.lang.RuntimeException: The real cause!")
- assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)")
- assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)")
assertThat(dumpedString)
- .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)")
+ .contains("Caused by: java.lang.RuntimeException: The real cause!")
+ assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:1)")
+ assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:2)")
}
@Test
- fun dump_writesSuppressedExceptionAndStacktraceLimitedToGivenDepth() {
- buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2)
+ fun dump_writesSuppressedExceptionAndStacktrace() {
+ buffer = createBuffer()
val exception = RuntimeException("Root exception message")
exception.addSuppressed(
createTestException(
"First suppressed exception",
"FirstClass",
- 5,
- createTestException("Cause of suppressed exp", "ThirdClass", 5)
+ createTestException("Cause of suppressed exp", "ThirdClass")
))
exception.addSuppressed(
- createTestException("Second suppressed exception", "SecondClass", 5))
+ createTestException("Second suppressed exception", "SecondClass"))
buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
val dumpedStr = dumpBuffer()
- // logs are limited to depth 2
// first suppressed exception
assertThat(dumpedStr)
- .contains("E Tag: Suppressed: " +
+ .contains("Suppressed: " +
"java.lang.RuntimeException: First suppressed exception")
- assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:1)")
- assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:2)")
- assertThat(dumpedStr)
- .doesNotContain("E Tag: \tat FirstClass.TestMethod(FirstClass.java:3)")
+ assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:1)")
+ assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:2)")
assertThat(dumpedStr)
- .contains("E Tag: Caused by: java.lang.RuntimeException: Cause of suppressed exp")
- assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:1)")
- assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:2)")
- assertThat(dumpedStr)
- .doesNotContain("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:3)")
+ .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp")
+ assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:1)")
+ assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:2)")
// second suppressed exception
assertThat(dumpedStr)
- .contains("E Tag: Suppressed: " +
+ .contains("Suppressed: " +
"java.lang.RuntimeException: Second suppressed exception")
- assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:1)")
- assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:2)")
- assertThat(dumpedStr)
- .doesNotContain("E Tag: \tat SecondClass.TestMethod(SecondClass.java:3)")
+ assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:1)")
+ assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:2)")
}
private fun createTestException(
- message: String,
- errorClass: String,
- stackTraceLength: Int,
- cause: Throwable? = null
+ message: String,
+ errorClass: String,
+ cause: Throwable? = null,
): Exception {
val exception = RuntimeException(message, cause)
- exception.stackTrace = createStackTraceElements(errorClass, stackTraceLength)
+ exception.stackTrace = (1..5).map { lineNumber ->
+ StackTraceElement(errorClass,
+ "TestMethod",
+ "$errorClass.java",
+ lineNumber)
+ }.toTypedArray()
return exception
}
@@ -152,16 +135,4 @@ class LogBufferTest : SysuiTestCase() {
buffer.dump(PrintWriter(outputWriter), tailLength = 100)
return outputWriter.toString()
}
-
- private fun createStackTraceElements(
- errorClass: String,
- stackTraceLength: Int
- ): Array<StackTraceElement> {
- return (1..stackTraceLength).map { lineNumber ->
- StackTraceElement(errorClass,
- "TestMethod",
- "$errorClass.java",
- lineNumber)
- }.toTypedArray()
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index 6a532d74967f..6468fe1a81d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -17,9 +17,9 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceAction
-import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -29,18 +29,18 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
private const val KEY = "TEST_KEY"
private const val KEY_ALT = "TEST_KEY_2"
@@ -433,7 +433,7 @@ class MediaDataFilterTest : SysuiTestCase() {
val dataCurrentAndActive = dataCurrent.copy(active = true)
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
eq(100), eq(true))
- assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
+ assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
// Smartspace update shouldn't be propagated for the empty rec list.
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
verify(logger, never()).logRecommendationAdded(any(), any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 568e0cb22f18..260bb8760f1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -254,4 +254,30 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
verify(mMediaOutputController).connectDevice(mMediaDevice2);
}
+
+ @Test
+ public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
+ when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
+ List<MediaDevice> selectableDevices = new ArrayList<>();
+ selectableDevices.add(mMediaDevice1);
+ when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(true);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ mViewHolder.mContainerLayout.performClick();
+
+ assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void onBindViewHolder_volumeControlChangeToEnabled_enableSeekbarAgain() {
+ when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+ assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
+
+ when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mSeekBar.isEnabled()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 5336ef09f368..ba49f3fa66ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -37,6 +37,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -78,6 +79,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -144,7 +146,25 @@ public class QSTileImplTest extends SysuiTestCase {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mTile.click(null /* view */);
- verify(mQsLogger).logTileClick(SPEC, StatusBarState.SHADE, Tile.STATE_ACTIVE);
+ verify(mQsLogger).logTileClick(eq(SPEC), eq(StatusBarState.SHADE), eq(Tile.STATE_ACTIVE),
+ anyInt());
+ }
+
+ @Test
+ public void testHandleClick_log() {
+ mTile.click(null);
+ mTile.click(null);
+ mTestableLooper.processAllMessages();
+ mTile.click(null);
+ mTestableLooper.processAllMessages();
+
+ InOrder inOrder = inOrder(mQsLogger);
+ inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(0));
+ inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(1));
+ inOrder.verify(mQsLogger).logHandleClick(SPEC, 0);
+ inOrder.verify(mQsLogger).logHandleClick(SPEC, 1);
+ inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(2));
+ inOrder.verify(mQsLogger).logHandleClick(SPEC, 2);
}
@Test
@@ -183,7 +203,25 @@ public class QSTileImplTest extends SysuiTestCase {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mTile.secondaryClick(null /* view */);
- verify(mQsLogger).logTileSecondaryClick(SPEC, StatusBarState.SHADE, Tile.STATE_ACTIVE);
+ verify(mQsLogger).logTileSecondaryClick(eq(SPEC), eq(StatusBarState.SHADE),
+ eq(Tile.STATE_ACTIVE), anyInt());
+ }
+
+ @Test
+ public void testHandleSecondaryClick_log() {
+ mTile.secondaryClick(null);
+ mTile.secondaryClick(null);
+ mTestableLooper.processAllMessages();
+ mTile.secondaryClick(null);
+ mTestableLooper.processAllMessages();
+
+ InOrder inOrder = inOrder(mQsLogger);
+ inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(0));
+ inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(1));
+ inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 0);
+ inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 1);
+ inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(2));
+ inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 2);
}
@Test
@@ -210,7 +248,25 @@ public class QSTileImplTest extends SysuiTestCase {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mTile.longClick(null /* view */);
- verify(mQsLogger).logTileLongClick(SPEC, StatusBarState.SHADE, Tile.STATE_ACTIVE);
+ verify(mQsLogger).logTileLongClick(eq(SPEC), eq(StatusBarState.SHADE),
+ eq(Tile.STATE_ACTIVE), anyInt());
+ }
+
+ @Test
+ public void testHandleLongClick_log() {
+ mTile.longClick(null);
+ mTile.longClick(null);
+ mTestableLooper.processAllMessages();
+ mTile.longClick(null);
+ mTestableLooper.processAllMessages();
+
+ InOrder inOrder = inOrder(mQsLogger);
+ inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(0));
+ inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(1));
+ inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 0);
+ inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 1);
+ inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(2));
+ inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 2);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index fee17c785ed2..18acf3f6ce53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -33,6 +33,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -621,7 +622,7 @@ public class BubblesTest extends SysuiTestCase {
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
// Send update
- mEntryListener.onEntryUpdated(mRow);
+ mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
// Nothing should have changed
// Notif is suppressed after expansion
@@ -789,7 +790,7 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testAddNotif_notBubble() {
mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
- mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
+ mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry(), /* fromSystem= */ true);
assertThat(mBubbleController.hasBubbles()).isFalse();
}
@@ -827,7 +828,7 @@ public class BubblesTest extends SysuiTestCase {
NotificationListenerService.Ranking ranking = new RankingBuilder(
mRow.getRanking()).setCanBubble(false).build();
mRow.setRanking(ranking);
- mEntryListener.onEntryUpdated(mRow);
+ mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
assertFalse(mBubbleController.hasBubbles());
verify(mDeleteIntent, never()).send();
@@ -1432,6 +1433,100 @@ public class BubblesTest extends SysuiTestCase {
assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
}
+ /**
+ * Verifies that if a bubble is in the overflow and a non-interruptive notification update
+ * comes in for it, it stays in the overflow but the entry is updated.
+ */
+ @Test
+ public void testNonInterruptiveUpdate_doesntBubbleFromOverflow() {
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
+ assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
+
+ // Dismiss the bubble so it's in the overflow
+ mBubbleController.removeBubble(
+ mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue();
+
+ // Update the entry to not show in shade
+ setMetadataFlags(mRow,
+ Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, /* enableFlag= */ true);
+ mBubbleController.updateBubble(mBubbleEntry,
+ /* suppressFlyout= */ false, /* showInShade= */ true);
+
+ // Check that the update was applied - shouldn't be show in shade
+ assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
+ // Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded)
+ verify(mBubbleController, times(1)).inflateAndAdd(
+ any(Bubble.class), anyBoolean(), anyBoolean());
+ }
+
+ /**
+ * Verifies that if a bubble is active, and a non-interruptive notification update comes in for
+ * it, it doesn't trigger a new inflate and add for that bubble.
+ */
+ @Test
+ public void testNonInterruptiveUpdate_doesntTriggerInflate() {
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
+ assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
+
+ // Update the entry to not show in shade
+ setMetadataFlags(mRow,
+ Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, /* enableFlag= */ true);
+ mBubbleController.updateBubble(mBubbleEntry,
+ /* suppressFlyout= */ false, /* showInShade= */ true);
+
+ // Check that the update was applied - shouldn't be show in shade
+ assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
+ // Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded)
+ verify(mBubbleController, times(1)).inflateAndAdd(
+ any(Bubble.class), anyBoolean(), anyBoolean());
+ }
+
+ /**
+ * Verifies that if a bubble is in the overflow and a non-interruptive notification update
+ * comes in for it with FLAG_BUBBLE that the flag is removed.
+ */
+ @Test
+ public void testNonInterruptiveUpdate_doesntOverrideOverflowFlagBubble() {
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
+ assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
+
+ // Dismiss the bubble so it's in the overflow
+ mBubbleController.removeBubble(
+ mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue();
+ // Once it's in the overflow it's not actively a bubble (doesn't have FLAG_BUBBLE)
+ Bubble b = mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey());
+ assertThat(b.isBubble()).isFalse();
+
+ // Send a non-notifying update that has FLAG_BUBBLE
+ mRow.getSbn().getNotification().flags = FLAG_BUBBLE;
+ assertThat(mRow.getSbn().getNotification().isBubbleNotification()).isTrue();
+ mBubbleController.updateBubble(mBubbleEntry,
+ /* suppressFlyout= */ false, /* showInShade= */ true);
+
+ // Verify that it still doesn't have FLAG_BUBBLE because it's in the overflow.
+ b = mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey());
+ assertThat(b.isBubble()).isFalse();
+ }
+
+ @Test
+ public void testNonSystemUpdatesIgnored() {
+ mEntryListener.onEntryAdded(mRow);
+ assertThat(mBubbleController.hasBubbles()).isTrue();
+
+ mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false);
+ mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false);
+ mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false);
+
+ // Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded)
+ verify(mBubbleController, times(1)).inflateAndAdd(
+ any(Bubble.class), anyBoolean(), anyBoolean());
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
diff --git a/services/Android.bp b/services/Android.bp
index 70692a63ff0f..1e4ce19f1541 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -102,6 +102,7 @@ filegroup {
":services.profcollect-sources",
":services.restrictions-sources",
":services.searchui-sources",
+ ":services.selectiontoolbar-sources",
":services.smartspace-sources",
":services.speech-sources",
":services.systemcaptions-sources",
@@ -157,6 +158,7 @@ java_library {
"services.profcollect",
"services.restrictions",
"services.searchui",
+ "services.selectiontoolbar",
"services.smartspace",
"services.speech",
"services.systemcaptions",
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index e1a0bfd25c9f..1fab28efb1ac 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -160,6 +160,7 @@ public class Watchdog implements Dumpable {
public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] {
"android.hardware.biometrics.face.IFace/",
"android.hardware.biometrics.fingerprint.IFingerprint/",
+ "android.hardware.graphics.composer3.IComposer/",
"android.hardware.input.processor.IInputProcessor/",
"android.hardware.light.ILights/",
"android.hardware.power.IPower/",
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 0cdf7bf62f55..339d5d4fe021 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -43,7 +43,6 @@ import android.content.pm.PackageChangeEvent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
-import android.content.pm.UserInfo;
import android.content.pm.VersionedPackage;
import android.net.Uri;
import android.os.Binder;
@@ -164,17 +163,6 @@ final class DeletePackageHelper {
return PackageManager.DELETE_FAILED_INTERNAL_ERROR;
}
- if (PackageManagerServiceUtils.isSystemApp(uninstalledPs)) {
- UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
- if (userInfo == null || (!userInfo.isAdmin() && !mUserManagerInternal.getUserInfo(
- mUserManagerInternal.getProfileParentId(userId)).isAdmin())) {
- Slog.w(TAG, "Not removing package " + packageName
- + " as only admin user (or their profile) may downgrade system apps");
- EventLog.writeEvent(0x534e4554, "170646036", -1, packageName);
- return PackageManager.DELETE_FAILED_USER_RESTRICTED;
- }
- }
-
disabledSystemPs = mPm.mSettings.getDisabledSystemPkgLPr(packageName);
// Static shared libs can be declared by any package, so let us not
// allow removing a package if it provides a lib others depend on.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 3b25f2876de4..8d94324a1414 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -586,7 +586,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
// Cannot embed activity across TaskFragments for activity result.
- if (a.resultTo != null && a.resultTo.getTaskFragment() != this) {
+ // If the activity that started for result is finishing, it's likely that this start mode
+ // is used to place an activity in the same task. Since the finishing activity won't be
+ // able to get the results, so it's OK to embed in a different TaskFragment.
+ if (a.resultTo != null && !a.resultTo.finishing && a.resultTo.getTaskFragment() != this) {
return EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 66c9f55b0403..8fd4b5aa6bee 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -338,6 +338,8 @@ public final class SystemServer implements Dumpable {
"com.android.server.contentcapture.ContentCaptureManagerService";
private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
"com.android.server.translation.TranslationManagerService";
+ private static final String SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS =
+ "com.android.server.selectiontoolbar.SelectionToolbarManagerService";
private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
"com.android.server.musicrecognition.MusicRecognitionManagerService";
private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
@@ -2634,6 +2636,11 @@ public final class SystemServer implements Dumpable {
Slog.d(TAG, "TranslationService not defined by OEM");
}
+ // Selection toolbar service
+ t.traceBegin("StartSelectionToolbarManagerService");
+ mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+
// NOTE: ClipboardService depends on ContentCapture and Autofill
t.traceBegin("StartClipboardService");
mSystemServiceManager.startService(ClipboardService.class);
diff --git a/services/selectiontoolbar/Android.bp b/services/selectiontoolbar/Android.bp
new file mode 100644
index 000000000000..cc6405f97bc3
--- /dev/null
+++ b/services/selectiontoolbar/Android.bp
@@ -0,0 +1,22 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "services.selectiontoolbar-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.selectiontoolbar",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.selectiontoolbar-sources"],
+ libs: ["services.core"],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
deleted file mode 100644
index e30f3d26119f..000000000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2022 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.server.pm
-
-import android.content.pm.PackageManager
-import android.content.pm.UserInfo
-import android.os.Build
-import android.util.Log
-import com.android.server.testutils.any
-import com.android.server.testutils.spy
-import com.android.server.testutils.whenever
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.doAnswer
-
-@RunWith(JUnit4::class)
-class DeletePackageHelperTest {
-
- @Rule
- @JvmField
- val rule = MockSystemRule()
-
- private lateinit var mPms: PackageManagerService
- private lateinit var mUserManagerInternal: UserManagerInternal
-
- @Before
- @Throws(Exception::class)
- fun setup() {
- Log.i("system.out", "setup", Exception())
- rule.system().stageNominalSystemState()
- rule.system().stageScanExistingPackage(
- "a.data.package", 1L, rule.system().dataAppDirectory)
-
- mUserManagerInternal = rule.mocks().injector.userManagerInternal
- whenever(mUserManagerInternal.getUserIds()).thenReturn(intArrayOf(0, 1))
-
- mPms = createPackageManagerService()
- doAnswer { false }.`when`(mPms).isPackageDeviceAdmin(any(), any())
- doAnswer { null }.`when`(mPms).freezePackageForDelete(any(), any(), any(), any())
- }
-
- private fun createPackageManagerService(): PackageManagerService {
- return spy(PackageManagerService(rule.mocks().injector,
- false /*coreOnly*/,
- false /*factoryTest*/,
- MockSystem.DEFAULT_VERSION_INFO.fingerprint,
- false /*isEngBuild*/,
- false /*isUserDebugBuild*/,
- Build.VERSION_CODES.CUR_DEVELOPMENT,
- Build.VERSION.INCREMENTAL))
- }
-
- @Test
- fun deleteSystemPackageFailsIfNotAdminAndNotProfile() {
- val ps = mPms.mSettings.getPackageLPr("a.data.package")
- whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
- whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0))
- whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1)
-
- val dph = DeletePackageHelper(mPms)
- val result = dph.deletePackageX("a.data.package", 1L, 1,
- PackageManager.DELETE_SYSTEM_APP, false)
-
- assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED)
- }
-
- @Test
- fun deleteSystemPackageFailsIfProfileOfNonAdmin() {
- val userId = 1
- val parentId = 5
- val ps = mPms.mSettings.getPackageLPr("a.data.package")
- whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
- whenever(mUserManagerInternal.getUserInfo(userId)).thenReturn(
- UserInfo(userId, "test", UserInfo.FLAG_PROFILE))
- whenever(mUserManagerInternal.getProfileParentId(userId)).thenReturn(parentId)
- whenever(mUserManagerInternal.getUserInfo(parentId)).thenReturn(
- UserInfo(userId, "testparent", 0))
-
- val dph = DeletePackageHelper(mPms)
- val result = dph.deletePackageX("a.data.package", 1L, userId,
- PackageManager.DELETE_SYSTEM_APP, false)
-
- assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED)
- }
-
- @Test
- fun deleteSystemPackageSucceedsIfAdmin() {
- val ps = mPms.mSettings.getPackageLPr("a.data.package")
- whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
- whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(
- UserInfo(1, "test", UserInfo.FLAG_ADMIN))
-
- val dph = DeletePackageHelper(mPms)
- val result = dph.deletePackageX("a.data.package", 1L, 1,
- PackageManager.DELETE_SYSTEM_APP, false)
-
- assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED)
- }
-
- @Test
- fun deleteSystemPackageSucceedsIfProfileOfAdmin() {
- val userId = 1
- val parentId = 5
- val ps = mPms.mSettings.getPackageLPr("a.data.package")
- whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
- whenever(mUserManagerInternal.getUserInfo(userId)).thenReturn(
- UserInfo(userId, "test", UserInfo.FLAG_PROFILE))
- whenever(mUserManagerInternal.getProfileParentId(userId)).thenReturn(parentId)
- whenever(mUserManagerInternal.getUserInfo(parentId)).thenReturn(
- UserInfo(userId, "testparent", UserInfo.FLAG_ADMIN))
-
- val dph = DeletePackageHelper(mPms)
- val result = dph.deletePackageX("a.data.package", 1L, userId,
- PackageManager.DELETE_SYSTEM_APP, false)
-
- assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED)
- }
-} \ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 1096351524d7..88eadfceedb6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -32,6 +32,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
@@ -468,6 +469,10 @@ public class TaskFragmentTest extends WindowTestsBase {
newActivity.resultTo = activity;
assertEquals(EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
newTaskFragment.isAllowedToEmbedActivity(newActivity));
+
+ // Allow embedding if the resultTo activity is finishing.
+ activity.finishing = true;
+ assertEquals(EMBEDDING_ALLOWED, newTaskFragment.isAllowedToEmbedActivity(newActivity));
}
@Test
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java
index 41f9faef99df..6fc4b6707e9e 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java
@@ -31,15 +31,14 @@ public final class UsbPortHalInstance {
public static UsbPortHal getInstance(UsbPortManager portManager, IndentingPrintWriter pw) {
logAndPrint(Log.DEBUG, null, "Querying USB HAL version");
- if (UsbPortHidl.isServicePresent(null)) {
- logAndPrint(Log.INFO, null, "USB HAL HIDL present");
- return new UsbPortHidl(portManager, pw);
- }
if (UsbPortAidl.isServicePresent(null)) {
logAndPrint(Log.INFO, null, "USB HAL AIDL present");
return new UsbPortAidl(portManager, pw);
}
-
+ if (UsbPortHidl.isServicePresent(null)) {
+ logAndPrint(Log.INFO, null, "USB HAL HIDL present");
+ return new UsbPortHidl(portManager, pw);
+ }
return null;
}
}