summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmds/bootanimation/Android.bp16
-rw-r--r--cmds/bootanimation/BootAnimation.cpp453
-rw-r--r--cmds/bootanimation/BootAnimation.h50
-rw-r--r--cmds/bootanimation/bootanimation_flags.aconfig12
-rw-r--r--core/api/current.txt11
-rw-r--r--core/java/android/app/WallpaperManager.java8
-rw-r--r--core/java/android/app/wearable/flags.aconfig8
-rw-r--r--core/java/android/os/SystemClock.java2
-rw-r--r--core/java/android/text/flags/flags.aconfig7
-rw-r--r--core/java/android/text/style/TtsSpan.java85
-rw-r--r--core/java/android/view/InsetsState.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java2
-rw-r--r--core/java/android/widget/RemoteViews.java558
-rw-r--r--core/java/android/window/DesktopModeFlags.java (renamed from core/java/android/window/flags/DesktopModeFlags.java)2
-rw-r--r--core/java/com/android/internal/policy/DecorView.java4
-rw-r--r--core/proto/android/widget/remoteviews.proto69
-rw-r--r--core/tests/coretests/src/android/window/DesktopModeFlagsTest.java (renamed from core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java)47
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java159
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java)67
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt2
-rw-r--r--libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp67
-rw-r--r--libs/hwui/pipeline/skia/BackdropFilterDrawable.h15
-rw-r--r--libs/hwui/tests/common/scenes/BackdropBlur.cpp67
-rw-r--r--libs/hwui/tests/unit/RenderNodeDrawableTests.cpp2
-rw-r--r--media/java/android/media/MediaCas.java24
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig7
-rw-r--r--packages/Shell/AndroidManifest.xml6
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig8
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt61
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt15
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt11
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt107
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt73
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/app/WallpaperManagerKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt43
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractorKosmos.kt23
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java34
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java13
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig11
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java81
-rw-r--r--services/core/java/com/android/server/display/mode/Vote.java2
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java36
-rw-r--r--services/core/java/com/android/server/power/feature/PowerManagerFlags.java11
-rw-r--r--services/core/java/com/android/server/power/feature/power_flags.aconfig7
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java19
-rw-r--r--services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java328
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java2
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeHelper.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java61
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java103
-rw-r--r--services/tests/powerstatstests/Android.bp6
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java433
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java25
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java26
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java81
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java9
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java206
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java10
-rw-r--r--tests/graphics/SilkFX/res/layout/view_blur_behind.xml148
-rw-r--r--tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt3
-rw-r--r--tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt30
107 files changed, 3222 insertions, 961 deletions
diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp
index 3534624a58a2..f80ccf7a56fb 100644
--- a/cmds/bootanimation/Android.bp
+++ b/cmds/bootanimation/Android.bp
@@ -7,6 +7,18 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
+aconfig_declarations {
+ name: "bootanimation_flags",
+ package: "com.android.graphics.bootanimation.flags",
+ container: "system",
+ srcs: ["bootanimation_flags.aconfig"],
+}
+
+cc_aconfig_library {
+ name: "libbootanimationflags",
+ aconfig_declarations: "bootanimation_flags",
+}
+
cc_defaults {
name: "bootanimation_defaults",
@@ -28,6 +40,10 @@ cc_defaults {
"liblog",
"libutils",
],
+
+ static_libs: [
+ "libbootanimationflags",
+ ],
}
// bootanimation executable
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 87c9fa4dbe28..14e238768f41 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_NDEBUG 0
+
#define LOG_TAG "BootAnimation"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
@@ -61,6 +62,8 @@
#include "BootAnimation.h"
+#include <com_android_graphics_bootanimation_flags.h>
+
#define ANIM_PATH_MAX 255
#define STR(x) #x
#define STRTO(x) STR(x)
@@ -448,19 +451,21 @@ public:
auto token = SurfaceComposerClient::getPhysicalDisplayToken(
event.header.displayId);
- if (token != mBootAnimation->mDisplayToken) {
+ auto firstDisplay = mBootAnimation->mDisplays.front();
+ if (token != firstDisplay.displayToken) {
// ignore hotplug of a secondary display
continue;
}
DisplayMode displayMode;
const status_t error = SurfaceComposerClient::getActiveDisplayMode(
- mBootAnimation->mDisplayToken, &displayMode);
+ firstDisplay.displayToken, &displayMode);
if (error != NO_ERROR) {
SLOGE("Can't get active display mode.");
}
mBootAnimation->resizeSurface(displayMode.resolution.getWidth(),
- displayMode.resolution.getHeight());
+ displayMode.resolution.getHeight(),
+ firstDisplay);
}
}
} while (numEvents > 0);
@@ -506,91 +511,106 @@ ui::Size BootAnimation::limitSurfaceSize(int width, int height) const {
status_t BootAnimation::readyToRun() {
ATRACE_CALL();
mAssets.addDefaultAssets();
+ return initDisplaysAndSurfaces();
+}
- const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
- if (ids.empty()) {
- SLOGE("Failed to get ID for any displays\n");
+status_t BootAnimation::initDisplaysAndSurfaces() {
+ std::vector<PhysicalDisplayId> displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
+ if (displayIds.empty()) {
+ SLOGE("Failed to get ID for any displays");
return NAME_NOT_FOUND;
}
- mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
- if (mDisplayToken == nullptr) {
- return NAME_NOT_FOUND;
+ // If Multi-Display isn't explicitly enabled, ignore all displays after the first one
+ if (!com::android::graphics::bootanimation::flags::multidisplay()) {
+ displayIds.erase(displayIds.begin() + 1, displayIds.end());
}
- DisplayMode displayMode;
- const status_t error =
- SurfaceComposerClient::getActiveDisplayMode(mDisplayToken, &displayMode);
- if (error != NO_ERROR) {
- return error;
+ for (const auto id : displayIds) {
+ if (const auto token = SurfaceComposerClient::getPhysicalDisplayToken(id)) {
+ mDisplays.push_back({.displayToken = token});
+ } else {
+ SLOGE("Failed to get display token for a display");
+ SLOGE("Failed to get display token for display %" PRIu64, id.value);
+ return NAME_NOT_FOUND;
+ }
}
- mMaxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
- mMaxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);
- ui::Size resolution = displayMode.resolution;
- resolution = limitSurfaceSize(resolution.width, resolution.height);
- // create the native surface
- sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
- resolution.getWidth(), resolution.getHeight(), PIXEL_FORMAT_RGB_565,
- ISurfaceComposerClient::eOpaque);
-
- SurfaceComposerClient::Transaction t;
- t.setDisplayLayerStack(mDisplayToken, ui::DEFAULT_LAYER_STACK);
- t.setLayerStack(control, ui::DEFAULT_LAYER_STACK);
-
- t.setLayer(control, 0x40000000)
- .apply();
-
- sp<Surface> s = control->getSurface();
-
- // initialize opengl and egl
- EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- eglInitialize(display, nullptr, nullptr);
- EGLConfig config = getEglConfig(display);
- EGLSurface surface = eglCreateWindowSurface(display, config, s.get(), nullptr);
- // Initialize egl context with client version number 2.0.
+ // Initialize EGL
+ mEgl = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ eglInitialize(mEgl, nullptr, nullptr);
+ EGLConfig config = getEglConfig(mEgl);
EGLint contextAttributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
- EGLContext context = eglCreateContext(display, config, nullptr, contextAttributes);
- EGLint w, h;
- eglQuerySurface(display, surface, EGL_WIDTH, &w);
- eglQuerySurface(display, surface, EGL_HEIGHT, &h);
+ mEglContext = eglCreateContext(mEgl, config, nullptr, contextAttributes);
- if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
- return NO_INIT;
- }
-
- mDisplay = display;
- mContext = context;
- mSurface = surface;
- mInitWidth = mWidth = w;
- mInitHeight = mHeight = h;
- mFlingerSurfaceControl = control;
- mFlingerSurface = s;
- mTargetInset = -1;
-
- // Rotate the boot animation according to the value specified in the sysprop
- // ro.bootanim.set_orientation_<display_id>. Four values are supported: ORIENTATION_0,
- // ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270.
- // If the value isn't specified or is ORIENTATION_0, nothing will be changed.
- // This is needed to support having boot animation in orientations different from the natural
- // device orientation. For example, on tablets that may want to keep natural orientation
- // portrait for applications compatibility and to have the boot animation in landscape.
- rotateAwayFromNaturalOrientationIfNeeded();
+ mMaxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
+ mMaxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);
- projectSceneToWindow();
+ for (size_t displayIdx = 0; displayIdx < mDisplays.size(); displayIdx++) {
+ auto& display = mDisplays[displayIdx];
+ DisplayMode displayMode;
+ const status_t error =
+ SurfaceComposerClient::getActiveDisplayMode(display.displayToken, &displayMode);
+ if (error != NO_ERROR) {
+ return error;
+ }
+ ui::Size resolution = displayMode.resolution;
+ // Clamp each surface to max size
+ resolution = limitSurfaceSize(resolution.width, resolution.height);
+ // Create the native surface
+ display.surfaceControl =
+ session()->createSurface(String8("BootAnimation"), resolution.width,
+ resolution.height, PIXEL_FORMAT_RGB_565,
+ ISurfaceComposerClient::eOpaque);
+ // Attach surface to layerstack, and associate layerstack with physical display
+ configureDisplayAndLayerStack(display, ui::LayerStack::fromValue(displayIdx));
+ display.surface = display.surfaceControl->getSurface();
+ display.eglSurface = eglCreateWindowSurface(mEgl, config, display.surface.get(), nullptr);
+
+ EGLint w, h;
+ eglQuerySurface(mEgl, display.eglSurface, EGL_WIDTH, &w);
+ eglQuerySurface(mEgl, display.eglSurface, EGL_HEIGHT, &h);
+ if (eglMakeCurrent(mEgl, display.eglSurface, display.eglSurface,
+ mEglContext) == EGL_FALSE) {
+ return NO_INIT;
+ }
+ display.initWidth = display.width = w;
+ display.initHeight = display.height = h;
+ mTargetInset = -1;
+
+ // Rotate the boot animation according to the value specified in the sysprop
+ // ro.bootanim.set_orientation_<display_id>. Four values are supported: ORIENTATION_0,
+ // ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270.
+ // If the value isn't specified or is ORIENTATION_0, nothing will be changed.
+ // This is needed to support boot animation in orientations different from the natural
+ // device orientation. For example, on tablets that may want to keep natural orientation
+ // portrait for applications compatibility and to have the boot animation in landscape.
+ rotateAwayFromNaturalOrientationIfNeeded(display);
+
+ projectSceneToWindow(display);
+ } // end iteration over all display tokens
// Register a display event receiver
mDisplayEventReceiver = std::make_unique<DisplayEventReceiver>();
status_t status = mDisplayEventReceiver->initCheck();
SLOGE_IF(status != NO_ERROR, "Initialization of DisplayEventReceiver failed with status: %d",
- status);
+ status);
mLooper->addFd(mDisplayEventReceiver->getFd(), 0, Looper::EVENT_INPUT,
- new DisplayEventCallback(this), nullptr);
+ new DisplayEventCallback(this), nullptr);
return NO_ERROR;
}
-void BootAnimation::rotateAwayFromNaturalOrientationIfNeeded() {
+void BootAnimation::configureDisplayAndLayerStack(const Display& display,
+ ui::LayerStack layerStack) {
+ SurfaceComposerClient::Transaction t;
+ t.setDisplayLayerStack(display.displayToken, layerStack);
+ t.setLayerStack(display.surfaceControl, layerStack)
+ .setLayer(display.surfaceControl, 0x40000000)
+ .apply();
+}
+
+void BootAnimation::rotateAwayFromNaturalOrientationIfNeeded(Display& display) {
ATRACE_CALL();
const auto orientation = parseOrientationProperty();
@@ -600,16 +620,16 @@ void BootAnimation::rotateAwayFromNaturalOrientationIfNeeded() {
}
if (orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270) {
- std::swap(mWidth, mHeight);
- std::swap(mInitWidth, mInitHeight);
- mFlingerSurfaceControl->updateDefaultBufferSize(mWidth, mHeight);
+ std::swap(display.width, display.height);
+ std::swap(display.initWidth, display.initHeight);
+ display.surfaceControl->updateDefaultBufferSize(display.width, display.height);
}
- Rect displayRect(0, 0, mWidth, mHeight);
- Rect layerStackRect(0, 0, mWidth, mHeight);
+ Rect displayRect(0, 0, display.width, display.height);
+ Rect layerStackRect(0, 0, display.width, display.height);
SurfaceComposerClient::Transaction t;
- t.setDisplayProjection(mDisplayToken, orientation, layerStackRect, displayRect);
+ t.setDisplayProjection(display.displayToken, orientation, layerStackRect, displayRect);
t.apply();
}
@@ -640,38 +660,37 @@ ui::Rotation BootAnimation::parseOrientationProperty() {
return ui::ROTATION_0;
}
-void BootAnimation::projectSceneToWindow() {
+void BootAnimation::projectSceneToWindow(const Display& display) {
ATRACE_CALL();
- glViewport(0, 0, mWidth, mHeight);
- glScissor(0, 0, mWidth, mHeight);
+ glViewport(0, 0, display.width, display.height);
+ glScissor(0, 0, display.width, display.height);
}
-void BootAnimation::resizeSurface(int newWidth, int newHeight) {
+void BootAnimation::resizeSurface(int newWidth, int newHeight, Display& display) {
ATRACE_CALL();
// We assume this function is called on the animation thread.
- if (newWidth == mWidth && newHeight == mHeight) {
+ if (newWidth == display.width && newHeight == display.height) {
return;
}
- SLOGV("Resizing the boot animation surface to %d %d", newWidth, newHeight);
- eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- eglDestroySurface(mDisplay, mSurface);
+ eglMakeCurrent(mEgl, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroySurface(mEgl, display.eglSurface);
const auto limitedSize = limitSurfaceSize(newWidth, newHeight);
- mWidth = limitedSize.width;
- mHeight = limitedSize.height;
-
- mFlingerSurfaceControl->updateDefaultBufferSize(mWidth, mHeight);
- EGLConfig config = getEglConfig(mDisplay);
- EGLSurface surface = eglCreateWindowSurface(mDisplay, config, mFlingerSurface.get(), nullptr);
- if (eglMakeCurrent(mDisplay, surface, surface, mContext) == EGL_FALSE) {
- SLOGE("Can't make the new surface current. Error %d", eglGetError());
+ display.width = limitedSize.width;
+ display.height = limitedSize.height;
+
+ display.surfaceControl->updateDefaultBufferSize(display.width, display.height);
+ EGLConfig config = getEglConfig(mEgl);
+ EGLSurface eglSurface = eglCreateWindowSurface(mEgl, config, display.surface.get(), nullptr);
+ if (eglMakeCurrent(mEgl, eglSurface, eglSurface, mEglContext) == EGL_FALSE) {
+ SLOGE("Can't make the new eglSurface current. Error %d", eglGetError());
return;
}
- projectSceneToWindow();
+ projectSceneToWindow(display);
- mSurface = surface;
+ display.eglSurface = eglSurface;
}
bool BootAnimation::preloadAnimation() {
@@ -801,24 +820,26 @@ bool BootAnimation::threadLoop() {
// animation.
if (mZipFileName.empty()) {
ALOGD("No animation file");
- result = android();
+ result = android(mDisplays.front());
} else {
result = movie();
}
mCallbacks->shutdown();
- eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- eglDestroyContext(mDisplay, mContext);
- eglDestroySurface(mDisplay, mSurface);
- mFlingerSurface.clear();
- mFlingerSurfaceControl.clear();
- eglTerminate(mDisplay);
+ eglMakeCurrent(mEgl, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroyContext(mEgl, mEglContext);
+ for (auto& display : mDisplays) {
+ eglDestroySurface(mEgl, display.eglSurface);
+ display.surface.clear();
+ display.surfaceControl.clear();
+ }
+ eglTerminate(mEgl);
eglReleaseThread();
IPCThreadState::self()->stopProcess();
return result;
}
-bool BootAnimation::android() {
+bool BootAnimation::android(const Display& display) {
ATRACE_CALL();
glActiveTexture(GL_TEXTURE0);
@@ -836,7 +857,7 @@ bool BootAnimation::android() {
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
- eglSwapBuffers(mDisplay, mSurface);
+ eglSwapBuffers(mEgl, display.eglSurface);
// Blend state
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -844,11 +865,11 @@ bool BootAnimation::android() {
const nsecs_t startTime = systemTime();
do {
processDisplayEvents();
- const GLint xc = (mWidth - mAndroid[0].w) / 2;
- const GLint yc = (mHeight - mAndroid[0].h) / 2;
+ const GLint xc = (display.width - mAndroid[0].w) / 2;
+ const GLint yc = (display.height - mAndroid[0].h) / 2;
const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
- glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
- updateRect.height());
+ glScissor(updateRect.left, display.height - updateRect.bottom, updateRect.width(),
+ updateRect.height());
nsecs_t now = systemTime();
double time = now - startTime;
@@ -862,14 +883,14 @@ bool BootAnimation::android() {
glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
- drawTexturedQuad(x, yc, mAndroid[1].w, mAndroid[1].h);
- drawTexturedQuad(x + mAndroid[1].w, yc, mAndroid[1].w, mAndroid[1].h);
+ drawTexturedQuad(x, yc, mAndroid[1].w, mAndroid[1].h, display);
+ drawTexturedQuad(x + mAndroid[1].w, yc, mAndroid[1].w, mAndroid[1].h, display);
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
- drawTexturedQuad(xc, yc, mAndroid[0].w, mAndroid[0].h);
+ drawTexturedQuad(xc, yc, mAndroid[0].w, mAndroid[0].h, display);
- EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
+ EGLBoolean res = eglSwapBuffers(mEgl, display.eglSurface);
if (res == EGL_FALSE)
break;
@@ -888,7 +909,7 @@ bool BootAnimation::android() {
void BootAnimation::checkExit() {
ATRACE_CALL();
- // Allow surface flinger to gracefully request shutdown
+ // Allow SurfaceFlinger to gracefully request shutdown
char value[PROPERTY_VALUE_MAX];
property_get(EXIT_PROP_NAME, value, "0");
int exitnow = atoi(value);
@@ -1034,7 +1055,8 @@ status_t BootAnimation::initFont(Font* font, const char* fallback) {
return status;
}
-void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {
+void BootAnimation::drawText(const char* str, const Font& font, bool bold,
+ int* x, int* y, const Display& display) {
ATRACE_CALL();
glEnable(GL_BLEND); // Allow us to draw on top of the animation
glBindTexture(GL_TEXTURE_2D, font.texture.name);
@@ -1045,14 +1067,14 @@ void BootAnimation::drawText(const char* str, const Font& font, bool bold, int*
const int strWidth = font.char_width * len;
if (*x == TEXT_CENTER_VALUE) {
- *x = (mWidth - strWidth) / 2;
+ *x = (display.width - strWidth) / 2;
} else if (*x < 0) {
- *x = mWidth + *x - strWidth;
+ *x = display.width + *x - strWidth;
}
if (*y == TEXT_CENTER_VALUE) {
- *y = (mHeight - font.char_height) / 2;
+ *y = (display.height - font.char_height) / 2;
} else if (*y < 0) {
- *y = mHeight + *y - font.char_height;
+ *y = display.height + *y - font.char_height;
}
for (int i = 0; i < len; i++) {
@@ -1072,7 +1094,7 @@ void BootAnimation::drawText(const char* str, const Font& font, bool bold, int*
float v1 = v0 + 1.0f / FONT_NUM_ROWS / 2;
float u1 = u0 + 1.0f / FONT_NUM_COLS;
glUniform4f(mTextCropAreaLocation, u0, v0, u1, v1);
- drawTexturedQuad(*x, *y, font.char_width, font.char_height);
+ drawTexturedQuad(*x, *y, font.char_width, font.char_height, display);
*x += font.char_width;
}
@@ -1082,7 +1104,8 @@ void BootAnimation::drawText(const char* str, const Font& font, bool bold, int*
}
// We render 12 or 24 hour time.
-void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) {
+void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos,
+ const Display& display) {
ATRACE_CALL();
static constexpr char TIME_FORMAT_12[] = "%l:%M";
static constexpr char TIME_FORMAT_24[] = "%H:%M";
@@ -1105,10 +1128,11 @@ void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos)
char* out = timeBuff[0] == ' ' ? &timeBuff[1] : &timeBuff[0];
int x = xPos;
int y = yPos;
- drawText(out, font, false, &x, &y);
+ drawText(out, font, false, &x, &y, display);
}
-void BootAnimation::drawProgress(int percent, const Font& font, const int xPos, const int yPos) {
+void BootAnimation::drawProgress(int percent, const Font& font, const int xPos, const int yPos,
+ const Display& display) {
ATRACE_CALL();
static constexpr int PERCENT_LENGTH = 5;
@@ -1118,7 +1142,7 @@ void BootAnimation::drawProgress(int percent, const Font& font, const int xPos,
sprintf(percentBuff, "%d;", percent);
int x = xPos;
int y = yPos;
- drawText(percentBuff, font, false, &x, &y);
+ drawText(percentBuff, font, false, &x, &y, display);
}
bool BootAnimation::parseAnimationDesc(Animation& animation) {
@@ -1247,8 +1271,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) {
bool BootAnimation::preloadZip(Animation& animation) {
ATRACE_CALL();
- // read all the data structures
- const size_t pcount = animation.parts.size();
+ const size_t numParts = animation.parts.size();
void *cookie = nullptr;
ZipFileRO* zip = animation.zip;
if (!zip->startIteration(&cookie)) {
@@ -1284,8 +1307,8 @@ bool BootAnimation::preloadZip(Animation& animation) {
continue;
}
- for (size_t j = 0; j < pcount; j++) {
- if (path.string() == animation.parts[j].path.c_str()) {
+ for (size_t partIdx = 0; partIdx < numParts; partIdx++) {
+ if (path.string() == animation.parts[partIdx].path.c_str()) {
uint16_t method;
// supports only stored png files
if (zip->getEntryInfo(entry, &method, nullptr, nullptr, nullptr, nullptr,
@@ -1293,7 +1316,7 @@ bool BootAnimation::preloadZip(Animation& animation) {
if (method == ZipFileRO::kCompressStored) {
FileMap* map = zip->createEntryFileMap(entry);
if (map) {
- Animation::Part& part(animation.parts.editItemAt(j));
+ Animation::Part& part(animation.parts.editItemAt(partIdx));
if (leaf == "audio.wav") {
// a part may have at most one audio file
part.audioData = (uint8_t *)map->getDataPtr();
@@ -1324,7 +1347,8 @@ bool BootAnimation::preloadZip(Animation& animation) {
// If there is trimData present, override the positioning defaults.
for (Animation::Part& part : animation.parts) {
const char* trimDataStr = part.trimData.c_str();
- for (size_t frameIdx = 0; frameIdx < part.frames.size(); frameIdx++) {
+ const size_t numFramesInPart = part.frames.size();
+ for (size_t frameIdxInPart = 0; frameIdxInPart < numFramesInPart; frameIdxInPart++) {
const char* endl = strstr(trimDataStr, "\n");
// No more trimData for this part.
if (endl == nullptr) {
@@ -1335,7 +1359,7 @@ bool BootAnimation::preloadZip(Animation& animation) {
trimDataStr = ++endl;
int width = 0, height = 0, x = 0, y = 0;
if (sscanf(lineStr, "%dx%d+%d+%d", &width, &height, &x, &y) == 4) {
- Animation::Frame& frame(part.frames.editItemAt(frameIdx));
+ Animation::Frame& frame(part.frames.editItemAt(frameIdxInPart));
frame.trimWidth = width;
frame.trimHeight = height;
frame.trimX = x;
@@ -1458,13 +1482,15 @@ float mapLinear(float x, float a1, float a2, float b1, float b2) {
return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
}
-void BootAnimation::drawTexturedQuad(float xStart, float yStart, float width, float height) {
+void BootAnimation::drawTexturedQuad(float xStart, float yStart,
+ float width, float height,
+ const Display& display) {
ATRACE_CALL();
// Map coordinates from screen space to world space.
- float x0 = mapLinear(xStart, 0, mWidth, -1, 1);
- float y0 = mapLinear(yStart, 0, mHeight, -1, 1);
- float x1 = mapLinear(xStart + width, 0, mWidth, -1, 1);
- float y1 = mapLinear(yStart + height, 0, mHeight, -1, 1);
+ float x0 = mapLinear(xStart, 0, display.width, -1, 1);
+ float y0 = mapLinear(yStart, 0, display.height, -1, 1);
+ float x1 = mapLinear(xStart + width, 0, display.width, -1, 1);
+ float y1 = mapLinear(yStart + height, 0, display.height, -1, 1);
// Update quad vertex positions.
quadPositions[0] = x0;
quadPositions[1] = y0;
@@ -1511,7 +1537,7 @@ void BootAnimation::initDynamicColors() {
bool BootAnimation::playAnimation(const Animation& animation) {
ATRACE_CALL();
- const size_t pcount = animation.parts.size();
+ const size_t numParts = animation.parts.size();
nsecs_t frameDuration = s2ns(1) / animation.fps;
SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
@@ -1521,9 +1547,9 @@ bool BootAnimation::playAnimation(const Animation& animation) {
int lastDisplayedProgress = 0;
int colorTransitionStart = animation.colorTransitionStart;
int colorTransitionEnd = animation.colorTransitionEnd;
- for (size_t i=0 ; i<pcount ; i++) {
- const Animation::Part& part(animation.parts[i]);
- const size_t fcount = part.frames.size();
+ for (size_t partIdx = 0; partIdx < numParts; partIdx++) {
+ const Animation::Part& part(animation.parts[partIdx]);
+ const size_t numFramesInPart = part.frames.size();
glBindTexture(GL_TEXTURE_2D, 0);
// Handle animation package
@@ -1535,7 +1561,9 @@ bool BootAnimation::playAnimation(const Animation& animation) {
}
// process the part not only while the count allows but also if already fading
- for (int r=0 ; !part.count || r<part.count || fadedFramesCount > 0 ; r++) {
+ for (int frameIdx = 0;
+ !part.count || frameIdx < part.count || fadedFramesCount > 0;
+ frameIdx++) {
if (shouldStopPlayingPart(part, fadedFramesCount, lastDisplayedProgress)) break;
// It's possible that the sysprops were not loaded yet at this boot phase.
@@ -1550,12 +1578,12 @@ bool BootAnimation::playAnimation(const Animation& animation) {
const int transitionLength = colorTransitionEnd - colorTransitionStart;
if (part.postDynamicColoring) {
colorTransitionStart = 0;
- colorTransitionEnd = fmin(transitionLength, fcount - 1);
+ colorTransitionEnd = fmin(transitionLength, numFramesInPart - 1);
}
}
}
- mCallbacks->playPart(i, part, r);
+ mCallbacks->playPart(partIdx, part, frameIdx);
glClearColor(
part.backgroundColor[0],
@@ -1569,11 +1597,10 @@ bool BootAnimation::playAnimation(const Animation& animation) {
// For the last animation, if we have progress indicator from
// the system, display it.
- int currentProgress = android::base::GetIntProperty(PROGRESS_PROP_NAME, 0);
- bool displayProgress = animation.progressEnabled &&
- (i == (pcount -1)) && currentProgress != 0;
+ const bool displayProgress = animation.progressEnabled && (partIdx == (numParts - 1)) &&
+ android::base::GetIntProperty(PROGRESS_PROP_NAME, 0) != 0;
- for (size_t j=0 ; j<fcount ; j++) {
+ for (size_t frameIdxInPart = 0; frameIdxInPart < numFramesInPart; frameIdxInPart++) {
if (shouldStopPlayingPart(part, fadedFramesCount, lastDisplayedProgress)) break;
// Color progress is
@@ -1584,21 +1611,15 @@ bool BootAnimation::playAnimation(const Animation& animation) {
// - 1 for parts that come after.
float colorProgress = part.useDynamicColoring
? fmin(fmax(
- ((float)j - colorTransitionStart) /
+ (static_cast<float>(frameIdxInPart) - colorTransitionStart) /
fmax(colorTransitionEnd - colorTransitionStart, 1.0f), 0.0f), 1.0f)
: (part.postDynamicColoring ? 1 : 0);
-
processDisplayEvents();
- const double ratio_w = static_cast<double>(mWidth) / mInitWidth;
- const double ratio_h = static_cast<double>(mHeight) / mInitHeight;
- const int animationX = (mWidth - animation.width * ratio_w) / 2;
- const int animationY = (mHeight - animation.height * ratio_h) / 2;
-
- const Animation::Frame& frame(part.frames[j]);
+ const Animation::Frame& frame(part.frames[frameIdxInPart]);
nsecs_t lastFrame = systemTime();
- if (r > 0) {
+ if (frameIdx > 0) {
glBindTexture(GL_TEXTURE_2D, frame.tid);
} else {
if (part.count != 1) {
@@ -1611,17 +1632,6 @@ bool BootAnimation::playAnimation(const Animation& animation) {
initTexture(frame.map, &w, &h, false /* don't premultiply alpha */);
}
- const int trimWidth = frame.trimWidth * ratio_w;
- const int trimHeight = frame.trimHeight * ratio_h;
- const int trimX = frame.trimX * ratio_w;
- const int trimY = frame.trimY * ratio_h;
- const int xc = animationX + trimX;
- const int yc = animationY + trimY;
- glClear(GL_COLOR_BUFFER_BIT);
- // specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
- // which is equivalent to mHeight - (yc + frame.trimHeight)
- const int frameDrawY = mHeight - (yc + trimHeight);
-
float fade = 0;
// if the part hasn't been stopped yet then continue fading if necessary
if (exitPending() && part.hasFadingPhase()) {
@@ -1630,40 +1640,66 @@ bool BootAnimation::playAnimation(const Animation& animation) {
fadedFramesCount = MAX_FADED_FRAMES_COUNT; // no more fading
}
}
- glUseProgram(mImageShader);
- glUniform1i(mImageTextureLocation, 0);
- glUniform1f(mImageFadeLocation, fade);
- if (animation.dynamicColoringEnabled) {
- glUniform1f(mImageColorProgressLocation, colorProgress);
- }
- glEnable(GL_BLEND);
- drawTexturedQuad(xc, frameDrawY, trimWidth, trimHeight);
- glDisable(GL_BLEND);
- if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
- drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
- }
+ // Draw the current frame's texture on every physical display that is enabled.
+ for (const auto& display : mDisplays) {
+ eglMakeCurrent(mEgl, display.eglSurface, display.eglSurface, mEglContext);
+
+ const double ratioW =
+ static_cast<double>(display.width) / display.initWidth;
+ const double ratioH =
+ static_cast<double>(display.height) / display.initHeight;
+ const int animationX = (display.width - animation.width * ratioW) / 2;
+ const int animationY = (display.height - animation.height * ratioH) / 2;
+
+ const int trimWidth = frame.trimWidth * ratioW;
+ const int trimHeight = frame.trimHeight * ratioH;
+ const int trimX = frame.trimX * ratioW;
+ const int trimY = frame.trimY * ratioH;
+ const int xc = animationX + trimX;
+ const int yc = animationY + trimY;
+ projectSceneToWindow(display);
+ handleViewport(frameDuration, display);
+ glClear(GL_COLOR_BUFFER_BIT);
+ // specify the y center as ceiling((height - frame.trimHeight) / 2)
+ // which is equivalent to height - (yc + frame.trimHeight)
+ const int frameDrawY = display.height - (yc + trimHeight);
+
+ glUseProgram(mImageShader);
+ glUniform1i(mImageTextureLocation, 0);
+ glUniform1f(mImageFadeLocation, fade);
+ if (animation.dynamicColoringEnabled) {
+ glUniform1f(mImageColorProgressLocation, colorProgress);
+ }
+ glEnable(GL_BLEND);
+ drawTexturedQuad(xc, frameDrawY, trimWidth, trimHeight, display);
+ glDisable(GL_BLEND);
- if (displayProgress) {
- int newProgress = android::base::GetIntProperty(PROGRESS_PROP_NAME, 0);
- // In case the new progress jumped suddenly, still show an
- // increment of 1.
- if (lastDisplayedProgress != 100) {
- // Artificially sleep 1/10th a second to slow down the animation.
- usleep(100000);
- if (lastDisplayedProgress < newProgress) {
- lastDisplayedProgress++;
- }
+ if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
+ drawClock(animation.clockFont, part.clockPosX, part.clockPosY, display);
+ }
+
+ if (displayProgress) {
+ int newProgress = android::base::GetIntProperty(PROGRESS_PROP_NAME, 0);
+ // In case the new progress jumped suddenly, still show an
+ // increment of 1.
+ if (lastDisplayedProgress != 100) {
+ // Artificially sleep 1/10th a second to slow down the animation.
+ usleep(100000);
+ if (lastDisplayedProgress < newProgress) {
+ lastDisplayedProgress++;
+ }
+ }
+ // Put the progress percentage right below the animation.
+ int posY = animation.height / 3;
+ int posX = TEXT_CENTER_VALUE;
+ drawProgress(lastDisplayedProgress,
+ animation.progressFont, posX, posY, display);
}
- // Put the progress percentage right below the animation.
- int posY = animation.height / 3;
- int posX = TEXT_CENTER_VALUE;
- drawProgress(lastDisplayedProgress, animation.progressFont, posX, posY);
- }
- handleViewport(frameDuration);
+ eglSwapBuffers(mEgl, display.eglSurface);
+ }
- eglSwapBuffers(mDisplay, mSurface);
nsecs_t now = systemTime();
nsecs_t delay = frameDuration - (now - lastFrame);
@@ -1709,9 +1745,7 @@ bool BootAnimation::playAnimation(const Animation& animation) {
// Free textures created for looping parts now that the animation is done.
for (const Animation::Part& part : animation.parts) {
if (part.count != 1) {
- const size_t fcount = part.frames.size();
- for (size_t j = 0; j < fcount; j++) {
- const Animation::Frame& frame(part.frames[j]);
+ for (const auto& frame : part.frames) {
glDeleteTextures(1, &frame.tid);
}
}
@@ -1730,16 +1764,17 @@ void BootAnimation::processDisplayEvents() {
mLooper->pollOnce(0);
}
-void BootAnimation::handleViewport(nsecs_t timestep) {
+void BootAnimation::handleViewport(nsecs_t timestep, const Display& display) {
ATRACE_CALL();
- if (mShuttingDown || !mFlingerSurfaceControl || mTargetInset == 0) {
+ if (mShuttingDown || !display.surfaceControl || mTargetInset == 0) {
return;
}
if (mTargetInset < 0) {
// Poll the amount for the top display inset. This will return -1 until persistent properties
// have been loaded.
- mTargetInset = android::base::GetIntProperty("persist.sys.displayinset.top",
- -1 /* default */, -1 /* min */, mHeight / 2 /* max */);
+ mTargetInset =
+ android::base::GetIntProperty("persist.sys.displayinset.top", -1 /* default */,
+ -1 /* min */, display.height / 2 /* max */);
}
if (mTargetInset <= 0) {
return;
@@ -1751,19 +1786,27 @@ void BootAnimation::handleViewport(nsecs_t timestep) {
int interpolatedInset = (cosf((fraction + 1) * M_PI) / 2.0f + 0.5f) * mTargetInset;
SurfaceComposerClient::Transaction()
- .setCrop(mFlingerSurfaceControl, Rect(0, interpolatedInset, mWidth, mHeight))
+ .setCrop(display.surfaceControl,
+ Rect(0, interpolatedInset, display.width, display.height))
.apply();
} else {
// At the end of the animation, we switch to the viewport that DisplayManager will apply
// later. This changes the coordinate system, and means we must move the surface up by
// the inset amount.
- Rect layerStackRect(0, 0, mWidth, mHeight - mTargetInset);
- Rect displayRect(0, mTargetInset, mWidth, mHeight);
-
+ Rect layerStackRect(0, 0,
+ display.width,
+ display.height - mTargetInset);
+ Rect displayRect(0, mTargetInset,
+ display.width,
+ display.height);
SurfaceComposerClient::Transaction t;
- t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset)
- .setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight));
- t.setDisplayProjection(mDisplayToken, ui::ROTATION_0, layerStackRect, displayRect);
+ t.setPosition(display.surfaceControl, 0, -mTargetInset)
+ .setCrop(display.surfaceControl,
+ Rect(0, mTargetInset,
+ display.width,
+ display.height));
+ t.setDisplayProjection(display.displayToken,
+ ui::ROTATION_0, layerStackRect, displayRect);
t.apply();
mTargetInset = mCurrentInset = 0;
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 8683b71a3762..0a057461228c 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -31,6 +31,7 @@
#include <binder/IBinder.h>
#include <ui/Rotation.h>
+#include <ui/LayerStack.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
@@ -119,6 +120,18 @@ public:
float endColors[4][3]; // End colors of dynamic color transition.
};
+ // Collects all attributes that must be tracked per physical display.
+ struct Display {
+ int width;
+ int height;
+ int initWidth;
+ int initHeight;
+ EGLDisplay eglSurface;
+ sp<IBinder> displayToken;
+ sp<SurfaceControl> surfaceControl;
+ sp<Surface> surface;
+ };
+
// All callbacks will be called from this class's internal thread.
class Callbacks : public RefBase {
public:
@@ -181,14 +194,18 @@ private:
bool premultiplyAlpha = true);
status_t initFont(Font* font, const char* fallback);
void initShaders();
- bool android();
+ bool android(const Display& display);
+ status_t initDisplaysAndSurfaces();
bool movie();
- void drawText(const char* str, const Font& font, bool bold, int* x, int* y);
- void drawClock(const Font& font, const int xPos, const int yPos);
- void drawProgress(int percent, const Font& font, const int xPos, const int yPos);
+ void drawText(const char* str, const Font& font, bool bold,
+ int* x, int* y, const Display& display);
+ void drawClock(const Font& font, const int xPos, const int yPos, const Display& display);
+ void drawProgress(int percent, const Font& font,
+ const int xPos, const int yPos, const Display& display);
void fadeFrame(int frameLeft, int frameBottom, int frameWidth, int frameHeight,
const Animation::Part& part, int fadedFramesCount);
- void drawTexturedQuad(float xStart, float yStart, float width, float height);
+ void drawTexturedQuad(float xStart, float yStart,
+ float width, float height, const Display& display);
bool validClock(const Animation::Part& part);
Animation* loadAnimation(const String8&);
bool playAnimation(const Animation&);
@@ -200,36 +217,31 @@ private:
bool preloadAnimation();
EGLConfig getEglConfig(const EGLDisplay&);
ui::Size limitSurfaceSize(int width, int height) const;
- void resizeSurface(int newWidth, int newHeight);
- void projectSceneToWindow();
- void rotateAwayFromNaturalOrientationIfNeeded();
+ void resizeSurface(int newWidth, int newHeight, Display& display);
+ void projectSceneToWindow(const Display& display);
+ void rotateAwayFromNaturalOrientationIfNeeded(Display& display);
ui::Rotation parseOrientationProperty();
+ void configureDisplayAndLayerStack(const Display& display, ui::LayerStack layerStack);
bool shouldStopPlayingPart(const Animation::Part& part, int fadedFramesCount,
int lastDisplayedProgress);
void checkExit();
- void handleViewport(nsecs_t timestep);
+ void handleViewport(nsecs_t timestep, const Display& display);
void initDynamicColors();
sp<SurfaceComposerClient> mSession;
AssetManager mAssets;
Texture mAndroid[2];
- int mWidth;
- int mHeight;
- int mInitWidth;
- int mInitHeight;
int mMaxWidth = 0;
int mMaxHeight = 0;
int mCurrentInset;
int mTargetInset;
bool mUseNpotTextures = false;
- EGLDisplay mDisplay;
- EGLDisplay mContext;
- EGLDisplay mSurface;
- sp<IBinder> mDisplayToken;
- sp<SurfaceControl> mFlingerSurfaceControl;
- sp<Surface> mFlingerSurface;
+ EGLDisplay mEgl;
+ EGLDisplay mEglContext;
+ // Per-Display Attributes (to support multi-display)
+ std::vector<Display> mDisplays;
bool mClockEnabled;
bool mTimeIsAccurate;
bool mTimeFormat12Hour;
diff --git a/cmds/bootanimation/bootanimation_flags.aconfig b/cmds/bootanimation/bootanimation_flags.aconfig
new file mode 100644
index 000000000000..04837b98e414
--- /dev/null
+++ b/cmds/bootanimation/bootanimation_flags.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.graphics.bootanimation.flags"
+container: "system"
+
+flag {
+ name: "multidisplay"
+ namespace: "bootanimation"
+ description: "Enable boot animation on multiple displays (e.g. foldables)"
+ bug: "335406617"
+ is_fixed_read_only: true
+}
+
+
diff --git a/core/api/current.txt b/core/api/current.txt
index 44b3c6232d59..ec6a20c49629 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -22601,6 +22601,7 @@ package android.media {
method public void sendEvent(int, int, @Nullable byte[]) throws android.media.MediaCasException;
method public void setEventListener(@Nullable android.media.MediaCas.EventListener, @Nullable android.os.Handler);
method public void setPrivateData(@NonNull byte[]) throws android.media.MediaCasException;
+ method @FlaggedApi("com.android.media.flags.update_client_profile_priority") public boolean updateResourcePriority(int, int);
field public static final int PLUGIN_STATUS_PHYSICAL_MODULE_CHANGED = 0; // 0x0
field public static final int PLUGIN_STATUS_SESSION_NUMBER_CHANGED = 1; // 0x1
field public static final int SCRAMBLING_MODE_AES128 = 9; // 0x9
@@ -49240,6 +49241,7 @@ package android.text.style {
field public static final String ARG_PROTOCOL = "android.arg.protocol";
field public static final String ARG_QUANTITY = "android.arg.quantity";
field public static final String ARG_QUERY_STRING = "android.arg.query_string";
+ field @FlaggedApi("com.android.text.flags.tts_span_duration") public static final String ARG_SECONDS = "android.arg.seconds";
field public static final String ARG_TEXT = "android.arg.text";
field public static final String ARG_UNIT = "android.arg.unit";
field public static final String ARG_USERNAME = "android.arg.username";
@@ -49276,6 +49278,7 @@ package android.text.style {
field public static final String TYPE_DATE = "android.type.date";
field public static final String TYPE_DECIMAL = "android.type.decimal";
field public static final String TYPE_DIGITS = "android.type.digits";
+ field @FlaggedApi("com.android.text.flags.tts_span_duration") public static final String TYPE_DURATION = "android.type.duration";
field public static final String TYPE_ELECTRONIC = "android.type.electronic";
field public static final String TYPE_FRACTION = "android.type.fraction";
field public static final String TYPE_MEASURE = "android.type.measure";
@@ -49335,6 +49338,13 @@ package android.text.style {
method public android.text.style.TtsSpan.DigitsBuilder setDigits(String);
}
+ @FlaggedApi("com.android.text.flags.tts_span_duration") public static class TtsSpan.DurationBuilder extends android.text.style.TtsSpan.SemioticClassBuilder<android.text.style.TtsSpan.DurationBuilder> {
+ ctor @FlaggedApi("com.android.text.flags.tts_span_duration") public TtsSpan.DurationBuilder();
+ method @FlaggedApi("com.android.text.flags.tts_span_duration") @NonNull public android.text.style.TtsSpan.DurationBuilder setHours(int);
+ method @FlaggedApi("com.android.text.flags.tts_span_duration") @NonNull public android.text.style.TtsSpan.DurationBuilder setMinutes(int);
+ method @FlaggedApi("com.android.text.flags.tts_span_duration") @NonNull public android.text.style.TtsSpan.DurationBuilder setSeconds(int);
+ }
+
public static class TtsSpan.ElectronicBuilder extends android.text.style.TtsSpan.SemioticClassBuilder<android.text.style.TtsSpan.ElectronicBuilder> {
ctor public TtsSpan.ElectronicBuilder();
method public android.text.style.TtsSpan.ElectronicBuilder setDomain(String);
@@ -49417,6 +49427,7 @@ package android.text.style {
ctor public TtsSpan.TimeBuilder(int, int);
method public android.text.style.TtsSpan.TimeBuilder setHours(int);
method public android.text.style.TtsSpan.TimeBuilder setMinutes(int);
+ method @FlaggedApi("com.android.text.flags.tts_span_duration") @NonNull public android.text.style.TtsSpan.TimeBuilder setSeconds(int);
}
public static class TtsSpan.VerbatimBuilder extends android.text.style.TtsSpan.SemioticClassBuilder<android.text.style.TtsSpan.VerbatimBuilder> {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index c1c96eaa098d..014e4660f944 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -285,6 +285,14 @@ public class WallpaperManager {
public static final String COMMAND_UNFREEZE = "android.wallpaper.unfreeze";
/**
+ * Command for {@link #sendWallpaperCommand}: in sendWallpaperCommand put extra to this command
+ * to give the bounds of space between the bottom of notifications and the top of shortcuts
+ * @hide
+ */
+ public static final String COMMAND_LOCKSCREEN_LAYOUT_CHANGED =
+ "android.wallpaper.lockscreen_layout_changed";
+
+ /**
* Extra passed back from setWallpaper() giving the new wallpaper's assigned ID.
* @hide
*/
diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
index b68bafe279bf..534f46172fc1 100644
--- a/core/java/android/app/wearable/flags.aconfig
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -38,4 +38,12 @@ flag {
namespace: "machine_learning"
description: "This flag enables the APIs related to hotword in WearableSensingManager and WearableSensingService."
bug: "310055381"
+}
+
+flag {
+ name: "enable_concurrent_wearable_connections"
+ is_exported: true
+ namespace: "machine_learning"
+ description: "This flag enables the APIs for providing multiple concurrent connections to the WearableSensingService."
+ bug: "358133158"
} \ No newline at end of file
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index dfc591b322df..8ef62e3d4628 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -62,7 +62,7 @@ import java.time.ZoneOffset;
* sleep (CPU off, display dark, device waiting for external input),
* but is not affected by clock scaling, idle, or other power saving
* mechanisms. This is the basis for most interval timing
- * such as {@link Thread#sleep(long) Thread.sleep(millls)},
+ * such as {@link Thread#sleep(long) Thread.sleep(millis)},
* {@link Object#wait(long) Object.wait(millis)}, and
* {@link System#nanoTime System.nanoTime()}. This clock is guaranteed
* to be monotonic, and is suitable for interval timing when the
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index ec5b488ccbbd..09b2201efb4e 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -188,3 +188,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "tts_span_duration"
+ namespace: "text"
+ description: "Feature flag for adding a TYPE_DURATION to TtsSpan"
+ bug: "337103893"
+}
diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java
index f9a1a0d36d30..b7b8f0b1b5d8 100644
--- a/core/java/android/text/style/TtsSpan.java
+++ b/core/java/android/text/style/TtsSpan.java
@@ -16,6 +16,10 @@
package android.text.style;
+import static com.android.text.flags.Flags.FLAG_TTS_SPAN_DURATION;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.text.ParcelableSpan;
@@ -112,6 +116,16 @@ public class TtsSpan implements ParcelableSpan {
public static final String TYPE_TIME = "android.type.time";
/**
+ * The text associated with this span is a duration, consisting of a number of
+ * hours, minutes, and seconds specified with {@link #ARG_HOURS},
+ * {@link #ARG_MINUTES}, and {@link #ARG_SECONDS}. This is different from {@link #TYPE_TIME}.
+ * This should be used to convey an interval of time, while {@link #TYPE_TIME} should be used to
+ * convey a particular moment in time, such as a clock time.
+ */
+ @FlaggedApi(FLAG_TTS_SPAN_DURATION)
+ public static final String TYPE_DURATION = "android.type.duration";
+
+ /**
* The text associated with this span is a date. At least one of the
* arguments {@link #ARG_MONTH} and {@link #ARG_YEAR} has to be provided.
* The argument {@link #ARG_DAY} is optional if {@link #ARG_MONTH} is set.
@@ -302,13 +316,21 @@ public class TtsSpan implements ParcelableSpan {
public static final String ARG_HOURS = "android.arg.hours";
/**
- * Argument used to specify the minutes of a time. The hours should be
+ * Argument used to specify the minutes of a time. The minutes should be
* provided as an integer in the range from 0 up to and including 59.
* Can be used with {@link #TYPE_TIME}.
*/
public static final String ARG_MINUTES = "android.arg.minutes";
/**
+ * Argument used to specify the seconds of a time or duration. The seconds should be
+ * provided as an integer in the range from 0 up to and including 59.
+ * Can be used with {@link #TYPE_TIME} or {@link #TYPE_DURATION}.
+ */
+ @FlaggedApi(FLAG_TTS_SPAN_DURATION)
+ public static final String ARG_SECONDS = "android.arg.seconds";
+
+ /**
* Argument used to specify the weekday of a date. The value should be
* provided as an integer and can be any of {@link #WEEKDAY_SUNDAY},
* {@link #WEEKDAY_MONDAY}, {@link #WEEKDAY_TUESDAY},
@@ -1132,9 +1154,70 @@ public class TtsSpan implements ParcelableSpan {
public TimeBuilder setMinutes(int minutes) {
return setIntArgument(TtsSpan.ARG_MINUTES, minutes);
}
+
+ /**
+ * Sets the {@link #ARG_SECONDS} argument.
+ * @param seconds The value to be set for seconds.
+ * @return This instance.
+ */
+ @FlaggedApi(FLAG_TTS_SPAN_DURATION)
+ @NonNull
+ public TimeBuilder setSeconds(int seconds) {
+ return setIntArgument(TtsSpan.ARG_SECONDS, seconds);
+ }
}
/**
+ * A builder for TtsSpans of type {@link #TYPE_DURATION}.
+ */
+ @FlaggedApi(FLAG_TTS_SPAN_DURATION)
+ public static class DurationBuilder
+ extends SemioticClassBuilder<DurationBuilder> {
+
+ /**
+ * Creates a builder for a TtsSpan of type {@link #TYPE_DURATION}.
+ */
+ @FlaggedApi(FLAG_TTS_SPAN_DURATION)
+ public DurationBuilder() {
+ super(TtsSpan.TYPE_DURATION);
+ }
+
+ /**
+ * Sets the {@link #ARG_HOURS} argument.
+ * @param hours The value to be set for hours.
+ * @return This instance.
+ */
+ @FlaggedApi(FLAG_TTS_SPAN_DURATION)
+ @NonNull
+ public DurationBuilder setHours(int hours) {
+ return setIntArgument(TtsSpan.ARG_HOURS, hours);
+ }
+
+ /**
+ * Sets the {@link #ARG_MINUTES} argument.
+ * @param minutes The value to be set for minutes.
+ * @return This instance.
+ */
+ @FlaggedApi(FLAG_TTS_SPAN_DURATION)
+ @NonNull
+ public DurationBuilder setMinutes(int minutes) {
+ return setIntArgument(TtsSpan.ARG_MINUTES, minutes);
+ }
+
+ /**
+ * Sets the {@link #ARG_SECONDS} argument.
+ * @param seconds The value to be set for seconds.
+ * @return This instance.
+ */
+ @FlaggedApi(FLAG_TTS_SPAN_DURATION)
+ @NonNull
+ public DurationBuilder setSeconds(int seconds) {
+ return setIntArgument(TtsSpan.ARG_SECONDS, seconds);
+ }
+ }
+
+
+ /**
* A builder for TtsSpans of type {@link #TYPE_DATE}.
*/
public static class DateBuilder
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 5c415165137e..67050e01b591 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -38,7 +38,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
+import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0ca442d66e6f..5afc7b2c5cef 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -125,7 +125,7 @@ import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
-import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
+import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 06820cd4c2ce..d7b5211ad9fd 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -46,6 +46,7 @@ import android.app.LoadedApk;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.appwidget.AppWidgetHostView;
+import android.appwidget.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -4119,6 +4120,71 @@ public class RemoteViews implements Parcelable, Filter {
public void visitUris(@NonNull Consumer<Uri> visitor) {
mNestedViews.visitUris(visitor);
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ if (!Flags.remoteViewsProto()) return;
+ final long token = out.start(RemoteViewsProto.Action.VIEW_GROUP_ADD_ACTION);
+ out.write(RemoteViewsProto.ViewGroupAddAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.ViewGroupAddAction.INDEX, mIndex);
+ out.write(RemoteViewsProto.ViewGroupAddAction.STABLE_ID, mStableId);
+ long rvToken = out.start(RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS);
+ mNestedViews.writePreviewToProto(context, out);
+ out.end(rvToken);
+ out.end(token);
+ }
+ }
+
+ private PendingResources<Action> createViewGroupActionAddFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.VIEW_GROUP_ADD_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.ViewGroupAddAction.VIEW_ID:
+ values.put(RemoteViewsProto.ViewGroupAddAction.VIEW_ID,
+ in.readString(RemoteViewsProto.ViewGroupAddAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS:
+ final long nvToken = in.start(RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS);
+ values.put(RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS,
+ createFromProto(in));
+ in.end(nvToken);
+ break;
+ case (int) RemoteViewsProto.ViewGroupAddAction.INDEX:
+ values.put(RemoteViewsProto.ViewGroupAddAction.INDEX,
+ in.readInt(RemoteViewsProto.ViewGroupAddAction.INDEX));
+ break;
+ case (int) RemoteViewsProto.ViewGroupAddAction.STABLE_ID:
+ values.put(RemoteViewsProto.ViewGroupAddAction.STABLE_ID,
+ in.readInt(RemoteViewsProto.ViewGroupAddAction.STABLE_ID));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.ViewGroupAddAction.VIEW_ID,
+ RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.ViewGroupAddAction.VIEW_ID);
+ return new ViewGroupActionAdd(viewId, ((PendingResources<RemoteViews>) values.get(
+ RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS)).create(context, resources,
+ rootData, depth),
+ (int) values.get(RemoteViewsProto.ViewGroupAddAction.INDEX, 0),
+ (int) values.get(RemoteViewsProto.ViewGroupAddAction.STABLE_ID, 0));
+ };
}
/**
@@ -4241,6 +4307,60 @@ public class RemoteViews implements Parcelable, Filter {
public int mergeBehavior() {
return MERGE_APPEND;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.VIEW_GROUP_REMOVE_ACTION);
+ out.write(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ if (mViewIdToKeep != REMOVE_ALL_VIEWS_ID) {
+ out.write(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP,
+ appResources.getResourceName(mViewIdToKeep));
+ }
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.VIEW_GROUP_REMOVE_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID:
+ values.put(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID,
+ in.readString(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP:
+ values.put(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP,
+ in.readString(
+ RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID);
+ int viewIdToKeep = (values.indexOfKey(
+ RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP) >= 0)
+ ? getAsIdentifier(resources, values,
+ RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP)
+ : REMOVE_ALL_VIEWS_ID;
+ return new ViewGroupActionRemove(viewId, viewIdToKeep);
+ };
+ }
}
/**
@@ -4497,6 +4617,200 @@ public class RemoteViews implements Parcelable, Filter {
visitIconUri(mI4, visitor);
}
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context,
+ Resources appResources) { // rebase
+ final long token = out.start(RemoteViewsProto.Action.TEXT_VIEW_DRAWABLE_ACTION);
+ out.write(RemoteViewsProto.TextViewDrawableAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE, mIsRelative);
+ if (mUseIcons) {
+ long iconsToken = out.start(RemoteViewsProto.TextViewDrawableAction.ICONS);
+ if (mI1 != null) {
+ writeIconToProto(out, appResources, mI1,
+ RemoteViewsProto.TextViewDrawableAction.Icons.ONE);
+ }
+ if (mI2 != null) {
+ writeIconToProto(out, appResources, mI2,
+ RemoteViewsProto.TextViewDrawableAction.Icons.TWO);
+ }
+ if (mI3 != null) {
+ writeIconToProto(out, appResources, mI3,
+ RemoteViewsProto.TextViewDrawableAction.Icons.THREE);
+ }
+ if (mI4 != null) {
+ writeIconToProto(out, appResources, mI4,
+ RemoteViewsProto.TextViewDrawableAction.Icons.FOUR);
+ }
+ out.end(iconsToken);
+ } else {
+ long resourcesToken = out.start(RemoteViewsProto.TextViewDrawableAction.RESOURCES);
+ if (mD1 != 0) {
+ out.write(RemoteViewsProto.TextViewDrawableAction.Resources.ONE,
+ appResources.getResourceName(mD1));
+ }
+ if (mD2 != 0) {
+ out.write(RemoteViewsProto.TextViewDrawableAction.Resources.TWO,
+ appResources.getResourceName(mD2));
+ }
+ if (mD3 != 0) {
+ out.write(RemoteViewsProto.TextViewDrawableAction.Resources.THREE,
+ appResources.getResourceName(mD3));
+ }
+ if (mD4 != 0) {
+ out.write(RemoteViewsProto.TextViewDrawableAction.Resources.FOUR,
+ appResources.getResourceName(mD4));
+ }
+ out.end(resourcesToken);
+ }
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ values.put(RemoteViewsProto.TextViewDrawableAction.ICONS,
+ new SparseArray<PendingResources<Icon>>());
+ values.put(RemoteViewsProto.TextViewDrawableAction.RESOURCES,
+ new SparseArray<String>());
+ final long token = in.start(RemoteViewsProto.Action.TEXT_VIEW_DRAWABLE_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.TextViewDrawableAction.VIEW_ID:
+ values.put(RemoteViewsProto.TextViewDrawableAction.VIEW_ID,
+ in.readString(RemoteViewsProto.TextViewDrawableAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE:
+ values.put(RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE,
+ in.readBoolean(
+ RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.RESOURCES:
+ final long resourcesToken = in.start(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.TextViewDrawableAction.Resources.ONE:
+ ((SparseArray<String>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put(
+ 1, in.readString(
+ RemoteViewsProto
+ .TextViewDrawableAction.Resources.ONE));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Resources.TWO:
+ ((SparseArray<String>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put(
+ 2, in.readString(
+ RemoteViewsProto
+ .TextViewDrawableAction.Resources.TWO));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Resources.THREE:
+ ((SparseArray<String>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put(
+ 3, in.readString(
+ RemoteViewsProto
+ .TextViewDrawableAction
+ .Resources.THREE));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Resources.FOUR:
+ ((SparseArray<String>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put(
+ 4, in.readString(
+ RemoteViewsProto
+ .TextViewDrawableAction
+ .Resources.FOUR));
+ break;
+ default:
+ Log.w(LOG_TAG,
+ "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(resourcesToken);
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.ICONS:
+ final long iconsToken = in.start(
+ RemoteViewsProto.TextViewDrawableAction.ICONS);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.TextViewDrawableAction.Icons.ONE:
+ ((SparseArray<PendingResources<Icon>>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.ICONS)).put(1,
+ createIconFromProto(in,
+ RemoteViewsProto
+ .TextViewDrawableAction.Icons.ONE));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Icons.TWO:
+ ((SparseArray<PendingResources<Icon>>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.ICONS)).put(2,
+ createIconFromProto(in,
+ RemoteViewsProto
+ .TextViewDrawableAction.Icons.TWO));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Icons.THREE:
+ ((SparseArray<PendingResources<Icon>>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.ICONS)).put(3,
+ createIconFromProto(in,
+ RemoteViewsProto
+ .TextViewDrawableAction.Icons.THREE));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Icons.FOUR:
+ ((SparseArray<PendingResources<Icon>>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.ICONS)).put(4,
+ createIconFromProto(in,
+ RemoteViewsProto
+ .TextViewDrawableAction.Icons.FOUR));
+ break;
+ default:
+ Log.w(LOG_TAG,
+ "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(iconsToken);
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.TextViewDrawableAction.VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.TextViewDrawableAction.VIEW_ID);
+ SparseArray<PendingResources<Icon>> icons =
+ (SparseArray<PendingResources<Icon>>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.ICONS);
+ SparseArray<String> resArray = (SparseArray<String>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES);
+ boolean isRelative = (boolean) values.get(
+ RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE, false);
+ if (icons.size() > 0) {
+ return new TextViewDrawableAction(viewId, isRelative,
+ icons.get(1).create(context, resources, rootData, depth),
+ icons.get(2).create(context, resources, rootData, depth),
+ icons.get(3).create(context, resources, rootData, depth),
+ icons.get(4).create(context, resources, rootData, depth));
+ } else {
+ int first = resArray.contains(1) ? getAsIdentifier(resources, resArray, 1) : 0;
+ int second = resArray.contains(2) ? getAsIdentifier(resources, resArray, 2) : 0;
+ int third = resArray.contains(3) ? getAsIdentifier(resources, resArray, 3) : 0;
+ int fourth = resArray.contains(4) ? getAsIdentifier(resources, resArray, 4) : 0;
+ return new TextViewDrawableAction(viewId, isRelative, first, second, third,
+ fourth);
+ }
+ };
+ }
}
/**
@@ -4535,6 +4849,58 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return TEXT_VIEW_SIZE_ACTION_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.TEXT_VIEW_SIZE_ACTION);
+ out.write(RemoteViewsProto.TextViewSizeAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.TextViewSizeAction.UNITS, mUnits);
+ out.write(RemoteViewsProto.TextViewSizeAction.SIZE, mSize);
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.TEXT_VIEW_SIZE_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.TextViewSizeAction.VIEW_ID:
+ values.put(RemoteViewsProto.TextViewSizeAction.VIEW_ID,
+ in.readString(RemoteViewsProto.TextViewSizeAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.TextViewSizeAction.UNITS:
+ values.put(RemoteViewsProto.TextViewSizeAction.UNITS,
+ in.readInt(RemoteViewsProto.TextViewSizeAction.UNITS));
+ break;
+ case (int) RemoteViewsProto.TextViewSizeAction.SIZE:
+ values.put(RemoteViewsProto.TextViewSizeAction.SIZE,
+ in.readFloat(RemoteViewsProto.TextViewSizeAction.SIZE));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.TextViewSizeAction.VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.TextViewSizeAction.VIEW_ID);
+ return new TextViewSizeAction(viewId,
+ (int) values.get(RemoteViewsProto.TextViewSizeAction.UNITS, 0),
+ (float) values.get(RemoteViewsProto.TextViewSizeAction.SIZE, 0));
+ };
+ }
}
/**
@@ -4579,6 +4945,70 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return VIEW_PADDING_ACTION_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.VIEW_PADDING_ACTION);
+ out.write(RemoteViewsProto.ViewPaddingAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.ViewPaddingAction.LEFT, mLeft);
+ out.write(RemoteViewsProto.ViewPaddingAction.RIGHT, mRight);
+ out.write(RemoteViewsProto.ViewPaddingAction.TOP, mTop);
+ out.write(RemoteViewsProto.ViewPaddingAction.BOTTOM, mBottom);
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.VIEW_PADDING_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.ViewPaddingAction.VIEW_ID:
+ values.put(RemoteViewsProto.ViewPaddingAction.VIEW_ID,
+ in.readString(RemoteViewsProto.ViewPaddingAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.ViewPaddingAction.LEFT:
+ values.put(RemoteViewsProto.ViewPaddingAction.LEFT,
+ in.readInt(RemoteViewsProto.ViewPaddingAction.LEFT));
+ break;
+ case (int) RemoteViewsProto.ViewPaddingAction.RIGHT:
+ values.put(RemoteViewsProto.ViewPaddingAction.RIGHT,
+ in.readInt(RemoteViewsProto.ViewPaddingAction.RIGHT));
+ break;
+ case (int) RemoteViewsProto.ViewPaddingAction.TOP:
+ values.put(RemoteViewsProto.ViewPaddingAction.TOP,
+ in.readInt(RemoteViewsProto.ViewPaddingAction.TOP));
+ break;
+ case (int) RemoteViewsProto.ViewPaddingAction.BOTTOM:
+ values.put(RemoteViewsProto.ViewPaddingAction.BOTTOM,
+ in.readInt(RemoteViewsProto.ViewPaddingAction.BOTTOM));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.ViewPaddingAction.VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.ViewPaddingAction.VIEW_ID);
+ return new ViewPaddingAction(viewId,
+ (int) values.get(RemoteViewsProto.ViewPaddingAction.LEFT, 0),
+ (int) values.get(RemoteViewsProto.ViewPaddingAction.TOP, 0),
+ (int) values.get(RemoteViewsProto.ViewPaddingAction.RIGHT, 0),
+ (int) values.get(RemoteViewsProto.ViewPaddingAction.BOTTOM, 0));
+ };
+ }
}
/**
@@ -5241,6 +5671,69 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return SET_VIEW_OUTLINE_RADIUS_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(
+ RemoteViewsProto.Action.SET_VIEW_OUTLINE_PREFERRED_RADIUS_ACTION);
+ out.write(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE, mValueType);
+ out.write(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE, mValue);
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(
+ RemoteViewsProto.Action.SET_VIEW_OUTLINE_PREFERRED_RADIUS_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID:
+ values.put(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID,
+ in.readString(
+ RemoteViewsProto
+ .SetViewOutlinePreferredRadiusAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE:
+ values.put(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE,
+ in.readInt(
+ RemoteViewsProto
+ .SetViewOutlinePreferredRadiusAction.VALUE_TYPE));
+ break;
+ case (int) RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE:
+ values.put(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE,
+ in.readInt(
+ RemoteViewsProto
+ .SetViewOutlinePreferredRadiusAction.VALUE));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values,
+ new long[]{RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID,
+ RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID);
+ return new SetViewOutlinePreferredRadiusAction(viewId,
+ (int) values.get(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE,
+ 0), (int) values.get(
+ RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE));
+ };
+ }
}
/**
@@ -5324,6 +5817,46 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return SET_DRAW_INSTRUCTION_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return drawDataParcel();
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ if (!drawDataParcel()) return;
+ final long token = out.start(RemoteViewsProto.Action.SET_DRAW_INSTRUCTION_ACTION);
+ if (mInstructions != null) {
+ for (byte[] bytes : mInstructions.mInstructions) {
+ out.write(RemoteViewsProto.SetDrawInstructionAction.INSTRUCTIONS, bytes);
+ }
+ }
+ out.end(token);
+ }
+ }
+
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ private PendingResources<Action> createSetDrawInstructionActionFromProto(ProtoInputStream in)
+ throws Exception {
+ List<byte[]> instructions = new ArrayList<byte[]>();
+
+ final long token = in.start(RemoteViewsProto.Action.SET_DRAW_INSTRUCTION_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.SetDrawInstructionAction.INSTRUCTIONS:
+ instructions.add(
+ in.readBytes(RemoteViewsProto.SetDrawInstructionAction.INSTRUCTIONS));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ return (context, resources, rootData, depth) -> new SetDrawInstructionAction(
+ new DrawInstructions.Builder(instructions).build());
}
/**
@@ -9604,12 +10137,20 @@ public class RemoteViews implements Parcelable, Filter {
}
if (ref.mMode == MODE_NORMAL) {
rv.setIdealSize(ref.mIdealSize);
+ boolean hasDrawInstructionAction = false;
for (PendingResources<Action> pendingAction : ref.mActions) {
Action action = pendingAction.create(appContext, appResources, rootData, depth);
if (action != null) {
+ if (action instanceof SetDrawInstructionAction) {
+ hasDrawInstructionAction = true;
+ }
rv.addAction(action);
}
}
+ if (rv.mHasDrawInstructions && !hasDrawInstructionAction) {
+ throw new InvalidProtoException(
+ "RemoteViews proto is missing DrawInstructions");
+ }
return rv;
} else if (ref.mMode == MODE_HAS_SIZED_REMOTEVIEWS) {
List<RemoteViews> sizedViews = new ArrayList<>();
@@ -9685,6 +10226,23 @@ public class RemoteViews implements Parcelable, Filter {
return rv.createSetRemoteCollectionItemListAdapterActionFromProto(in);
case (int) RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION:
return SetRippleDrawableColor.createFromProto(in);
+ case (int) RemoteViewsProto.Action.SET_VIEW_OUTLINE_PREFERRED_RADIUS_ACTION:
+ return SetViewOutlinePreferredRadiusAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.TEXT_VIEW_DRAWABLE_ACTION:
+ return TextViewDrawableAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.TEXT_VIEW_SIZE_ACTION:
+ return TextViewSizeAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.VIEW_GROUP_ADD_ACTION:
+ return rv.createViewGroupActionAddFromProto(in);
+ case (int) RemoteViewsProto.Action.VIEW_GROUP_REMOVE_ACTION:
+ return ViewGroupActionRemove.createFromProto(in);
+ case (int) RemoteViewsProto.Action.VIEW_PADDING_ACTION:
+ return ViewPaddingAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.SET_DRAW_INSTRUCTION_ACTION:
+ if (!drawDataParcel()) {
+ return null;
+ }
+ return rv.createSetDrawInstructionActionFromProto(in);
default:
throw new RuntimeException("Unhandled field while reading Action proto!\n"
+ ProtoUtils.currentFieldToString(in));
diff --git a/core/java/android/window/flags/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 47af50dac930..6fb82af2b292 100644
--- a/core/java/android/window/flags/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.window.flags;
+package android.window;
import android.annotation.Nullable;
import android.app.ActivityThread;
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 6faea17f24b2..bd746d5ecf04 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -38,8 +38,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATIO
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.flags.Flags.customizableWindowHeaders;
-import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
-import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
+import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
+import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL;
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index f477d32cd915..6a987a475711 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -32,6 +32,8 @@ import "frameworks/base/core/proto/android/content/res/color_state_list.proto";
*
* Do not change the tag number or type of any fields in order to maintain compatibility with
* previous versions. If a field is deleted, use `reserved` to mark its tag number.
+ *
+ * Next tag: 17
*/
message RemoteViewsProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -290,6 +292,7 @@ message RemoteViewsProto {
}
}
+ // Next tag: 23
message Action {
oneof action {
AttributeReflectionAction attribute_reflection_action = 1;
@@ -307,6 +310,13 @@ message RemoteViewsProto {
SetRadioGroupCheckedAction set_radio_group_checked_action = 13;
SetRemoteCollectionItemListAdapterAction set_remote_collection_item_list_adapter_action = 14;
SetRippleDrawableColorAction set_ripple_drawable_color_action = 15;
+ SetViewOutlinePreferredRadiusAction set_view_outline_preferred_radius_action = 16;
+ TextViewDrawableAction text_view_drawable_action = 17;
+ TextViewSizeAction text_view_size_action = 18;
+ ViewGroupAddAction view_group_add_action = 19;
+ ViewGroupRemoveAction view_group_remove_action = 20;
+ ViewPaddingAction view_padding_action = 21;
+ SetDrawInstructionAction set_draw_instruction_action = 22;
}
}
@@ -428,6 +438,65 @@ message RemoteViewsProto {
optional string view_id = 1;
optional android.content.res.ColorStateListProto color_state_list = 2;
}
+
+ message SetViewOutlinePreferredRadiusAction {
+ optional string view_id = 1;
+ optional int32 value_type = 2;
+ optional int32 value = 3;
+ }
+
+ message TextViewDrawableAction {
+ optional string view_id = 1;
+ optional bool is_relative = 2;
+ oneof drawables {
+ Resources resources = 3;
+ Icons icons = 4;
+ };
+
+ message Resources {
+ optional string one = 1;
+ optional string two = 2;
+ optional string three = 3;
+ optional string four = 4;
+ }
+
+ message Icons {
+ optional Icon one = 1;
+ optional Icon two = 2;
+ optional Icon three = 3;
+ optional Icon four = 4;
+ }
+ }
+
+ message TextViewSizeAction {
+ optional string view_id = 1;
+ optional int32 units = 2;
+ optional float size = 3;
+ }
+
+ message ViewGroupAddAction {
+ optional string view_id = 1;
+ optional RemoteViewsProto nested_views = 2;
+ optional int32 index = 3;
+ optional int32 stableId = 4;
+ }
+
+ message ViewGroupRemoveAction {
+ optional string view_id = 1;
+ optional string view_id_to_keep = 2;
+ }
+
+ message ViewPaddingAction {
+ optional string view_id = 1;
+ optional int32 left = 2;
+ optional int32 right = 3;
+ optional int32 top = 4;
+ optional int32 bottom = 5;
+ }
+
+ message SetDrawInstructionAction {
+ repeated bytes instructions = 1;
+ }
}
diff --git a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java b/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java
index a311d0d81010..b28e2b04b342 100644
--- a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
+++ b/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package android.window.flags;
+package android.window;
-import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE;
-import static android.window.flags.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF;
-import static android.window.flags.DesktopModeFlags.ToggleOverride.OVERRIDE_ON;
-import static android.window.flags.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET;
-import static android.window.flags.DesktopModeFlags.ToggleOverride.fromSetting;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE;
+import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF;
+import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_ON;
+import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET;
+import static android.window.DesktopModeFlags.ToggleOverride.fromSetting;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS;
@@ -40,6 +41,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -48,7 +50,7 @@ import org.junit.runner.RunWith;
import java.lang.reflect.Field;
/**
- * Test class for {@link DesktopModeFlags}
+ * Test class for {@link android.window.DesktopModeFlags}
*
* Build/Install/Run:
* atest FrameworksCoreTests:DesktopModeFlagsTest
@@ -68,8 +70,12 @@ public class DesktopModeFlagsTest {
private static final int OVERRIDE_UNSET_SETTING = -1;
@Before
- public void setUp() throws Exception {
+ public void setUp() {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
+
+ @After
+ public void tearDown() throws Exception {
resetCache();
}
@@ -203,7 +209,7 @@ public class DesktopModeFlagsTest {
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
}
@Test
@@ -212,7 +218,7 @@ public class DesktopModeFlagsTest {
public void isTrue_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() {
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
}
@Test
@@ -225,7 +231,7 @@ public class DesktopModeFlagsTest {
setOverride(OVERRIDE_ON_SETTING);
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
}
@Test
@@ -235,7 +241,7 @@ public class DesktopModeFlagsTest {
setOverride(OVERRIDE_ON_SETTING);
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
}
@Test
@@ -248,7 +254,7 @@ public class DesktopModeFlagsTest {
setOverride(OVERRIDE_OFF_SETTING);
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
}
@Test
@@ -258,7 +264,7 @@ public class DesktopModeFlagsTest {
setOverride(OVERRIDE_OFF_SETTING);
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
}
@Test
@@ -271,7 +277,7 @@ public class DesktopModeFlagsTest {
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
}
@Test
@@ -284,7 +290,7 @@ public class DesktopModeFlagsTest {
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
}
@Test
@@ -297,7 +303,7 @@ public class DesktopModeFlagsTest {
setOverride(OVERRIDE_ON_SETTING);
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
}
@Test
@@ -310,7 +316,7 @@ public class DesktopModeFlagsTest {
setOverride(OVERRIDE_ON_SETTING);
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
}
@Test
@@ -323,7 +329,7 @@ public class DesktopModeFlagsTest {
setOverride(OVERRIDE_OFF_SETTING);
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
}
@Test
@@ -336,7 +342,7 @@ public class DesktopModeFlagsTest {
setOverride(OVERRIDE_OFF_SETTING);
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
}
@Test
@@ -375,5 +381,6 @@ public class DesktopModeFlagsTest {
"sCachedToggleOverride");
cachedToggleOverride.setAccessible(true);
cachedToggleOverride.set(null, null);
+ setOverride(OVERRIDE_UNSET_SETTING);
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 647a555ad169..0150bcdbd412 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -19,7 +19,7 @@ package com.android.wm.shell.shared.desktopmode;
import android.annotation.NonNull;
import android.content.Context;
import android.os.SystemProperties;
-import android.window.flags.DesktopModeFlags;
+import android.window.DesktopModeFlags;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 4d15605c756a..2128cbc144b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -27,7 +27,7 @@ import android.graphics.Rect;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
-import android.window.flags.DesktopModeFlags;
+import android.window.DesktopModeFlags;
import com.android.internal.annotations.VisibleForTesting;
import com.android.window.flags.Flags;
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 79c31e01e365..d457a0f32f30 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
@@ -16,8 +16,8 @@
package com.android.wm.shell.dagger;
-import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS;
-import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
import android.annotation.Nullable;
import android.app.KeyguardManager;
@@ -71,10 +71,10 @@ import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver;
-import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
@@ -92,9 +92,9 @@ import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
-import com.android.wm.shell.freeform.TaskChangeListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarterInitializer;
+import com.android.wm.shell.freeform.TaskChangeListener;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipTransitionController;
@@ -134,14 +134,14 @@ import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
-import kotlinx.coroutines.CoroutineScope;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
-import kotlinx.coroutines.MainCoroutineDispatcher;
-
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import kotlinx.coroutines.CoroutineScope;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+import kotlinx.coroutines.MainCoroutineDispatcher;
+
/**
* Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
* from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index fa8b6e6427a7..75c795b70378 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -48,14 +48,14 @@ import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.DesktopModeFlags
+import android.window.DesktopModeFlags.DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE
+import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+import android.window.DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
import android.window.RemoteTransition
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
-import android.window.flags.DesktopModeFlags
-import android.window.flags.DesktopModeFlags.DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE
-import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
-import android.window.flags.DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
import androidx.annotation.BinderThread
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 37bec21730a6..d6b721253abf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -24,7 +24,7 @@ import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
-import android.window.flags.DesktopModeFlags
+import android.window.DesktopModeFlags
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
import com.android.internal.jank.InteractionJankMonitor
@@ -39,7 +39,7 @@ import com.android.wm.shell.transition.Transitions.TransitionObserver
* Limits the number of tasks shown in Desktop Mode.
*
* This class should only be used if
- * [android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT]
+ * [android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT]
* is enabled and [maxTasksLimit] is strictly greater than 0.
*/
class DesktopTasksLimiter (
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 64ae35b620dc..a4bc2fe9460b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -24,8 +24,8 @@ import android.view.WindowManager
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
-import android.window.flags.DesktopModeFlags
-import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+import android.window.DesktopModeFlags
+import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 92e645d493ec..ae65892ef6c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -24,7 +24,7 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.util.SparseArray;
import android.view.SurfaceControl;
-import android.window.flags.DesktopModeFlags;
+import android.window.DesktopModeFlags;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
new file mode 100644
index 000000000000..f40a87c39aef
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 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.pip2.animation;
+
+import android.animation.Animator;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.shared.animation.Interpolators;
+
+/**
+ * Animator that handles bounds animations for entering PIP.
+ */
+public class PipEnterAnimator extends ValueAnimator
+ implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
+ @NonNull private final SurfaceControl mLeash;
+ private final SurfaceControl.Transaction mStartTransaction;
+ private final SurfaceControl.Transaction mFinishTransaction;
+
+ // Bounds updated by the evaluator as animator is running.
+ private final Rect mAnimatedRect = new Rect();
+
+ private final RectEvaluator mRectEvaluator;
+ private final Rect mEndBounds = new Rect();
+ @Nullable private final Rect mSourceRectHint;
+ private final @Surface.Rotation int mRotation;
+ @Nullable private Runnable mAnimationStartCallback;
+ @Nullable private Runnable mAnimationEndCallback;
+
+ private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ mSurfaceControlTransactionFactory;
+
+ // Internal state representing initial transform - cached to avoid recalculation.
+ private final PointF mInitScale = new PointF();
+ private final PointF mInitPos = new PointF();
+ private final Rect mInitCrop = new Rect();
+
+ public PipEnterAnimator(Context context,
+ @NonNull SurfaceControl leash,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
+ @NonNull Rect endBounds,
+ @Nullable Rect sourceRectHint,
+ @Surface.Rotation int rotation) {
+ mLeash = leash;
+ mStartTransaction = startTransaction;
+ mFinishTransaction = finishTransaction;
+ mRectEvaluator = new RectEvaluator(mAnimatedRect);
+ mEndBounds.set(endBounds);
+ mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null;
+ mRotation = rotation;
+ mSurfaceControlTransactionFactory =
+ new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+
+ final int enterAnimationDuration = context.getResources()
+ .getInteger(R.integer.config_pipEnterAnimationDuration);
+ setDuration(enterAnimationDuration);
+ setFloatValues(0f, 1f);
+ setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ addListener(this);
+ addUpdateListener(this);
+ }
+
+ public void setAnimationStartCallback(@NonNull Runnable runnable) {
+ mAnimationStartCallback = runnable;
+ }
+
+ public void setAnimationEndCallback(@NonNull Runnable runnable) {
+ mAnimationEndCallback = runnable;
+ }
+
+ @Override
+ public void onAnimationStart(@NonNull Animator animation) {
+ if (mAnimationStartCallback != null) {
+ mAnimationStartCallback.run();
+ }
+ if (mStartTransaction != null) {
+ onEnterAnimationUpdate(mInitScale, mInitPos, mInitCrop,
+ 0f /* fraction */, mStartTransaction);
+ mStartTransaction.apply();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation) {
+ if (mAnimationEndCallback != null) {
+ mAnimationEndCallback.run();
+ }
+ }
+
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
+ final float fraction = getAnimatedFraction();
+ onEnterAnimationUpdate(mInitScale, mInitPos, mInitCrop, fraction, tx);
+ tx.apply();
+ }
+
+ private void onEnterAnimationUpdate(PointF initScale, PointF initPos, Rect initCrop,
+ float fraction, SurfaceControl.Transaction tx) {
+ float scaleX = 1 + (initScale.x - 1) * (1 - fraction);
+ float scaleY = 1 + (initScale.y - 1) * (1 - fraction);
+ tx.setScale(mLeash, scaleX, scaleY);
+
+ float posX = initPos.x + (mEndBounds.left - initPos.x) * fraction;
+ float posY = initPos.y + (mEndBounds.top - initPos.y) * fraction;
+ tx.setPosition(mLeash, posX, posY);
+
+ Rect endCrop = new Rect(mEndBounds);
+ endCrop.offsetTo(0, 0);
+ mRectEvaluator.evaluate(fraction, initCrop, endCrop);
+ tx.setCrop(mLeash, mAnimatedRect);
+ }
+
+ // no-ops
+
+ @Override
+ public void onAnimationCancel(@NonNull Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(@NonNull Animator animation) {}
+
+ /**
+ * Caches the initial transform relevant values for the bounds enter animation.
+ *
+ * Since enter PiP makes use of a config-at-end transition, initial transform needs to be
+ * calculated differently from generic transitions.
+ * @param pipChange PiP change received as a transition target.
+ */
+ public void setEnterStartState(@NonNull TransitionInfo.Change pipChange) {
+ PipUtils.calcStartTransform(pipChange, mInitScale, mInitPos, mInitCrop);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
index 8ebdc96c21a3..8fa5aa933929 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
@@ -19,7 +19,6 @@ package com.android.wm.shell.pip2.animation;
import android.animation.Animator;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
-import android.annotation.IntDef;
import android.content.Context;
import android.graphics.Rect;
import android.view.Surface;
@@ -30,35 +29,22 @@ import androidx.annotation.Nullable;
import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import com.android.wm.shell.shared.animation.Interpolators;
/**
- * Animator that handles bounds animations for entering / exiting PIP.
+ * Animator that handles bounds animations for exit-via-expanding PIP.
*/
-public class PipEnterExitAnimator extends ValueAnimator
+public class PipExpandAnimator extends ValueAnimator
implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
- @IntDef(prefix = {"BOUNDS_"}, value = {
- BOUNDS_ENTER,
- BOUNDS_EXIT
- })
-
- @Retention(RetentionPolicy.SOURCE)
- public @interface BOUNDS {}
-
- public static final int BOUNDS_ENTER = 0;
- public static final int BOUNDS_EXIT = 1;
-
- @NonNull private final SurfaceControl mLeash;
+ @NonNull
+ private final SurfaceControl mLeash;
private final SurfaceControl.Transaction mStartTransaction;
private final SurfaceControl.Transaction mFinishTransaction;
- private final int mEnterExitAnimationDuration;
- private final @BOUNDS int mDirection;
private final @Surface.Rotation int mRotation;
// optional callbacks for tracking animation start and end
- @Nullable private Runnable mAnimationStartCallback;
+ @Nullable
+ private Runnable mAnimationStartCallback;
@Nullable private Runnable mAnimationEndCallback;
private final Rect mBaseBounds = new Rect();
@@ -78,7 +64,7 @@ public class PipEnterExitAnimator extends ValueAnimator
private final RectEvaluator mInsetEvaluator;
private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
- public PipEnterExitAnimator(Context context,
+ public PipExpandAnimator(Context context,
@NonNull SurfaceControl leash,
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
@@ -86,7 +72,6 @@ public class PipEnterExitAnimator extends ValueAnimator
@NonNull Rect startBounds,
@NonNull Rect endBounds,
@Nullable Rect sourceRectHint,
- @BOUNDS int direction,
@Surface.Rotation int rotation) {
mLeash = leash;
mStartTransaction = startTransaction;
@@ -98,7 +83,6 @@ public class PipEnterExitAnimator extends ValueAnimator
mRectEvaluator = new RectEvaluator(mAnimatedRect);
mInsetEvaluator = new RectEvaluator(new Rect());
mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context);
- mDirection = direction;
mRotation = rotation;
mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null;
@@ -113,12 +97,14 @@ public class PipEnterExitAnimator extends ValueAnimator
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
- mEnterExitAnimationDuration = context.getResources()
+
+ final int enterAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipEnterAnimationDuration);
+ setDuration(enterAnimationDuration);
setObjectValues(startBounds, endBounds);
- setDuration(mEnterExitAnimationDuration);
setEvaluator(mRectEvaluator);
+ setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
addListener(this);
addUpdateListener(this);
}
@@ -147,9 +133,10 @@ public class PipEnterExitAnimator extends ValueAnimator
// finishTransaction might override some state (eg. corner radii) so we want to
// manually set the state to the end of the animation
mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash, mSourceRectHint,
- mBaseBounds, mAnimatedRect, getInsets(1f), isInPipDirection(), 1f)
- .round(mFinishTransaction, mLeash, isInPipDirection())
- .shadow(mFinishTransaction, mLeash, isInPipDirection());
+ mBaseBounds, mAnimatedRect, getInsets(1f),
+ false /* isInPipDirection */, 1f)
+ .round(mFinishTransaction, mLeash, false /* applyCornerRadius */)
+ .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */);
}
if (mAnimationEndCallback != null) {
mAnimationEndCallback.run();
@@ -160,32 +147,22 @@ public class PipEnterExitAnimator extends ValueAnimator
public void onAnimationUpdate(@NonNull ValueAnimator animation) {
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
final float fraction = getAnimatedFraction();
- Rect insets = getInsets(fraction);
// TODO (b/350801661): implement fixed rotation
+ Rect insets = getInsets(fraction);
mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint,
- mBaseBounds, mAnimatedRect, insets, isInPipDirection(), fraction)
- .round(tx, mLeash, isInPipDirection())
- .shadow(tx, mLeash, isInPipDirection());
+ mBaseBounds, mAnimatedRect, insets, false /* isInPipDirection */, fraction)
+ .round(tx, mLeash, false /* applyCornerRadius */)
+ .shadow(tx, mLeash, false /* applyCornerRadius */);
tx.apply();
}
-
private Rect getInsets(float fraction) {
- Rect startInsets = isInPipDirection() ? mZeroInsets : mSourceRectHintInsets;
- Rect endInsets = isInPipDirection() ? mSourceRectHintInsets : mZeroInsets;
-
+ final Rect startInsets = mSourceRectHintInsets;
+ final Rect endInsets = mZeroInsets;
return mInsetEvaluator.evaluate(fraction, startInsets, endInsets);
}
- private boolean isInPipDirection() {
- return mDirection == BOUNDS_ENTER;
- }
-
- private boolean isOutPipDirection() {
- return mDirection == BOUNDS_EXIT;
- }
-
// no-ops
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 62a60fab6603..b57f51aff176 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -56,7 +56,8 @@ import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
-import com.android.wm.shell.pip2.animation.PipEnterExitAnimator;
+import com.android.wm.shell.pip2.animation.PipEnterAnimator;
+import com.android.wm.shell.pip2.animation.PipExpandAnimator;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.sysui.ShellInit;
@@ -218,6 +219,7 @@ public class PipTransition extends PipTransitionController implements
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ mFinishCallback = finishCallback;
if (transition == mEnterTransition || info.getType() == TRANSIT_PIP) {
mEnterTransition = null;
// If we are in swipe PiP to Home transition we are ENTERING_PIP as a jumpcut transition
@@ -258,6 +260,7 @@ public class PipTransition extends PipTransitionController implements
if (isRemovePipTransition(info)) {
return removePipImmediately(info, startTransaction, finishTransaction, finishCallback);
}
+ mFinishCallback = null;
return false;
}
@@ -297,7 +300,6 @@ public class PipTransition extends PipTransitionController implements
mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
}
- mFinishCallback = finishCallback;
mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS, extra);
return true;
}
@@ -349,7 +351,6 @@ public class PipTransition extends PipTransitionController implements
startTransaction.setMatrix(pipLeash, transformTensor, matrixTmp);
}
startTransaction.apply();
- finishCallback.onTransitionFinished(null /* finishWct */);
finishInner();
return true;
}
@@ -386,14 +387,6 @@ public class PipTransition extends PipTransitionController implements
return false;
}
- WindowContainerToken pipTaskToken = pipChange.getContainer();
- if (pipTaskToken == null) {
- return false;
- }
-
- WindowContainerTransaction finishWct = new WindowContainerTransaction();
- SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
-
Rect startBounds = pipChange.getStartAbsBounds();
Rect endBounds = pipChange.getEndAbsBounds();
SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
@@ -405,29 +398,22 @@ public class PipTransition extends PipTransitionController implements
sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
}
- // For opening type transitions, if there is a non-pip change of mode TO_FRONT/OPEN,
+ // For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
// make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
// by the Transitions framework to simplify Task opening transitions.
if (TransitionUtil.isOpeningType(info.getType())) {
for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getLeash() == null || change == pipChange) continue;
+ if (change.getLeash() == null) continue;
if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
startTransaction.setAlpha(change.getLeash(), 1f);
}
}
}
- PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash,
- startTransaction, finishTransaction, startBounds, startBounds, endBounds,
- sourceRectHint, PipEnterExitAnimator.BOUNDS_ENTER, Surface.ROTATION_0);
-
- tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
- this::finishInner);
- finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
-
- animator.setAnimationEndCallback(() ->
- finishCallback.onTransitionFinished(finishWct));
-
+ PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
+ startTransaction, finishTransaction, endBounds, sourceRectHint, Surface.ROTATION_0);
+ animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange));
+ animator.setAnimationEndCallback(this::finishInner);
animator.start();
return true;
}
@@ -452,11 +438,8 @@ public class PipTransition extends PipTransitionController implements
PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipLeash, startTransaction,
PipAlphaAnimator.FADE_IN);
- animator.setAnimationEndCallback(() -> {
- finishCallback.onTransitionFinished(null);
- // This should update the pip transition state accordingly after we stop playing.
- finishInner();
- });
+ // This should update the pip transition state accordingly after we stop playing.
+ animator.setAnimationEndCallback(this::finishInner);
animator.start();
return true;
@@ -510,9 +493,9 @@ public class PipTransition extends PipTransitionController implements
sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint();
}
- PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash,
+ PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash,
startTransaction, finishTransaction, endBounds, startBounds, endBounds,
- sourceRectHint, PipEnterExitAnimator.BOUNDS_EXIT, Surface.ROTATION_0);
+ sourceRectHint, Surface.ROTATION_0);
animator.setAnimationEndCallback(() -> {
mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
@@ -631,6 +614,7 @@ public class PipTransition extends PipTransitionController implements
//
private void finishInner() {
+ finishTransition(null /* tx */);
if (mPipTransitionState.getSwipePipToHomeOverlay() != null) {
startOverlayFadeoutAnimation();
} else if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) {
@@ -652,6 +636,7 @@ public class PipTransition extends PipTransitionController implements
}
if (mFinishCallback != null) {
mFinishCallback.onTransitionFinished(wct);
+ mFinishCallback = null;
}
}
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 a0b7a29cee98..6086801491e2 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
@@ -38,8 +38,8 @@ import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
+import android.window.DesktopModeFlags;
import android.window.WindowContainerToken;
-import android.window.flags.DesktopModeFlags;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 0cbbb715cde6..1af99f974a28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -21,7 +21,7 @@ import android.os.IBinder
import android.util.ArrayMap
import android.view.SurfaceControl
import android.window.TransitionInfo
-import android.window.flags.DesktopModeFlags
+import android.window.DesktopModeFlags
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 839973fcbdd5..576c911d4459 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.windowdecor;
-import static android.window.flags.DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING;
+import static android.window.DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index bcf48d9ec2eb..e55bc67ba41b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -77,10 +77,10 @@ import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Toast;
+import android.window.DesktopModeFlags;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import android.window.flags.DesktopModeFlags;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
@@ -103,8 +103,8 @@ import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
-import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
@@ -133,14 +133,14 @@ import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import kotlin.Pair;
import kotlin.Unit;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* View model for the window decoration with a caption and shadows. Works with
* {@link DesktopModeWindowDecoration}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 25d37fce7270..a78fb9b5e245 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -24,8 +24,8 @@ import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
-import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
-import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
+import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
+import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode;
@@ -72,9 +72,9 @@ import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.ImageButton;
+import android.window.DesktopModeFlags;
import android.window.TaskSnapshot;
import android.window.WindowContainerTransaction;
-import android.window.flags.DesktopModeFlags;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 38f9cfaca7ae..60c922293d80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -27,7 +27,7 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
-import android.window.flags.DesktopModeFlags;
+import android.window.DesktopModeFlags;
import androidx.annotation.NonNull;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index d726f5083eb6..33d1c260cb84 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -18,7 +18,7 @@ package com.android.wm.shell.windowdecor;
import static android.view.InputDevice.SOURCE_MOUSE;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
-import static android.window.flags.DesktopModeFlags.ENABLE_WINDOWING_EDGE_DRAG_RESIZE;
+import static android.window.DesktopModeFlags.ENABLE_WINDOWING_EDGE_DRAG_RESIZE;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
index 68a58ee0a370..376cd2a78baf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
@@ -33,7 +33,7 @@ import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import androidx.core.content.ContextCompat
import com.android.wm.shell.R
-import android.window.flags.DesktopModeFlags
+import android.window.DesktopModeFlags
private const val OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS = 350
private const val MAX_DRAWABLE_ALPHA = 255
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index 52bf40062cdb..c2af1d45e76f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -49,7 +49,7 @@ import com.android.internal.R.attr.materialColorSurfaceDim
import com.android.window.flags.Flags
import com.android.window.flags.Flags.enableMinimizeButton
import com.android.wm.shell.R
-import android.window.flags.DesktopModeFlags
+import android.window.DesktopModeFlags
import com.android.wm.shell.windowdecor.MaximizeButtonView
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.OPACITY_100
diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
index e81cbfb508ae..c0ef4b14d53f 100644
--- a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
+++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
@@ -29,37 +29,6 @@ namespace android {
namespace uirenderer {
namespace skiapipeline {
-BackdropFilterDrawable::~BackdropFilterDrawable() {}
-
-bool BackdropFilterDrawable::prepareToDraw(SkCanvas* canvas, const RenderProperties& properties,
- int backdropImageWidth, int backdropImageHeight) {
- // the drawing bounds for blurred content.
- mDstBounds.setWH(properties.getWidth(), properties.getHeight());
-
- float alphaMultiplier = 1.0f;
- RenderNodeDrawable::setViewProperties(properties, canvas, &alphaMultiplier, true);
-
- // get proper subset for previous content.
- canvas->getTotalMatrix().mapRect(&mImageSubset, mDstBounds);
- SkRect imageSubset(mImageSubset);
- // ensure the subset is inside bounds of previous content.
- if (!mImageSubset.intersect(SkRect::MakeWH(backdropImageWidth, backdropImageHeight))) {
- return false;
- }
-
- // correct the drawing bounds if subset was changed.
- if (mImageSubset != imageSubset) {
- SkMatrix inverse;
- if (canvas->getTotalMatrix().invert(&inverse)) {
- inverse.mapRect(&mDstBounds, mImageSubset);
- }
- }
-
- // follow the alpha from the target RenderNode.
- mPaint.setAlpha(properties.layerProperties().alpha() * alphaMultiplier);
- return true;
-}
-
void BackdropFilterDrawable::onDraw(SkCanvas* canvas) {
const RenderProperties& properties = mTargetRenderNode->properties();
auto* backdropFilter = properties.layerProperties().getBackdropImageFilter();
@@ -68,27 +37,43 @@ void BackdropFilterDrawable::onDraw(SkCanvas* canvas) {
return;
}
- auto backdropImage = surface->makeImageSnapshot();
- // sync necessary properties from target RenderNode.
- if (!prepareToDraw(canvas, properties, backdropImage->width(), backdropImage->height())) {
+ SkRect srcBounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+
+ float alphaMultiplier = 1.0f;
+ RenderNodeDrawable::setViewProperties(properties, canvas, &alphaMultiplier, true);
+ SkPaint paint;
+ paint.setAlpha(properties.layerProperties().alpha() * alphaMultiplier);
+
+ SkRect surfaceSubset;
+ canvas->getTotalMatrix().mapRect(&surfaceSubset, srcBounds);
+ if (!surfaceSubset.intersect(SkRect::MakeWH(surface->width(), surface->height()))) {
return;
}
- auto imageSubset = mImageSubset.roundOut();
+ auto backdropImage = surface->makeImageSnapshot(surfaceSubset.roundOut());
+
+ SkIRect imageBounds = SkIRect::MakeWH(backdropImage->width(), backdropImage->height());
+ SkIPoint offset;
+ SkIRect imageSubset;
+
#ifdef __ANDROID__
if (canvas->recordingContext()) {
backdropImage =
SkImages::MakeWithFilter(canvas->recordingContext(), backdropImage, backdropFilter,
- imageSubset, imageSubset, &mOutSubset, &mOutOffset);
+ imageBounds, imageBounds, &imageSubset, &offset);
} else
#endif
{
- backdropImage = SkImages::MakeWithFilter(backdropImage, backdropFilter, imageSubset,
- imageSubset, &mOutSubset, &mOutOffset);
+ backdropImage = SkImages::MakeWithFilter(backdropImage, backdropFilter, imageBounds,
+ imageBounds, &imageSubset, &offset);
}
- canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds,
- SkSamplingOptions(SkFilterMode::kLinear), &mPaint,
- SkCanvas::kStrict_SrcRectConstraint);
+
+ canvas->save();
+ canvas->resetMatrix();
+ canvas->drawImageRect(backdropImage, SkRect::Make(imageSubset), surfaceSubset,
+ SkSamplingOptions(SkFilterMode::kLinear), &paint,
+ SkCanvas::kFast_SrcRectConstraint);
+ canvas->restore();
}
} // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.h b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h
index 9e35837675ae..5e216a1fc3c3 100644
--- a/libs/hwui/pipeline/skia/BackdropFilterDrawable.h
+++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h
@@ -37,23 +37,10 @@ public:
BackdropFilterDrawable(RenderNode* renderNode, SkCanvas* canvas)
: mTargetRenderNode(renderNode), mBounds(canvas->getLocalClipBounds()) {}
- ~BackdropFilterDrawable();
+ ~BackdropFilterDrawable() = default;
private:
RenderNode* mTargetRenderNode;
- SkPaint mPaint;
-
- SkRect mDstBounds;
- SkRect mImageSubset;
- SkIRect mOutSubset;
- SkIPoint mOutOffset;
-
- /**
- * Check all necessary properties before actual drawing.
- * Return true if ready to draw.
- */
- bool prepareToDraw(SkCanvas* canvas, const RenderProperties& properties, int backdropImageWidth,
- int backdropImageHeight);
protected:
void onDraw(SkCanvas* canvas) override;
diff --git a/libs/hwui/tests/common/scenes/BackdropBlur.cpp b/libs/hwui/tests/common/scenes/BackdropBlur.cpp
new file mode 100644
index 000000000000..a1133ffe96ef
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/BackdropBlur.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <SkBlendMode.h>
+
+#include "SkImageFilter.h"
+#include "SkImageFilters.h"
+#include "TestSceneBase.h"
+#include "utils/Blur.h"
+
+class BackdropBlurAnimation : public TestScene {
+private:
+ std::unique_ptr<TestScene> listView;
+
+public:
+ explicit BackdropBlurAnimation(const TestScene::Options& opts) {
+ listView.reset(TestScene::testMap()["listview"].createScene(opts));
+ }
+
+ void createContent(int width, int height, Canvas& canvas) override {
+ sp<RenderNode> list = TestUtils::createNode(
+ 0, 0, width, height,
+ [this, width, height](RenderProperties& props, Canvas& canvas) {
+ props.setClipToBounds(false);
+ listView->createContent(width, height, canvas);
+ });
+
+ canvas.drawRenderNode(list.get());
+
+ int x = width / 8;
+ int y = height / 4;
+ sp<RenderNode> blurNode = TestUtils::createNode(
+ x, y, width - x, height - y, [](RenderProperties& props, Canvas& canvas) {
+ props.mutableOutline().setRoundRect(0, 0, props.getWidth(), props.getHeight(),
+ dp(16), 1);
+ props.mutableOutline().setShouldClip(true);
+ sk_sp<SkImageFilter> blurFilter = SkImageFilters::Blur(
+ Blur::convertRadiusToSigma(dp(8)), Blur::convertRadiusToSigma(dp(8)),
+ SkTileMode::kClamp, nullptr, nullptr);
+ props.mutateLayerProperties().setBackdropImageFilter(blurFilter.get());
+ canvas.drawColor(0x33000000, SkBlendMode::kSrcOver);
+ });
+
+ canvas.drawRenderNode(blurNode.get());
+ }
+
+ void doFrame(int frameNr) override { listView->doFrame(frameNr); }
+};
+
+static TestScene::Registrar _BackdropBlur(TestScene::Info{
+ "backdropblur", "A rounded rect that does a blur-behind of a sky animation.",
+ [](const TestScene::Options& opts) -> test::TestScene* {
+ return new BackdropBlurAnimation(opts);
+ }});
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index ca540874833c..4b29100c55a6 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -1280,7 +1280,7 @@ RENDERTHREAD_TEST(BackdropFilterDrawable, drawing) {
canvas->drawDrawable(&backdropDrawable);
// the drawable is still visible, ok to draw.
EXPECT_EQ(2, canvas->mDrawCounter);
- EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH - 30, CANVAS_HEIGHT - 30), canvas->mDstBounds);
+ EXPECT_EQ(SkRect::MakeLTRB(30, 30, CANVAS_WIDTH, CANVAS_HEIGHT), canvas->mDstBounds);
canvas->translate(CANVAS_WIDTH, CANVAS_HEIGHT);
canvas->drawDrawable(&drawable);
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 2d7db5e6ed94..8b04644a2989 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -16,6 +16,9 @@
package android.media;
+import static com.android.media.flags.Flags.FLAG_UPDATE_CLIENT_PROFILE_PRIORITY;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -970,6 +973,27 @@ public final class MediaCas implements AutoCloseable {
registerClient(context, tvInputServiceSessionId, priorityHint);
}
+ /**
+ * Updates client priority with an arbitrary value along with a nice value.
+ *
+ * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
+ * to reclaim insufficient resources from another client.
+ *
+ * <p>The nice value represents how much the client intends to give up the resource when an
+ * insufficient resource situation happens.
+ *
+ * @see <a
+ * href="https://source.android.com/docs/devices/tv/tuner-framework#priority-nice-value">
+ * Priority value and nice value</a>
+ * @param priority the new priority. Any negative value would cause no-op on priority setting
+ * and the API would only process nice value setting in that case.
+ * @param niceValue the nice value.
+ */
+ @FlaggedApi(FLAG_UPDATE_CLIENT_PROFILE_PRIORITY)
+ public boolean updateResourcePriority(int priority, int niceValue) {
+ return mTunerResourceManager.updateClientPriority(mClientId, priority, niceValue);
+ }
+
IHwBinder getBinder() {
if (mICas != null) {
return null; // Return IHwBinder only for HIDL
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 7252135ef0ca..1ef98f2c2644 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -70,6 +70,13 @@ flag {
}
flag {
+ name: "update_client_profile_priority"
+ namespace: "media"
+ description : "Feature flag to add updateResourcePriority api to MediaCas"
+ bug: "300565729"
+}
+
+flag {
name: "enable_built_in_speaker_route_suitability_statuses"
is_exported: true
namespace: "media_solutions"
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 456fedf912ff..408ed1e861c3 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -743,12 +743,6 @@
<uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" />
<uses-permission android:name="android.permission.MANAGE_SAFETY_CENTER" />
- <!-- Permissions required for CTS test - CtsVirtualDevicesTestCases -->
- <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
- <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY" />
- <uses-permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY" />
-
-
<!-- Permission required for CTS test - Notification test suite -->
<uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 26e6e71beae7..a21a80506279 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1473,3 +1473,11 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "magic_portrait_wallpapers"
+ namespace: "systemui"
+ description: "Magic Portrait related changes in systemui"
+ bug: "370863642"
+}
+
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt b/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt
deleted file mode 100644
index 97c8076e910b..000000000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compose.activity
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.contentColorFor
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import com.android.compose.rememberSystemUiController
-import com.android.compose.theme.PlatformTheme
-
-/** Scaffolding for an edge-to-edge activity content. */
-@Composable
-fun EdgeToEdgeActivityContent(
- modifier: Modifier = Modifier,
- content: @Composable () -> Unit,
-) {
- // Make the status and navigation bars transparent, ensuring that the status bar icons are dark
- // when the theme is light and vice-versa.
- val systemUiController = rememberSystemUiController()
- val isDarkTheme = isSystemInDarkTheme()
- val useDarkIcons = !isDarkTheme
- DisposableEffect(systemUiController, useDarkIcons) {
- systemUiController.setSystemBarsColor(
- color = Color.Transparent,
- darkIcons = useDarkIcons,
- )
- onDispose {}
- }
-
- PlatformTheme(isDarkTheme) {
- val backgroundColor = MaterialTheme.colorScheme.background
- Box(modifier.fillMaxSize().background(backgroundColor)) {
- CompositionLocalProvider(LocalContentColor provides contentColorFor(backgroundColor)) {
- content()
- }
- }
- }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index bf8f6ce50944..7dc2901273b2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -522,13 +522,7 @@ private fun FoldAware(
val currentSceneKey =
if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
- val state = remember {
- MutableSceneTransitionLayoutState(
- currentSceneKey,
- SceneTransitions,
- enableInterruptions = false,
- )
- }
+ val state = remember { MutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions) }
// Update state whenever currentSceneKey has changed.
LaunchedEffect(state, currentSceneKey) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 2e8fc14517c4..2657d7cf8156 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -234,7 +234,6 @@ fun MutableSceneTransitionLayoutState(
canHideOverlay: (OverlayKey) -> Boolean = { true },
canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
stateLinks: List<StateLink> = emptyList(),
- enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
): MutableSceneTransitionLayoutState {
return MutableSceneTransitionLayoutStateImpl(
initialScene,
@@ -245,7 +244,6 @@ fun MutableSceneTransitionLayoutState(
canHideOverlay,
canReplaceOverlay,
stateLinks,
- enableInterruptions,
)
}
@@ -261,9 +259,6 @@ internal class MutableSceneTransitionLayoutStateImpl(
true
},
private val stateLinks: List<StateLink> = emptyList(),
-
- // TODO(b/290930950): Remove this flag.
- internal val enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
) : MutableSceneTransitionLayoutState {
private val creationThread: Thread = Thread.currentThread()
@@ -406,13 +401,6 @@ internal class MutableSceneTransitionLayoutStateImpl(
transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
}
- if (!enableInterruptions) {
- // Set the current transition.
- check(transitionStates.size == 1)
- transitionStates = listOf(transition)
- return
- }
-
when (val currentState = transitionStates.last()) {
is TransitionState.Idle -> {
// Replace [Idle] by [transition].
@@ -755,9 +743,6 @@ internal class MutableSceneTransitionLayoutStateImpl(
private const val TAG = "SceneTransitionLayoutState"
-/** Whether support for interruptions in enabled by default. */
-internal const val DEFAULT_INTERRUPTIONS_ENABLED = true
-
/**
* The max number of concurrent transitions. If the number of transitions goes past this number,
* this probably means that there is a leak and we will Log.wtf before clearing the list of
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index d6751ae9ff81..9d3f25e9af22 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -337,10 +337,6 @@ sealed interface TransitionState {
}
internal open fun interruptionProgress(layoutImpl: SceneTransitionLayoutImpl): Float {
- if (!layoutImpl.state.enableInterruptions) {
- return 0f
- }
-
if (replacedTransition != null) {
return replacedTransition.interruptionProgress(layoutImpl)
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 1eed54ed3019..a2c2729e09be 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -213,10 +213,6 @@ class ElementTest {
from(SceneA, to = SceneB) { spec = tween }
from(SceneB, to = SceneC) { spec = tween }
},
-
- // Disable interruptions so that the current transition is directly removed
- // when starting a new one.
- enableInterruptions = false,
)
}
@@ -243,7 +239,12 @@ class ElementTest {
onElement(TestElements.Bar).assertExists()
// Start transition from SceneB to SceneC
- rule.runOnUiThread { state.setTargetScene(SceneC, coroutineScope) }
+ rule.runOnUiThread {
+ // We snap to scene B so that the transition A => B is removed from the list of
+ // transitions.
+ state.snapToScene(SceneB)
+ state.setTargetScene(SceneC, coroutineScope)
+ }
}
at(3 * frameDuration) { onElement(TestElements.Bar).assertIsNotDisplayed() }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 400f0b39a8e5..b00b894b5dea 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -85,15 +85,9 @@ class SceneTransitionLayoutTest {
/** The content under test. */
@Composable
- private fun TestContent(enableInterruptions: Boolean = true) {
+ private fun TestContent() {
coroutineScope = rememberCoroutineScope()
- layoutState = remember {
- MutableSceneTransitionLayoutState(
- SceneA,
- EmptyTestTransitions,
- enableInterruptions = enableInterruptions,
- )
- }
+ layoutState = remember { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
SceneTransitionLayout(state = layoutState, modifier = Modifier.size(LayoutSize)) {
scene(SceneA, userActions = mapOf(Back to SceneB)) {
@@ -205,7 +199,7 @@ class SceneTransitionLayoutTest {
@Test
fun testSharedElement() {
- rule.setContent { TestContent(enableInterruptions = false) }
+ rule.setContent { TestContent() }
// In scene A, the shared element SharedFoo() is at the top end of the layout and has a size
// of 50.dp.
@@ -253,6 +247,9 @@ class SceneTransitionLayoutTest {
.isWithin(DpOffsetSubject.DefaultTolerance)
.of(DpOffset(25.dp, 25.dp))
+ // Finish the transition.
+ rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
+
// Animate to scene C, let the animation start then go to the middle of the transition.
currentScene = SceneC
rule.mainClock.advanceTimeByFrame()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
index fe5024fdc0a3..59676ce126da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -17,10 +17,17 @@
package com.android.systemui.wallpapers.data.repository
import android.app.WallpaperInfo
+import android.view.View
import kotlinx.coroutines.flow.MutableStateFlow
/** Fake implementation of the wallpaper repository. */
class FakeWallpaperRepository : WallpaperRepository {
override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null)
override val wallpaperSupportsAmbientMode = MutableStateFlow(false)
+ override var rootView: View? = null
+ private val _notificationStackAbsoluteBottom = MutableStateFlow(0F)
+
+ override fun setNotificationStackAbsoluteBottom(bottom: Float) {
+ _notificationStackAbsoluteBottom.value = bottom
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index ec52055020f2..95d1b5d7fcac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -68,11 +68,16 @@ interface KeyguardClockRepository {
val previewClock: Flow<ClockController>
+ /** top of notifications without bcsmartspace in small clock settings */
+ val notificationDefaultTop: StateFlow<Float>
+
val clockEventController: ClockEventController
val shouldForceSmallClock: Boolean
fun setClockSize(size: ClockSize)
+
+ fun setNotificationDefaultTop(top: Float)
}
@SysUISingleton
@@ -108,7 +113,7 @@ constructor(
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = getClockSize()
+ initialValue = getClockSize(),
)
override val currentClockId: Flow<ClockId> =
@@ -138,7 +143,7 @@ constructor(
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = clockRegistry.createCurrentClock()
+ initialValue = clockRegistry.createCurrentClock(),
)
override val previewClock: Flow<ClockController> =
@@ -149,6 +154,14 @@ constructor(
clockRegistry.createCurrentClock()
}
+ private val _notificationDefaultTop: MutableStateFlow<Float> = MutableStateFlow(0F)
+
+ override val notificationDefaultTop: StateFlow<Float> = _notificationDefaultTop.asStateFlow()
+
+ override fun setNotificationDefaultTop(top: Float) {
+ _notificationDefaultTop.value = top
+ }
+
override val shouldForceSmallClock: Boolean
get() =
featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE) &&
@@ -160,7 +173,7 @@ constructor(
secureSettings.getIntForUser(
Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
/* defaultValue= */ 1,
- UserHandle.USER_CURRENT
+ UserHandle.USER_CURRENT,
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 130242f55600..821017418277 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -269,6 +269,9 @@ interface KeyguardRepository {
*/
val isEncryptedOrLockdown: Flow<Boolean>
+ /** The top of shortcut in screen, used by wallpaper to find remaining space in lockscreen */
+ val shortcutAbsoluteTop: StateFlow<Float>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -339,6 +342,8 @@ interface KeyguardRepository {
* otherwise.
*/
fun isShowKeyguardWhenReenabled(): Boolean
+
+ fun setShortcutAbsoluteTop(top: Float)
}
/** Encapsulates application state for the keyguard. */
@@ -503,7 +508,7 @@ constructor(
trySendWithFailureLogging(
statusBarStateController.dozeAmount,
TAG,
- "initial dozeAmount"
+ "initial dozeAmount",
)
awaitClose { statusBarStateController.removeCallback(callback) }
@@ -521,7 +526,7 @@ constructor(
object : DozeTransitionCallback {
override fun onDozeTransition(
oldState: DozeMachine.State,
- newState: DozeMachine.State
+ newState: DozeMachine.State,
) {
trySendWithFailureLogging(
DozeTransitionModel(
@@ -529,7 +534,7 @@ constructor(
to = dozeMachineStateToModel(newState),
),
TAG,
- "doze transition model"
+ "doze transition model",
)
}
}
@@ -541,7 +546,7 @@ constructor(
to = dozeMachineStateToModel(dozeTransitionListener.newState),
),
TAG,
- "initial doze transition model"
+ "initial doze transition model",
)
awaitClose { dozeTransitionListener.removeCallback(callback) }
@@ -579,7 +584,7 @@ constructor(
trySendWithFailureLogging(
statusBarStateIntToObject(state),
TAG,
- "state"
+ "state",
)
}
}
@@ -590,7 +595,7 @@ constructor(
.stateIn(
scope,
SharingStarted.Eagerly,
- statusBarStateIntToObject(statusBarStateController.state)
+ statusBarStateIntToObject(statusBarStateController.state),
)
private val _biometricUnlockState: MutableStateFlow<BiometricUnlockModel> =
@@ -610,7 +615,7 @@ constructor(
trySendWithFailureLogging(
authController.fingerprintSensorLocation,
TAG,
- "AuthController.Callback#onFingerprintLocationChanged"
+ "AuthController.Callback#onFingerprintLocationChanged",
)
}
@@ -635,6 +640,9 @@ constructor(
private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
override val isActiveDreamLockscreenHosted = _isActiveDreamLockscreenHosted.asStateFlow()
+ private val _shortcutAbsoluteTop = MutableStateFlow(0F)
+ override val shortcutAbsoluteTop = _shortcutAbsoluteTop.asStateFlow()
+
init {
val callback =
object : KeyguardStateController.Callback {
@@ -721,6 +729,10 @@ constructor(
}
}
+ override fun setShortcutAbsoluteTop(top: Float) {
+ _shortcutAbsoluteTop.value = top
+ }
+
private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
return when (state) {
DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index c0049d4e2e6c..5b7eeddfb8e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -94,7 +94,7 @@ constructor(
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = ClockSize.LARGE
+ initialValue = ClockSize.LARGE,
)
} else {
keyguardClockRepository.clockSize
@@ -152,4 +152,8 @@ constructor(
clock.largeClock.animations.fold(foldFraction)
}
}
+
+ fun setNotificationStackDefaultTop(top: Float) {
+ keyguardClockRepository.setNotificationDefaultTop(top)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index e6ee11215595..d7f96b55c4a3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -526,6 +526,10 @@ constructor(
repository.showDismissibleKeyguard()
}
+ fun setShortcutAbsoluteTop(top: Float) {
+ repository.setShortcutAbsoluteTop(top)
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index a1c963b3137a..0b10c8a14633 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -44,6 +44,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shared.R as sharedR
import com.android.systemui.util.ui.value
import dagger.Lazy
@@ -70,6 +71,7 @@ constructor(
val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
private val rootViewModel: KeyguardRootViewModel,
private val aodBurnInViewModel: AodBurnInViewModel,
+ private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
) : KeyguardSection() {
private var disposableHandle: DisposableHandle? = null
@@ -172,7 +174,7 @@ constructor(
}
}
- open fun applyDefaultConstraints(constraints: ConstraintSet) {
+ fun applyDefaultConstraints(constraints: ConstraintSet) {
val guideline =
if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID
else R.id.split_shade_guideline
@@ -211,6 +213,28 @@ constructor(
// Explicitly clear pivot to force recalculate pivot instead of using legacy value
setTransformPivot(R.id.lockscreen_clock_view_large, Float.NaN, Float.NaN)
+
+ val smallClockBottom =
+ keyguardClockViewModel.getSmallClockTopMargin() +
+ context.resources.getDimensionPixelSize(
+ com.android.systemui.customization.R.dimen.small_clock_height
+ )
+ val dateWeatherSmartspaceHeight = getDimen(context, DATE_WEATHER_VIEW_HEIGHT).toFloat()
+ val marginBetweenSmartspaceAndNotification =
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_status_view_bottom_margin
+ ) +
+ if (context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)) {
+ largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
+ } else {
+ 0
+ }
+
+ clockInteractor.setNotificationStackDefaultTop(
+ smallClockBottom +
+ dateWeatherSmartspaceHeight +
+ marginBetweenSmartspaceAndNotification
+ )
}
constrainWeatherClockDateIconsBarrier(constraints)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 6c6e14cac84d..d3895def28e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -30,6 +30,7 @@ import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
@@ -52,6 +53,7 @@ constructor(
private val indicationController: KeyguardIndicationController,
private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
+ private val keyguardInteractor: KeyguardInteractor,
) : BaseShortcutSection() {
// Amount to increase the bottom margin by to avoid colliding with inset
@@ -117,7 +119,7 @@ constructor(
BOTTOM,
PARENT_ID,
BOTTOM,
- verticalOffsetMargin + safeInsetBottom
+ verticalOffsetMargin + safeInsetBottom,
)
constrainWidth(R.id.end_button, width)
@@ -128,7 +130,7 @@ constructor(
BOTTOM,
PARENT_ID,
BOTTOM,
- verticalOffsetMargin + safeInsetBottom
+ verticalOffsetMargin + safeInsetBottom,
)
// The constraint set visibility for start and end button are default visible, set to
@@ -136,5 +138,13 @@ constructor(
setVisibilityMode(R.id.start_button, VISIBILITY_MODE_IGNORE)
setVisibilityMode(R.id.end_button, VISIBILITY_MODE_IGNORE)
}
+
+ val shortcutAbsoluteTopInScreen =
+ (resources.displayMetrics.heightPixels -
+ (verticalOffsetMargin + safeInsetBottom) -
+ height)
+ .toFloat()
+
+ keyguardInteractor.setShortcutAbsoluteTop(shortcutAbsoluteTopInScreen)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
index 3920d585734e..ab614f9c32d9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
@@ -646,7 +646,7 @@ public class LegacyScreenshotController implements InteractiveScreenshotHandler
private void saveScreenshotInBackground(ScreenshotData screenshot, UUID requestId,
Consumer<Uri> finisher, Consumer<ImageExporter.Result> onResult) {
ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
- requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(),
+ requestId, screenshot.getBitmap(), screenshot.getUserHandle(),
mDisplay.getDisplayId());
future.addListener(() -> {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index c390e71d3701..b5b15a9512ae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -4,7 +4,6 @@ import android.content.ComponentName
import android.graphics.Bitmap
import android.graphics.Insets
import android.graphics.Rect
-import android.os.Process
import android.os.UserHandle
import android.view.Display
import android.view.WindowManager
@@ -30,10 +29,6 @@ data class ScreenshotData(
val packageNameString
get() = topComponent?.packageName ?: ""
- fun getUserOrDefault(): UserHandle {
- return userHandle ?: Process.myUserHandle()
- }
-
companion object {
@JvmStatic
fun fromRequest(request: ScreenshotRequest, displayId: Int = Display.DEFAULT_DISPLAY) =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 2c6b09cb4cb6..adb3352b948c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -3884,6 +3884,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
showingLayout.dump(pw, args);
dumpCustomOutline(pw, args);
+ dumpClipping(pw, args);
if (getViewState() != null) {
getViewState().dump(pw, args);
pw.println();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index afda4262bf9d..ef6cad134534 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -856,17 +856,23 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
pw.println();
}
if (DUMP_VERBOSE) {
- pw.println("mInRemovalAnimation: " + mInRemovalAnimation);
- pw.println("mClipTopAmount: " + mClipTopAmount);
- pw.println("mClipBottomAmount " + mClipBottomAmount);
- pw.println("mClipToActualHeight: " + mClipToActualHeight);
- pw.println("mExtraWidthForClipping: " + mExtraWidthForClipping);
- pw.println("mMinimumHeightForClipping: " + mMinimumHeightForClipping);
- pw.println("getClipBounds(): " + getClipBounds());
+ dumpClipping(pw, args);
}
});
}
+ protected void dumpClipping(IndentingPrintWriter pw, String[] args) {
+ pw.print("Clipping: ");
+ pw.print("mInRemovalAnimation", mInRemovalAnimation);
+ pw.print("mClipTopAmount", mClipTopAmount);
+ pw.print("mClipBottomAmount", mClipBottomAmount);
+ pw.print("mClipToActualHeight", mClipToActualHeight);
+ pw.print("mExtraWidthForClipping", mExtraWidthForClipping);
+ pw.print("mMinimumHeightForClipping", mMinimumHeightForClipping);
+ pw.print("getClipBounds()", getClipBounds());
+ pw.println();
+ }
+
/**
* return the amount that the content is translated
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 8ae6b7920674..d828a6700ee6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -130,6 +130,7 @@ import com.android.systemui.util.Assert;
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.ListenerSet;
+import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
import com.google.errorprone.annotations.CompileTimeConstant;
@@ -628,6 +629,9 @@ public class NotificationStackScrollLayout
@Nullable
private OnClickListener mManageButtonClickListener;
+ @Nullable
+ private WallpaperInteractor mWallpaperInteractor;
+
public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0, 0);
Resources res = getResources();
@@ -1192,6 +1196,7 @@ public class NotificationStackScrollLayout
if (!SceneContainerFlag.isEnabled()) {
setMaxLayoutHeight(getHeight());
updateContentHeight();
+ mWallpaperInteractor.setNotificationStackAbsoluteBottom(mContentHeight);
}
clampScrollPosition();
requestChildrenUpdate();
@@ -1253,6 +1258,7 @@ public class NotificationStackScrollLayout
if (mAmbientState.getStackTop() != stackTop) {
mAmbientState.setStackTop(stackTop);
onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
+ mWallpaperInteractor.setNotificationStackAbsoluteBottom((int) stackTop);
}
}
@@ -5898,6 +5904,10 @@ public class NotificationStackScrollLayout
mController.getNotificationRoundnessManager().setAnimatedChildren(mChildrenToAddAnimated);
}
+ public void setWallpaperInteractor(WallpaperInteractor wallpaperInteractor) {
+ mWallpaperInteractor = wallpaperInteractor;
+ }
+
void addSwipedOutView(View v) {
mSwipedOutViews.add(v);
}
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 7b02d0cebfb3..00c5c40fc8ac 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
@@ -145,6 +145,7 @@ import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.Compile;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -226,6 +227,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final SensitiveNotificationProtectionController
mSensitiveNotificationProtectionController;
+ private final WallpaperInteractor mWallpaperInteractor;
+
private View mLongPressedView;
private final NotificationListContainerImpl mNotificationListContainer =
@@ -756,7 +759,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
NotificationDismissibilityProvider dismissibilityProvider,
ActivityStarter activityStarter,
SplitShadeStateController splitShadeStateController,
- SensitiveNotificationProtectionController sensitiveNotificationProtectionController) {
+ SensitiveNotificationProtectionController sensitiveNotificationProtectionController,
+ WallpaperInteractor wallpaperInteractor) {
mView = view;
mKeyguardTransitionRepo = keyguardTransitionRepo;
mViewBinder = viewBinder;
@@ -812,6 +816,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mDismissibilityProvider = dismissibilityProvider;
mActivityStarter = activityStarter;
mSensitiveNotificationProtectionController = sensitiveNotificationProtectionController;
+ mWallpaperInteractor = wallpaperInteractor;
mView.passSplitShadeStateController(splitShadeStateController);
if (SceneContainerFlag.isEnabled()) {
mWakeUpCoordinator.setStackScroller(this);
@@ -948,6 +953,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
this::onKeyguardTransitionChanged);
}
+
+ mView.setWallpaperInteractor(mWallpaperInteractor);
}
private boolean isInVisibleLocation(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
index 65a02184f96d..0744b5a0b18c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
@@ -32,19 +32,24 @@ private const val TAG = "WallpaperController"
* Note: New logic should be added to [WallpaperRepository], not this class.
*/
@SysUISingleton
-class WallpaperController @Inject constructor(
+class WallpaperController
+@Inject
+constructor(
private val wallpaperManager: WallpaperManager,
private val wallpaperRepository: WallpaperRepository,
) {
var rootView: View? = null
+ set(value) {
+ field = value
+ wallpaperRepository.rootView = value
+ }
private var notificationShadeZoomOut: Float = 0f
private var unfoldTransitionZoomOut: Float = 0f
private val shouldUseDefaultUnfoldTransition: Boolean
- get() = wallpaperRepository.wallpaperInfo.value?.shouldUseDefaultUnfoldTransition()
- ?: true
+ get() = wallpaperRepository.wallpaperInfo.value?.shouldUseDefaultUnfoldTransition() ?: true
fun setNotificationShadeZoom(zoomOut: Float) {
notificationShadeZoomOut = zoomOut
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
index b45b8cd15bf5..54953c9c2574 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.wallpapers.data.repository
import android.app.WallpaperInfo
+import android.view.View
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -33,4 +34,7 @@ import kotlinx.coroutines.flow.asStateFlow
class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow()
override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow()
+ override var rootView: View? = null
+
+ override fun setNotificationStackAbsoluteBottom(bottom: Float) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index 041b6f963e27..203e1da2afcf 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -21,10 +21,16 @@ import android.app.WallpaperManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.os.Bundle
import android.os.UserHandle
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Flags
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.user.data.model.SelectedUserModel
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -32,16 +38,19 @@ import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/** A repository storing information about the current wallpaper. */
@@ -51,6 +60,15 @@ interface WallpaperRepository {
/** Emits true if the current user's current wallpaper supports ambient mode. */
val wallpaperSupportsAmbientMode: StateFlow<Boolean>
+
+ /** Set rootView to get its windowToken afterwards */
+ var rootView: View?
+
+ /**
+ * Set bottom of notifications from notification stack, and Magic Portrait will layout base on
+ * this value
+ */
+ fun setNotificationStackAbsoluteBottom(bottom: Float)
}
@SysUISingleton
@@ -61,6 +79,8 @@ constructor(
@Background private val bgDispatcher: CoroutineDispatcher,
broadcastDispatcher: BroadcastDispatcher,
userRepository: UserRepository,
+ keyguardRepository: KeyguardRepository,
+ keyguardClockRepository: KeyguardClockRepository,
private val wallpaperManager: WallpaperManager,
context: Context,
) : WallpaperRepository {
@@ -69,10 +89,7 @@ constructor(
private val wallpaperChanged: Flow<Unit> =
broadcastDispatcher
- .broadcastFlow(
- IntentFilter(Intent.ACTION_WALLPAPER_CHANGED),
- user = UserHandle.ALL,
- )
+ .broadcastFlow(IntentFilter(Intent.ACTION_WALLPAPER_CHANGED), user = UserHandle.ALL)
// The `combine` defining `wallpaperSupportsAmbientMode` will not run until both of the
// input flows emit at least once. Since this flow is an input flow, it needs to emit
// when it starts up to ensure that the `combine` will run if the user changes before we
@@ -87,6 +104,27 @@ constructor(
// Only update the wallpaper status once the user selection has finished.
.filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
+ /** The bottom of notification stack respect to the top of screen. */
+ private val notificationStackAbsoluteBottom: MutableStateFlow<Float> = MutableStateFlow(0F)
+
+ /** The top of shortcut respect to the top of screen. */
+ private val shortcutAbsoluteTop: StateFlow<Float> = keyguardRepository.shortcutAbsoluteTop
+
+ /**
+ * The top of notification stack to give a default state of lockscreen remaining space for
+ * states with notifications to compare with. It's the bottom of smartspace date and weather
+ * smartspace in small clock state, plus proper bottom margin.
+ */
+ private val notificationStackDefaultTop = keyguardClockRepository.notificationDefaultTop
+ @VisibleForTesting var sendLockscreenLayoutJob: Job? = null
+ private val lockscreenRemainingSpaceWithNotification: Flow<Triple<Float, Float, Float>> =
+ combine(
+ notificationStackAbsoluteBottom,
+ notificationStackDefaultTop,
+ shortcutAbsoluteTop,
+ ::Triple,
+ )
+
override val wallpaperInfo: StateFlow<WallpaperInfo?> =
if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) {
MutableStateFlow(null).asStateFlow()
@@ -116,9 +154,70 @@ constructor(
initialValue = wallpaperInfo.value?.supportsAmbientMode() == true,
)
+ override var rootView: View? = null
+
+ val shouldSendNotificationLayout =
+ wallpaperInfo
+ .map {
+ val shouldSendNotificationLayout = shouldSendNotificationLayout(it)
+ if (shouldSendNotificationLayout) {
+ sendLockscreenLayoutJob =
+ scope.launch {
+ lockscreenRemainingSpaceWithNotification.collect {
+ (notificationBottom, notificationDefaultTop, shortcutTop) ->
+ wallpaperManager.sendWallpaperCommand(
+ /* windowToken = */ rootView?.windowToken,
+ /* action = */ WallpaperManager
+ .COMMAND_LOCKSCREEN_LAYOUT_CHANGED,
+ /* x = */ 0,
+ /* y = */ 0,
+ /* z = */ 0,
+ /* extras = */ Bundle().apply {
+ putFloat("screenLeft", 0F)
+ putFloat("smartspaceBottom", notificationDefaultTop)
+ putFloat("notificationBottom", notificationBottom)
+ putFloat(
+ "screenRight",
+ context.resources.displayMetrics.widthPixels.toFloat(),
+ )
+ putFloat("shortCutTop", shortcutTop)
+ },
+ )
+ }
+ }
+ } else {
+ sendLockscreenLayoutJob?.cancel()
+ }
+ shouldSendNotificationLayout
+ }
+ .stateIn(
+ scope,
+ // Always be listening for wallpaper changes.
+ if (Flags.magicPortraitWallpapers()) SharingStarted.Eagerly
+ else SharingStarted.Lazily,
+ initialValue = false,
+ )
+
+ override fun setNotificationStackAbsoluteBottom(bottom: Float) {
+ notificationStackAbsoluteBottom.value = bottom
+ }
+
private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
return withContext(bgDispatcher) {
wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
}
}
+
+ private fun shouldSendNotificationLayout(wallpaperInfo: WallpaperInfo?): Boolean {
+ return if (wallpaperInfo != null && wallpaperInfo.component != null) {
+ wallpaperInfo.component!!.className == MAGIC_PORTRAIT_CLASSNAME
+ } else {
+ false
+ }
+ }
+
+ companion object {
+ const val MAGIC_PORTRAIT_CLASSNAME =
+ "com.google.android.apps.magicportrait.service.MagicPortraitWallpaperService"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt
new file mode 100644
index 000000000000..79ebf0128d02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 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.wallpapers.domain.interactor
+
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import javax.inject.Inject
+
+class WallpaperInteractor @Inject constructor(val wallpaperRepository: WallpaperRepository) {
+ fun setNotificationStackAbsoluteBottom(bottom: Float) {
+ wallpaperRepository.setNotificationStackAbsoluteBottom(bottom)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 0b944f04a6a2..96a0aadacbc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -39,13 +39,13 @@ import com.android.systemui.keyguard.ui.viewmodel.keyguardSmartspaceViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,6 +57,7 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@@ -122,6 +123,7 @@ class ClockSectionTest : SysuiTestCase() {
{ keyguardBlueprintInteractor },
keyguardRootViewModel,
aodBurnInViewModel,
+ largeScreenHeaderHelperLazy = { mock<LargeScreenHeaderHelper>() },
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 7cd306ead027..6425da46fc67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -83,6 +83,7 @@ import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -103,7 +104,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -113,6 +113,7 @@ import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionCont
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
import org.junit.Before;
import org.junit.Test;
@@ -154,6 +155,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private KeyguardBypassController mKeyguardBypassController;
@Mock private PowerInteractor mPowerInteractor;
@Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @Mock private WallpaperInteractor mWallpaperInteractor;
@Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
@Mock private MetricsLogger mMetricsLogger;
@Mock private ColorUpdateLogger mColorUpdateLogger;
@@ -1070,7 +1072,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mock(NotificationDismissibilityProvider.class),
mActivityStarter,
new ResourcesSplitShadeStateController(),
- mSensitiveNotificationProtectionController);
+ mSensitiveNotificationProtectionController,
+ mWallpaperInteractor);
}
static class LogMatcher implements ArgumentMatcher<LogMaker> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 8a3e5510b561..59fc0d157d54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -102,6 +102,7 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.AvalancheController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
import kotlin.Unit;
@@ -146,6 +147,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Mock private NotificationStackScrollLayoutController mStackScrollLayoutController;
@Mock private ScreenOffAnimationController mScreenOffAnimationController;
@Mock private NotificationShelf mNotificationShelf;
+ @Mock private WallpaperInteractor mWallpaperInteractor;
@Mock private NotificationStackSizeCalculator mStackSizeCalculator;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
@@ -208,6 +210,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
.thenReturn(mNotificationRoundnessManager);
mStackScroller.setController(mStackScrollLayoutController);
mStackScroller.setShelf(mNotificationShelf);
+ mStackScroller.setWallpaperInteractor(mWallpaperInteractor);
when(mStackScroller.getExpandHelper()).thenReturn(mExpandHelper);
doNothing().when(mGroupExpansionManager).collapseGroups();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
index bdecf2bdb53d..b8dd334dcad9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -18,18 +18,21 @@ package com.android.systemui.wallpapers.data.repository
import android.app.WallpaperInfo
import android.app.WallpaperManager
+import android.content.ComponentName
import android.content.Intent
import android.content.pm.UserInfo
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.user.data.model.SelectedUserModel
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.wallpapers.data.repository.WallpaperRepositoryImpl.Companion.MAGIC_PORTRAIT_CLASSNAME
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -39,6 +42,9 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@@ -48,6 +54,8 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
private val userRepository = FakeUserRepository()
+ private val keyguardClockRepository = FakeKeyguardClockRepository()
+ private val keyguardRepository = FakeKeyguardRepository()
private val wallpaperManager: WallpaperManager = mock()
private val underTest: WallpaperRepositoryImpl by lazy {
@@ -56,6 +64,8 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
testDispatcher,
fakeBroadcastDispatcher,
userRepository,
+ keyguardRepository,
+ keyguardClockRepository,
wallpaperManager,
context,
)
@@ -219,7 +229,7 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
context.orCreateTestableResources.addOverride(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
- false
+ false,
)
val latest by collectLastValue(underTest.wallpaperInfo)
@@ -407,7 +417,7 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
context.orCreateTestableResources.addOverride(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
- false
+ false,
)
val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
@@ -425,6 +435,54 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
assertThat(latest).isFalse()
}
+ @Test
+ @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
+ fun shouldSendNotificationLayout_setMagicPortraitWallpaper_launchSendLayoutJob() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.shouldSendNotificationLayout)
+ val magicPortraitWallpaper =
+ mock<WallpaperInfo>().apply {
+ whenever(this.component)
+ .thenReturn(ComponentName(context, MAGIC_PORTRAIT_CLASSNAME))
+ }
+ whenever(wallpaperManager.getWallpaperInfoForUser(any()))
+ .thenReturn(magicPortraitWallpaper)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+ assertThat(latest).isTrue()
+ assertThat(underTest.sendLockscreenLayoutJob).isNotNull()
+ assertThat(underTest.sendLockscreenLayoutJob!!.isActive).isEqualTo(true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
+ fun shouldSendNotificationLayout_setNotMagicPortraitWallpaper_cancelSendLayoutJob() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.shouldSendNotificationLayout)
+ val magicPortraitWallpaper = MAGIC_PORTRAIT_WP
+ whenever(wallpaperManager.getWallpaperInfoForUser(any()))
+ .thenReturn(magicPortraitWallpaper)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+ assertThat(latest).isTrue()
+ assertThat(underTest.sendLockscreenLayoutJob).isNotNull()
+ assertThat(underTest.sendLockscreenLayoutJob!!.isActive).isEqualTo(true)
+
+ val nonMagicPortraitWallpaper = UNSUPPORTED_WP
+ whenever(wallpaperManager.getWallpaperInfoForUser(any()))
+ .thenReturn(nonMagicPortraitWallpaper)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+ assertThat(latest).isFalse()
+ assertThat(underTest.sendLockscreenLayoutJob?.isCancelled).isEqualTo(true)
+ }
+
private companion object {
val USER_WITH_UNSUPPORTED_WP = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
val UNSUPPORTED_WP =
@@ -433,5 +491,10 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
val USER_WITH_SUPPORTED_WP = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
val SUPPORTED_WP =
mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(true) }
+
+ val MAGIC_PORTRAIT_WP =
+ mock<WallpaperInfo>().apply {
+ whenever(this.component).thenReturn(ComponentName("", MAGIC_PORTRAIT_CLASSNAME))
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/WallpaperManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/WallpaperManagerKosmos.kt
new file mode 100644
index 000000000000..2850ab7b1e41
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/app/WallpaperManagerKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.app
+
+import android.app.WallpaperManager
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.wallpaperManager: WallpaperManager by Fixture {
+ WallpaperManager.getInstance(applicationContext)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
index 5e5f8cb1055a..159dd34efbbc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
@@ -46,16 +46,27 @@ class FakeKeyguardClockRepository() : KeyguardClockRepository {
private val _previewClock = MutableStateFlow(Mockito.mock(ClockController::class.java))
override val previewClock: Flow<ClockController>
get() = _previewClock
+
+ private val _notificationDefaultTop = MutableStateFlow(0F)
+ override val notificationDefaultTop: StateFlow<Float>
+ get() = _notificationDefaultTop
+
override val clockEventController: ClockEventController
get() = mock()
+
override val shouldForceSmallClock: Boolean
get() = _shouldForceSmallClock
+
private var _shouldForceSmallClock: Boolean = false
override fun setClockSize(size: ClockSize) {
_clockSize.value = size
}
+ override fun setNotificationDefaultTop(top: Float) {
+ _notificationDefaultTop.value = top
+ }
+
fun setSelectedClockSize(size: ClockSizeSetting) {
_selectedClockSize.value = size
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 54a6c0c1d182..e513e8d2a350 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -131,6 +131,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
private val _isEncryptedOrLockdown = MutableStateFlow(true)
override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown
+ private val _shortcutAbsoluteTop = MutableStateFlow(0F)
+ override val shortcutAbsoluteTop: StateFlow<Float>
+ get() = _shortcutAbsoluteTop.asStateFlow()
+
private val _isKeyguardEnabled = MutableStateFlow(true)
override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow()
@@ -241,7 +245,7 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
override fun setBiometricUnlockState(
mode: BiometricUnlockMode,
- source: BiometricUnlockSource?
+ source: BiometricUnlockSource?,
) {
_biometricUnlockState.tryEmit(BiometricUnlockModel(mode, source))
}
@@ -294,6 +298,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
return isShowKeyguardWhenReenabled
}
+ override fun setShortcutAbsoluteTop(top: Float) {
+ _shortcutAbsoluteTop.value = top
+ }
+
override fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean) {
_canIgnoreAuthAndReturnToGone.value = canWake
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
index 12d7c49194ff..49a8c1866aa2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -29,9 +29,10 @@ import com.android.systemui.keyguard.ui.viewmodel.keyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.keyguardSmartspaceViewModel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.shade.LargeScreenHeaderHelper
import java.util.Optional
import org.mockito.Mockito.spy
+import org.mockito.kotlin.mock
val Kosmos.keyguardClockSection: ClockSection by
Kosmos.Fixture {
@@ -43,6 +44,7 @@ val Kosmos.keyguardClockSection: ClockSection by
blueprintInteractor = { keyguardBlueprintInteractor },
rootViewModel = keyguardRootViewModel,
aodBurnInViewModel = aodBurnInViewModel,
+ largeScreenHeaderHelperLazy = { mock<LargeScreenHeaderHelper>() },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
index d52883eb38af..bdb9abb03c5f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
@@ -27,13 +27,13 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.heads
val Kosmos.keyguardClockInteractor by
Kosmos.Fixture {
KeyguardClockInteractor(
- keyguardClockRepository = keyguardClockRepository,
- applicationScope = applicationCoroutineScope,
mediaCarouselInteractor = mediaCarouselInteractor,
activeNotificationsInteractor = activeNotificationsInteractor,
shadeInteractor = shadeInteractor,
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
headsUpNotificationInteractor = headsUpNotificationInteractor,
+ applicationScope = applicationCoroutineScope,
+ keyguardClockRepository = keyguardClockRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
new file mode 100644
index 000000000000..1d8c891679aa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.wallpapers.data.repository
+
+import android.content.applicationContext
+import com.android.app.wallpaperManager
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.keyguard.data.repository.keyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.user.data.repository.userRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.wallpaperRepository by Fixture {
+ WallpaperRepositoryImpl(
+ context = applicationContext,
+ scope = testScope,
+ bgDispatcher = testDispatcher,
+ broadcastDispatcher = broadcastDispatcher,
+ userRepository = userRepository,
+ wallpaperManager = wallpaperManager,
+ keyguardClockRepository = keyguardClockRepository,
+ keyguardRepository = keyguardRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractorKosmos.kt
new file mode 100644
index 000000000000..5278351520f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.wallpapers.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.wallpapers.data.repository.wallpaperRepository
+
+val Kosmos.wallpaperInteractor by
+ Kosmos.Fixture { WallpaperInteractor(wallpaperRepository = wallpaperRepository) }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 596e375b439d..e0cf96fbccd0 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -72,9 +72,6 @@ import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
import static android.permission.flags.Flags.deviceAwareAppOpNewSchemaEnabled;
-import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION;
-import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION;
-import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
import android.Manifest;
@@ -163,7 +160,6 @@ import com.android.internal.os.Clock;
import com.android.internal.pm.pkg.component.ParsedAttribution;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -2833,26 +2829,12 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public int checkOperation(int code, int uid, String packageName) {
- if (Flags.appopAccessTrackingLoggingEnabled()) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
- uid, code,
- APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
- false);
- }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
Context.DEVICE_ID_DEFAULT, false /*raw*/);
}
@Override
public int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId) {
- if (Flags.appopAccessTrackingLoggingEnabled()) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
- uid, code,
- APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
- false);
- }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
virtualDeviceId, false /*raw*/);
}
@@ -3033,14 +3015,6 @@ public class AppOpsService extends IAppOpsService.Stub {
public SyncNotedAppOp noteProxyOperationWithState(int code,
AttributionSourceState attributionSourceState, boolean shouldCollectAsyncNotedOp,
String message, boolean shouldCollectMessage, boolean skipProxyOperation) {
- if (Flags.appopAccessTrackingLoggingEnabled()) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
- attributionSourceState.uid, code,
- APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION,
- attributionSourceState.attributionTag != null);
- }
-
AttributionSource attributionSource = new AttributionSource(attributionSourceState);
return mCheckOpsDelegateDispatcher.noteProxyOperation(code, attributionSource,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
@@ -3122,14 +3096,6 @@ public class AppOpsService extends IAppOpsService.Stub {
public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage) {
- if (Flags.appopAccessTrackingLoggingEnabled()) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
- uid, code,
- APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION,
- attributionTag != null);
- }
-
return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
shouldCollectMessage);
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index eeec36919480..99ced7f8bb00 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -212,6 +212,10 @@ public class DisplayManagerFlags {
Flags.FLAG_BLOCK_AUTOBRIGHTNESS_CHANGES_ON_STYLUS_USAGE,
Flags::blockAutobrightnessChangesOnStylusUsage
);
+ private final FlagState mIsUserRefreshRateForExternalDisplayEnabled = new FlagState(
+ Flags.FLAG_ENABLE_USER_REFRESH_RATE_FOR_EXTERNAL_DISPLAY,
+ Flags::enableUserRefreshRateForExternalDisplay
+ );
private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState(
Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS,
@@ -456,6 +460,14 @@ public class DisplayManagerFlags {
}
/**
+ * @return {@code true} if need to use user refresh rate settings for
+ * external displays.
+ */
+ public boolean isUserRefreshRateForExternalDisplayEnabled() {
+ return mIsUserRefreshRateForExternalDisplayEnabled.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -501,6 +513,7 @@ public class DisplayManagerFlags {
pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
pw.println(" " + mEnableBatteryStatsForAllDisplays);
pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage);
+ pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 20ecd2d8303e..2f04d9e5fdbb 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -381,3 +381,14 @@ flag {
bug: "352411468"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_user_refresh_rate_for_external_display"
+ namespace: "display_manager"
+ description: "Apply refresh rate from user preferred display mode to external displays"
+ bug: "370657357"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 18e0d6ee5ea3..ffa64bfcf29f 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1194,6 +1194,13 @@ public class DisplayModeDirector {
@GuardedBy("mLock")
private void updateRefreshRateSettingLocked(float minRefreshRate, float peakRefreshRate,
float defaultRefreshRate, int displayId) {
+ if (mDisplayObserver.isExternalDisplayLocked(displayId)) {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "skip updateRefreshRateSettingLocked for external display "
+ + displayId);
+ }
+ return;
+ }
// TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is
// used to predict if we're going to be doing frequent refresh rate switching, and if
// so, enable the brightness observer. The logic here is more complicated and fragile
@@ -1243,6 +1250,8 @@ public class DisplayModeDirector {
}
private void removeRefreshRateSetting(int displayId) {
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
+ null);
mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
null);
mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
@@ -1458,11 +1467,11 @@ public class DisplayModeDirector {
public void onDisplayAdded(int displayId) {
updateDisplayDeviceConfig(displayId);
DisplayInfo displayInfo = getDisplayInfo(displayId);
+ registerExternalDisplay(displayInfo);
updateDisplayModes(displayId, displayInfo);
updateLayoutLimitedFrameRate(displayId, displayInfo);
updateUserSettingDisplayPreferredSize(displayInfo);
updateDisplaysPeakRefreshRateAndResolution(displayInfo);
- addDisplaysSynchronizedPeakRefreshRate(displayInfo);
}
@Override
@@ -1477,7 +1486,7 @@ public class DisplayModeDirector {
updateLayoutLimitedFrameRate(displayId, null);
removeUserSettingDisplayPreferredSize(displayId);
removeDisplaysPeakRefreshRateAndResolution(displayId);
- removeDisplaysSynchronizedPeakRefreshRate(displayId);
+ unregisterExternalDisplay(displayId);
}
@Override
@@ -1489,6 +1498,30 @@ public class DisplayModeDirector {
updateUserSettingDisplayPreferredSize(displayInfo);
}
+ private void registerExternalDisplay(DisplayInfo displayInfo) {
+ if (displayInfo == null || displayInfo.type != Display.TYPE_EXTERNAL) {
+ return;
+ }
+ synchronized (mLock) {
+ mExternalDisplaysConnected.add(displayInfo.displayId);
+ if (mExternalDisplaysConnected.size() == 1) {
+ addDisplaysSynchronizedPeakRefreshRate();
+ }
+ }
+ }
+
+ private void unregisterExternalDisplay(int displayId) {
+ synchronized (mLock) {
+ if (!isExternalDisplayLocked(displayId)) {
+ return;
+ }
+ mExternalDisplaysConnected.remove(displayId);
+ if (mExternalDisplaysConnected.isEmpty()) {
+ removeDisplaysSynchronizedPeakRefreshRate();
+ }
+ }
+ }
+
boolean isExternalDisplayLocked(int displayId) {
return mExternalDisplaysConnected.contains(displayId);
}
@@ -1534,10 +1567,24 @@ public class DisplayModeDirector {
return;
}
- mVotesStorage.updateVote(info.displayId,
- Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE,
- Vote.forSize(/* width */ preferredMode.getPhysicalWidth(),
- /* height */ preferredMode.getPhysicalHeight()));
+ if (info.type == Display.TYPE_EXTERNAL
+ && mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()
+ && !isRefreshRateSynchronizationEnabled()) {
+ mVotesStorage.updateVote(info.displayId,
+ Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE,
+ Vote.forSizeAndPhysicalRefreshRatesRange(
+ /* minWidth */ preferredMode.getPhysicalWidth(),
+ /* minHeight */ preferredMode.getPhysicalHeight(),
+ /* width */ preferredMode.getPhysicalWidth(),
+ /* height */ preferredMode.getPhysicalHeight(),
+ /* minRefreshRate */ preferredMode.getRefreshRate(),
+ /* maxRefreshRate */ preferredMode.getRefreshRate()));
+ } else {
+ mVotesStorage.updateVote(info.displayId,
+ Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE,
+ Vote.forSize(/* width */ preferredMode.getPhysicalWidth(),
+ /* height */ preferredMode.getPhysicalHeight()));
+ }
}
@Nullable
@@ -1584,17 +1631,10 @@ public class DisplayModeDirector {
* Sets 60Hz target refresh rate as the vote with
* {@link Vote#PRIORITY_SYNCHRONIZED_REFRESH_RATE} priority.
*/
- private void addDisplaysSynchronizedPeakRefreshRate(@Nullable final DisplayInfo info) {
- if (info == null || info.type != Display.TYPE_EXTERNAL
- || !isRefreshRateSynchronizationEnabled()) {
+ private void addDisplaysSynchronizedPeakRefreshRate() {
+ if (!isRefreshRateSynchronizationEnabled()) {
return;
}
- synchronized (mLock) {
- mExternalDisplaysConnected.add(info.displayId);
- if (mExternalDisplaysConnected.size() != 1) {
- return;
- }
- }
// set minRefreshRate as the max refresh rate.
mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE,
Vote.forPhysicalRefreshRates(
@@ -1610,19 +1650,10 @@ public class DisplayModeDirector {
+ SYNCHRONIZED_REFRESH_RATE_TOLERANCE));
}
- private void removeDisplaysSynchronizedPeakRefreshRate(final int displayId) {
+ private void removeDisplaysSynchronizedPeakRefreshRate() {
if (!isRefreshRateSynchronizationEnabled()) {
return;
}
- synchronized (mLock) {
- if (!isExternalDisplayLocked(displayId)) {
- return;
- }
- mExternalDisplaysConnected.remove(displayId);
- if (!mExternalDisplaysConnected.isEmpty()) {
- return;
- }
- }
mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null);
mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE, null);
}
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 459f9a6e8f13..f5abb0561ce7 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -44,7 +44,7 @@ interface Vote {
// It votes [minRefreshRate, Float.POSITIVE_INFINITY]
int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3;
- // User setting preferred display resolution.
+ // User setting preferred display resolution, for external displays also includes refresh rate.
int PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE = 4;
// APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index cd0a2a7a314a..03fc60cad8d6 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -106,8 +106,7 @@ abstract public class ManagedServices {
protected final String TAG = getClass().getSimpleName().replace('$', '.');
protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- protected static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
- protected static final int ON_BINDING_DIED_REBIND_MSG = 1234;
+ private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
protected static final String ENABLED_SERVICES_SEPARATOR = ":";
private static final String DB_VERSION_1 = "1";
private static final String DB_VERSION_2 = "2";
@@ -857,13 +856,7 @@ abstract public class ManagedServices {
String approvedItem = getApprovedValue(pkgOrComponent);
if (approvedItem != null) {
- final ComponentName component = ComponentName.unflattenFromString(approvedItem);
if (enabled) {
- if (component != null && !isValidService(component, userId)) {
- Log.e(TAG, "Skip allowing " + mConfig.caption + " " + pkgOrComponent
- + " (userSet: " + userSet + ") for invalid service");
- return;
- }
approved.add(approvedItem);
} else {
approved.remove(approvedItem);
@@ -961,7 +954,7 @@ abstract public class ManagedServices {
|| isPackageOrComponentAllowed(component.getPackageName(), userId))) {
return false;
}
- return isValidService(component, userId);
+ return componentHasBindPermission(component, userId);
}
private boolean componentHasBindPermission(ComponentName component, int userId) {
@@ -1313,12 +1306,11 @@ abstract public class ManagedServices {
if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
final ComponentName component = ComponentName.unflattenFromString(
approvedPackageOrComponent);
- if (component != null && !isValidService(component, userId)) {
+ if (component != null && !componentHasBindPermission(component, userId)) {
approved.removeAt(j);
if (DEBUG) {
Slog.v(TAG, "Removing " + approvedPackageOrComponent
- + " from approved list; no bind permission or "
- + "service interface filter found "
+ + " from approved list; no bind permission found "
+ mConfig.bindPermission);
}
}
@@ -1337,11 +1329,6 @@ abstract public class ManagedServices {
}
}
- protected boolean isValidService(ComponentName component, int userId) {
- return componentHasBindPermission(component, userId) && queryPackageForServices(
- component.getPackageName(), userId).contains(component);
- }
-
protected boolean isValidEntry(String packageOrComponent, int userId) {
return hasMatchingServices(packageOrComponent, userId);
}
@@ -1499,25 +1486,23 @@ abstract public class ManagedServices {
* Called when user switched to unbind all services from other users.
*/
@VisibleForTesting
- void unbindOtherUserServices(int switchedToUser) {
+ void unbindOtherUserServices(int currentUser) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("ManagedServices.unbindOtherUserServices_current" + switchedToUser);
- unbindServicesImpl(switchedToUser, true /* allExceptUser */);
+ t.traceBegin("ManagedServices.unbindOtherUserServices_current" + currentUser);
+ unbindServicesImpl(currentUser, true /* allExceptUser */);
t.traceEnd();
}
- void unbindUserServices(int removedUser) {
+ void unbindUserServices(int user) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("ManagedServices.unbindUserServices" + removedUser);
- unbindServicesImpl(removedUser, false /* allExceptUser */);
+ t.traceBegin("ManagedServices.unbindUserServices" + user);
+ unbindServicesImpl(user, false /* allExceptUser */);
t.traceEnd();
}
void unbindServicesImpl(int user, boolean allExceptUser) {
final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
synchronized (mMutex) {
- // Remove enqueued rebinds to avoid rebinding services for a switched user
- mHandler.removeMessages(ON_BINDING_DIED_REBIND_MSG);
final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices();
for (ManagedServiceInfo info : removableBoundServices) {
if ((allExceptUser && (info.userid != user))
@@ -1712,7 +1697,6 @@ abstract public class ManagedServices {
mServicesRebinding.add(servicesBindingTag);
mHandler.postDelayed(() ->
reregisterService(name, userid),
- ON_BINDING_DIED_REBIND_MSG,
ON_BINDING_DIED_REBIND_DELAY_MS);
} else {
Slog.v(TAG, getCaption() + " not rebinding in user " + userid
diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
index fd60e06c0762..db57d11a75c0 100644
--- a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -47,6 +47,9 @@ public class PowerManagerFlags {
Flags::perDisplayWakeByTouch
);
+ private final FlagState mFrameworkWakelockInfo =
+ new FlagState(Flags.FLAG_FRAMEWORK_WAKELOCK_INFO, Flags::frameworkWakelockInfo);
+
/** Returns whether early-screen-timeout-detector is enabled on not. */
public boolean isEarlyScreenTimeoutDetectorEnabled() {
return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
@@ -67,6 +70,13 @@ public class PowerManagerFlags {
}
/**
+ * @return Whether FrameworkWakelockInfo atom logging is enabled or not.
+ */
+ public boolean isFrameworkWakelockInfoEnabled() {
+ return mFrameworkWakelockInfo.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -75,6 +85,7 @@ public class PowerManagerFlags {
pw.println(" " + mEarlyScreenTimeoutDetectorFlagState);
pw.println(" " + mImproveWakelockLatency);
pw.println(" " + mPerDisplayWakeByTouch);
+ pw.println(" " + mFrameworkWakelockInfo);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index 9cf3bb6df3db..8bb69ba0c5de 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -26,3 +26,10 @@ flag {
bug: "343295183"
is_fixed_read_only: true
}
+
+flag {
+ name: "framework_wakelock_info"
+ namespace: "power"
+ description: "Feature flag to enable statsd pulling of FrameworkWakelockInfo atoms"
+ bug: "352602149"
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 936fadf0b12a..391071ff9fe8 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -145,6 +145,7 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
+import com.android.server.power.feature.PowerManagerFlags;
import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.power.stats.format.MobileRadioPowerStatsLayout;
@@ -5142,6 +5143,10 @@ public class BatteryStatsImpl extends BatteryStats {
mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
uidStats.mProcessState, true /* acquired */,
getPowerManagerWakeLockLevel(type));
+ if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) {
+ mFrameworkEvents.noteStartWakeLock(
+ mapIsolatedUid(uid), name, getPowerManagerWakeLockLevel(type), uptimeMs);
+ }
}
}
@@ -5187,6 +5192,10 @@ public class BatteryStatsImpl extends BatteryStats {
mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
uidStats.mProcessState, false/* acquired */,
getPowerManagerWakeLockLevel(type));
+ if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) {
+ mFrameworkEvents.noteStopWakeLock(
+ mapIsolatedUid(uid), name, getPowerManagerWakeLockLevel(type), uptimeMs);
+ }
if (mappedUid != uid) {
// Decrement the ref count for the isolated uid and delete the mapping if uneeded.
@@ -11343,6 +11352,9 @@ public class BatteryStatsImpl extends BatteryStats {
return mTmpCpuTimeInFreq;
}
+ WakelockStatsFrameworkEvents mFrameworkEvents = new WakelockStatsFrameworkEvents();
+ PowerManagerFlags mPowerManagerFlags = new PowerManagerFlags();
+
public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
@NonNull MonotonicClock monotonicClock, @Nullable File systemDir,
@NonNull Handler handler, @Nullable PlatformIdleStateCallback platformIdleStateCallback,
@@ -15964,6 +15976,10 @@ public class BatteryStatsImpl extends BatteryStats {
// Already plugged in. Schedule the long plug in alarm.
scheduleNextResetWhilePluggedInCheck();
}
+
+ if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) {
+ mFrameworkEvents.initialize(context);
+ }
}
}
@@ -16791,7 +16807,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
int NSORPMS = in.readInt();
if (NSORPMS > 10000) {
- throw new ParcelFormatException("File corrupt: too many screen-off rpm stats " + NSORPMS);
+ throw new ParcelFormatException(
+ "File corrupt: too many screen-off rpm stats " + NSORPMS);
}
for (int irpm = 0; irpm < NSORPMS; irpm++) {
if (in.readInt() != 0) {
diff --git a/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java b/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java
new file mode 100644
index 000000000000..c9693bd20a08
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2024 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.power.stats;
+
+import android.app.StatsManager;
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.StatsEvent;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ConcurrentUtils;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/** A class to initialise and log metrics pulled by statsd. */
+public class WakelockStatsFrameworkEvents {
+ // statsd has a dimensional limit on the number of different keys it can handle.
+ // Beyond that limit, statsd will drop data.
+ //
+ // When we have seem SUMMARY_THRESHOLD distinct (uid, tag, wakeLockLevel) keys,
+ // we start summarizing new keys as (uid, OVERFLOW_TAG, OVERFLOW_LEVEL) to
+ // reduce the number of keys we pass to statsd.
+ //
+ // When we reach MAX_WAKELOCK_DIMENSIONS distinct keys, we summarize all new keys
+ // as (OVERFLOW_UID, HARD_CAP_TAG, OVERFLOW_LEVEL) to hard cap the number of
+ // distinct keys we pass to statsd.
+ @VisibleForTesting public static final int SUMMARY_THRESHOLD = 500;
+ @VisibleForTesting public static final int MAX_WAKELOCK_DIMENSIONS = 1000;
+
+ @VisibleForTesting public static final int HARD_CAP_UID = -1;
+ @VisibleForTesting public static final String OVERFLOW_TAG = "*overflow*";
+ @VisibleForTesting public static final String HARD_CAP_TAG = "*overflow hard cap*";
+ @VisibleForTesting public static final int OVERFLOW_LEVEL = 1;
+
+ private static class WakeLockKey {
+ private int uid;
+ private String tag;
+ private int powerManagerWakeLockLevel;
+ private int hashCode;
+
+ WakeLockKey(int uid, String tag, int powerManagerWakeLockLevel) {
+ this.uid = uid;
+ this.tag = new String(tag);
+ this.powerManagerWakeLockLevel = powerManagerWakeLockLevel;
+
+ this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
+ }
+
+ int getUid() {
+ return uid;
+ }
+
+ String getTag() {
+ return tag;
+ }
+
+ int getPowerManagerWakeLockLevel() {
+ return powerManagerWakeLockLevel;
+ }
+
+ void setOverflow() {
+ tag = OVERFLOW_TAG;
+ powerManagerWakeLockLevel = OVERFLOW_LEVEL;
+ this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
+ }
+
+ void setHardCap() {
+ uid = HARD_CAP_UID;
+ tag = HARD_CAP_TAG;
+ powerManagerWakeLockLevel = OVERFLOW_LEVEL;
+ this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof WakeLockKey)) return false;
+
+ WakeLockKey that = (WakeLockKey) o;
+ return uid == that.uid
+ && tag.equals(that.tag)
+ && powerManagerWakeLockLevel == that.powerManagerWakeLockLevel;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.hashCode;
+ }
+ }
+
+ private static class WakeLockStats {
+ // accumulated uptime attributed to this WakeLock since boot, where overlap
+ // (including nesting) is ignored
+ public long uptimeMillis = 0;
+
+ // count of WakeLocks that have been acquired and then released
+ public long completedCount = 0;
+ }
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final Map<WakeLockKey, WakeLockStats> mWakeLockStats = new HashMap<>();
+
+ private static class WakeLockData {
+ // uptime millis when first acquired
+ public long acquireUptimeMillis = 0;
+ public int refCount = 0;
+
+ WakeLockData(long uptimeMillis) {
+ acquireUptimeMillis = uptimeMillis;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private final Map<WakeLockKey, WakeLockData> mOpenWakeLocks = new HashMap<>();
+
+ public void noteStartWakeLock(
+ int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) {
+ final WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel);
+
+ synchronized (mLock) {
+ WakeLockData data =
+ mOpenWakeLocks.computeIfAbsent(key, k -> new WakeLockData(eventUptimeMillis));
+ data.refCount++;
+ mOpenWakeLocks.put(key, data);
+ }
+ }
+
+ @VisibleForTesting
+ public boolean inOverflow() {
+ synchronized (mLock) {
+ return inOverflowLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean inOverflowLocked() {
+ return mWakeLockStats.size() >= SUMMARY_THRESHOLD;
+ }
+
+ @VisibleForTesting
+ public boolean inHardCap() {
+ synchronized (mLock) {
+ return inHardCapLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean inHardCapLocked() {
+ return mWakeLockStats.size() >= MAX_WAKELOCK_DIMENSIONS;
+ }
+
+ public void noteStopWakeLock(
+ int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) {
+ WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel);
+
+ synchronized (mLock) {
+ WakeLockData data = mOpenWakeLocks.get(key);
+ if (data == null) {
+ Log.e(TAG, "WakeLock not found when stopping: " + uid + " " + tag);
+ return;
+ }
+
+ if (data.refCount == 1) {
+ mOpenWakeLocks.remove(key);
+ long wakeLockDur = eventUptimeMillis - data.acquireUptimeMillis;
+
+ // Rewrite key if in an overflow state.
+ if (inOverflowLocked() && !mWakeLockStats.containsKey(key)) {
+ key.setOverflow();
+ if (inHardCapLocked() && !mWakeLockStats.containsKey(key)) {
+ key.setHardCap();
+ }
+ }
+
+ WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats());
+ stats.uptimeMillis += wakeLockDur;
+ stats.completedCount++;
+ mWakeLockStats.put(key, stats);
+ } else {
+ data.refCount--;
+ mOpenWakeLocks.put(key, data);
+ }
+ }
+ }
+
+ public List<StatsEvent> pullFrameworkWakelockInfoAtoms() {
+ return pullFrameworkWakelockInfoAtoms(SystemClock.uptimeMillis());
+ }
+
+ public List<StatsEvent> pullFrameworkWakelockInfoAtoms(long nowMillis) {
+ List<StatsEvent> result = new ArrayList<>();
+ HashSet<WakeLockKey> keys = new HashSet<>();
+
+ // Used to collect open WakeLocks when in an overflow state.
+ HashMap<WakeLockKey, WakeLockStats> openOverflowStats = new HashMap<>();
+
+ synchronized (mLock) {
+ keys.addAll(mWakeLockStats.keySet());
+
+ // If we are in an overflow state, an open wakelock may have a new key
+ // that needs to be summarized.
+ if (inOverflowLocked()) {
+ for (WakeLockKey key : mOpenWakeLocks.keySet()) {
+ if (!mWakeLockStats.containsKey(key)) {
+ WakeLockData data = mOpenWakeLocks.get(key);
+
+ key.setOverflow();
+ if (inHardCapLocked() && !mWakeLockStats.containsKey(key)) {
+ key.setHardCap();
+ }
+ keys.add(key);
+
+ WakeLockStats stats =
+ openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats());
+ stats.uptimeMillis += nowMillis - data.acquireUptimeMillis;
+ openOverflowStats.put(key, stats);
+ }
+ }
+ } else {
+ keys.addAll(mOpenWakeLocks.keySet());
+ }
+
+ for (WakeLockKey key : keys) {
+ long openWakeLockUptime = 0;
+ WakeLockData data = mOpenWakeLocks.get(key);
+ if (data != null) {
+ openWakeLockUptime = nowMillis - data.acquireUptimeMillis;
+ }
+
+ WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats());
+ WakeLockStats extraTime =
+ openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats());
+
+ stats.uptimeMillis += openWakeLockUptime + extraTime.uptimeMillis;
+
+ StatsEvent event =
+ StatsEvent.newBuilder()
+ .setAtomId(FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO)
+ .writeInt(key.getUid())
+ .writeString(key.getTag())
+ .writeInt(key.getPowerManagerWakeLockLevel())
+ .writeLong(stats.uptimeMillis)
+ .writeLong(stats.completedCount)
+ .build();
+ result.add(event);
+ }
+ }
+
+ return result;
+ }
+
+ private static final String TAG = "BatteryStatsPulledMetrics";
+
+ private final StatsPullCallbackHandler mStatsPullCallbackHandler =
+ new StatsPullCallbackHandler();
+
+ private boolean mIsInitialized = false;
+
+ public void initialize(Context context) {
+ if (mIsInitialized) {
+ return;
+ }
+
+ final StatsManager statsManager = context.getSystemService(StatsManager.class);
+ if (statsManager == null) {
+ Log.e(
+ TAG,
+ "Error retrieving StatsManager. Cannot initialize BatteryStatsPulledMetrics.");
+ } else {
+ Log.d(TAG, "Registering callback with StatsManager");
+
+ // DIRECT_EXECUTOR means that callback will run on binder thread.
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO,
+ null /* metadata */,
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ mStatsPullCallbackHandler);
+ mIsInitialized = true;
+ }
+ }
+
+ private class StatsPullCallbackHandler implements StatsManager.StatsPullAtomCallback {
+ @Override
+ public int onPullAtom(int atomTag, List<StatsEvent> data) {
+ // handle the tags appropriately.
+ List<StatsEvent> events = pullEvents(atomTag);
+ if (events == null) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ data.addAll(events);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private List<StatsEvent> pullEvents(int atomTag) {
+ switch (atomTag) {
+ case FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO:
+ return pullFrameworkWakelockInfoAtoms();
+ default:
+ return null;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index 1a8f5fc46827..fcf88d395f1c 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -36,7 +36,7 @@ import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.Size;
import android.view.Gravity;
-import android.window.flags.DesktopModeFlags;
+import android.window.DesktopModeFlags;
import java.util.function.Consumer;
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index b5ea0bdfc27a..6bf1c466aeb5 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -19,7 +19,7 @@ package com.android.server.wm;
import android.annotation.NonNull;
import android.content.Context;
import android.os.SystemProperties;
-import android.window.flags.DesktopModeFlags;
+import android.window.DesktopModeFlags;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index af57c8422881..95cf6bc3b771 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -36,7 +36,7 @@ import static android.view.WindowInsets.Type.InsetsType;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
-import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
+import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index d91f154c1b87..58f0ab4411bc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1872,6 +1872,60 @@ public class DisplayModeDirectorTest {
}
@Test
+ public void testPeakRefreshRate_notAppliedToExternalDisplays() {
+ when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+ .thenReturn(true);
+ mInjector.mDisplayInfo.type = Display.TYPE_EXTERNAL;
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+ director.getDisplayObserver().onDisplayAdded(DISPLAY_ID);
+ director.getDisplayObserver().onDisplayAdded(DISPLAY_ID_2);
+
+ Display.Mode[] modes1 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 130),
+ };
+ Display.Mode[] modes2 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 140),
+ };
+ SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+ supportedModesByDisplay.put(DISPLAY_ID, modes1);
+ supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
+ Sensor lightSensor = createLightSensor();
+ SensorManager sensorManager = createMockSensorManager(lightSensor);
+ director.start(sensorManager);
+ director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+ // Disable Smooth Display
+ setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+ Vote vote1 = director.getVote(DISPLAY_ID,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ Vote vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertThat(vote1).isNull();
+ assertThat(vote2).isNull();
+
+ // Enable Smooth Display
+ setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+ vote1 = director.getVote(DISPLAY_ID,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertThat(vote1).isNull();
+ assertThat(vote2).isNull();
+ }
+
+ @Test
public void testPeakRefreshRate_DisplayChanged() {
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
@@ -1968,8 +2022,9 @@ public class DisplayModeDirectorTest {
@Test
@Parameters({
"true, true, 60",
- "false, true, 50",
- "true, false, 50"
+ "false, true, 60",
+ "true, false, 50",
+ "false, false, 50"
})
public void testExternalDisplayMaxRefreshRate(boolean isRefreshRateSynchronizationEnabled,
boolean isExternalDisplay, float expectedMaxRenderFrameRate) {
@@ -3810,6 +3865,7 @@ public class DisplayModeDirectorTest {
SensorManagerInternal sensorManagerInternal) {
mDeviceConfig = new FakeDeviceConfig();
mDisplayInfo = new DisplayInfo();
+ mDisplayInfo.type = Display.TYPE_INTERNAL;
mDisplayInfo.defaultModeId = MODE_ID;
mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
800, 600, /* refreshRate= */ 60)};
@@ -3856,6 +3912,7 @@ public class DisplayModeDirectorTest {
@Override
public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
displayInfo.copyFrom(mDisplayInfo);
+ displayInfo.displayId = displayId;
return mDisplayInfoValid;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index 5e240cf66674..e3f150e9fbc1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -189,6 +189,7 @@ public class DisplayObserverTest {
@Test
public void testExternalDisplay_voteUserPreferredMode() {
when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(false);
var preferredMode = mExternalDisplayModes[5];
mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
var expectedVote = Vote.forSize(
@@ -229,6 +230,108 @@ public class DisplayObserverTest {
.isEqualTo(null);
}
+ /** Vote for user preferred mode */
+ @Test
+ public void testDefaultDisplay_voteUserPreferredMode() {
+ when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(true);
+ var preferredMode = mInternalDisplayModes[5];
+ mInternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ var expectedVote = Vote.forSize(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight());
+ init();
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+ mObserver.onDisplayAdded(DEFAULT_DISPLAY);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+
+ mInternalDisplayUserPreferredModeId = INVALID_MODE_ID;
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ mObserver.onDisplayChanged(DEFAULT_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+
+ preferredMode = mInternalDisplayModes[4];
+ mInternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ expectedVote = Vote.forSize(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight());
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ mObserver.onDisplayChanged(DEFAULT_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+
+ // Testing that the vote is removed.
+ mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ }
+
+ /** Vote for user preferred mode with refresh rate, synchronization vote must be disabled */
+ @Test
+ public void testExternalDisplay_voteUserPreferredMode_withRefreshRate() {
+ when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(false);
+ when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(true);
+ var preferredMode = mExternalDisplayModes[5];
+ mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ var expectedVote = Vote.forSizeAndPhysicalRefreshRatesRange(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getRefreshRate(),
+ preferredMode.getRefreshRate());
+ init();
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+
+ mExternalDisplayUserPreferredModeId = INVALID_MODE_ID;
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+
+ preferredMode = mExternalDisplayModes[4];
+ mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ expectedVote = Vote.forSizeAndPhysicalRefreshRatesRange(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getRefreshRate(),
+ preferredMode.getRefreshRate());
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+
+ // Testing that the vote is removed.
+ mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ }
+
/** External display: Do not apply limit to user preferred mode */
@Test
public void testExternalDisplay_doNotApplyLimitToUserPreferredMode() {
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index d6ca10a23fb9..fab20315913d 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -27,6 +27,9 @@ android_test {
"servicestests-utils",
"platform-test-annotations",
"flag-junit",
+ "statsdprotolite",
+ "StatsdTestUtils",
+ "platformprotoslite",
],
libs: [
@@ -68,6 +71,9 @@ android_ravenwood_test {
"androidx.test.uiautomator_uiautomator",
"modules-utils-binary-xml",
"flag-junit",
+ "statsdprotolite",
+ "StatsdTestUtils",
+ "platformprotoslite",
],
srcs: [
"src/com/android/server/power/stats/*.java",
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java
new file mode 100644
index 000000000000..cb644dbe8c96
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2024 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.power.stats;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.WakeLockLevelEnum;
+import android.util.StatsEvent;
+import android.util.StatsEventTestUtils;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.os.AtomsProto;
+import com.android.os.framework.FrameworkExtensionAtoms;
+import com.android.os.framework.FrameworkExtensionAtoms.FrameworkWakelockInfo;
+
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.ExtensionRegistryLite;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WakelockStatsFrameworkEventsTest {
+ private WakelockStatsFrameworkEvents mEvents;
+ private ExtensionRegistryLite mRegistry;
+
+ @Before
+ public void setup() {
+ mEvents = new WakelockStatsFrameworkEvents();
+ mRegistry = ExtensionRegistryLite.newInstance();
+ FrameworkExtensionAtoms.registerAllExtensions(mRegistry);
+ }
+
+ private static final int UID_1 = 1;
+ private static final int UID_2 = 2;
+
+ private static final String TAG_1 = "TAG1";
+ private static final String TAG_2 = "TAG2";
+
+ private static final WakeLockLevelEnum WAKELOCK_TYPE_1 = WakeLockLevelEnum.PARTIAL_WAKE_LOCK;
+ private static final WakeLockLevelEnum WAKELOCK_TYPE_2 = WakeLockLevelEnum.DOZE_WAKE_LOCK;
+
+ private static final long TS_1 = 1000;
+ private static final long TS_2 = 2000;
+ private static final long TS_3 = 3000;
+ private static final long TS_4 = 4000;
+ private static final long TS_5 = 5000;
+
+ // Assumes that mEvents is empty.
+ private void makeMetricsAlmostOverflow() throws Exception {
+ for (int i = 0; i < mEvents.SUMMARY_THRESHOLD - 1; i++) {
+ String tag = "forceOverflow" + i;
+ mEvents.noteStartWakeLock(UID_1, tag, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(UID_1, tag, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ }
+
+ assertFalse("not overflow", mEvents.inOverflow());
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo notOverflowInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.OVERFLOW_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("not overflow", notOverflowInfo, null);
+
+ // Add one more to hit an overflow state.
+ String lastTag = "forceOverflowLast";
+ mEvents.noteStartWakeLock(UID_1, lastTag, WAKELOCK_TYPE_2.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(UID_1, lastTag, WAKELOCK_TYPE_2.getNumber(), TS_2);
+
+ assertTrue("overflow", mEvents.inOverflow());
+ info = pullResults(TS_4);
+
+ FrameworkWakelockInfo tag1Info =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(lastTag))
+ .findFirst()
+ .orElse(null);
+
+ assertTrue("lastTag found", tag1Info != null);
+ assertEquals("uid", UID_1, tag1Info.getAttributionUid());
+ assertEquals("tag", lastTag, tag1Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_2, tag1Info.getType());
+ assertEquals("duration", TS_2 - TS_1, tag1Info.getUptimeMillis());
+ assertEquals("count", 1, tag1Info.getCompletedCount());
+ }
+
+ // Assumes that mEvents is empty.
+ private void makeMetricsAlmostHardCap() throws Exception {
+ for (int i = 0; i < mEvents.MAX_WAKELOCK_DIMENSIONS - 1; i++) {
+ mEvents.noteStartWakeLock(i /* uid */, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(i /* uid */, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ }
+
+ assertFalse("not hard capped", mEvents.inHardCap());
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo notOverflowInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.HARD_CAP_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("not overflow", notOverflowInfo, null);
+
+ // Add one more to hit an hardcap state.
+ int hardCapUid = mEvents.MAX_WAKELOCK_DIMENSIONS;
+ mEvents.noteStartWakeLock(hardCapUid, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(hardCapUid, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_2);
+
+ assertTrue("hard capped", mEvents.inHardCap());
+ info = pullResults(TS_4);
+
+ FrameworkWakelockInfo tag2Info =
+ info.stream()
+ .filter(i -> i.getAttributionUid() == hardCapUid)
+ .findFirst()
+ .orElse(null);
+
+ assertTrue("hardCapUid found", tag2Info != null);
+ assertEquals("uid", hardCapUid, tag2Info.getAttributionUid());
+ assertEquals("tag", mEvents.OVERFLOW_TAG, tag2Info.getAttributionTag());
+ assertEquals(
+ "type", WakeLockLevelEnum.forNumber(mEvents.OVERFLOW_LEVEL), tag2Info.getType());
+ assertEquals("duration", TS_2 - TS_1, tag2Info.getUptimeMillis());
+ assertEquals("count", 1, tag2Info.getCompletedCount());
+ }
+
+ private ArrayList<FrameworkWakelockInfo> pullResults(long timestamp) throws Exception {
+ ArrayList<FrameworkWakelockInfo> result = new ArrayList<>();
+ List<StatsEvent> events = mEvents.pullFrameworkWakelockInfoAtoms(timestamp);
+
+ for (StatsEvent e : events) {
+ // The returned atom does not have external extensions registered.
+ // So we serialize and then deserialize with extensions registered.
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(e);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ CodedOutputStream codedos = CodedOutputStream.newInstance(outputStream);
+ atom.writeTo(codedos);
+ codedos.flush();
+
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+ CodedInputStream codedis = CodedInputStream.newInstance(inputStream);
+ AtomsProto.Atom atomWithExtensions = AtomsProto.Atom.parseFrom(codedis, mRegistry);
+
+ assertTrue(
+ atomWithExtensions.hasExtension(FrameworkExtensionAtoms.frameworkWakelockInfo));
+ FrameworkWakelockInfo info =
+ atomWithExtensions.getExtension(FrameworkExtensionAtoms.frameworkWakelockInfo);
+ result.add(info);
+ }
+
+ return result;
+ }
+
+ @Test
+ public void singleWakelock() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_2);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_3);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).getAttributionUid());
+ assertEquals("tag", TAG_1, info.get(0).getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).getType());
+ assertEquals("duration", TS_2 - TS_1, info.get(0).getUptimeMillis());
+ assertEquals("count", 1, info.get(0).getCompletedCount());
+ }
+
+ @Test
+ public void wakelockOpen() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_3);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).getAttributionUid());
+ assertEquals("tag", TAG_1, info.get(0).getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).getType());
+ assertEquals("duration", TS_3 - TS_1, info.get(0).getUptimeMillis());
+ assertEquals("count", 0, info.get(0).getCompletedCount());
+ }
+
+ @Test
+ public void wakelockOpenOverlap() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_3);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).getAttributionUid());
+ assertEquals("tag", TAG_1, info.get(0).getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).getType());
+ assertEquals("duration", TS_4 - TS_1, info.get(0).getUptimeMillis());
+ assertEquals("count", 0, info.get(0).getCompletedCount());
+ }
+
+ @Test
+ public void testOverflow() throws Exception {
+ makeMetricsAlmostOverflow();
+
+ // This one gets tagged as an overflow.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_2);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo overflowInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.OVERFLOW_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", UID_1, overflowInfo.getAttributionUid());
+ assertEquals(
+ "type",
+ WakeLockLevelEnum.forNumber(mEvents.OVERFLOW_LEVEL),
+ overflowInfo.getType());
+ assertEquals("duration", TS_2 - TS_1, overflowInfo.getUptimeMillis());
+ assertEquals("count", 1, overflowInfo.getCompletedCount());
+ }
+
+ @Test
+ public void testOverflowOpen() throws Exception {
+ makeMetricsAlmostOverflow();
+
+ // This is the open wakelock that overflows.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_1);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo overflowInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.OVERFLOW_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", UID_1, overflowInfo.getAttributionUid());
+ assertEquals(
+ "type",
+ WakeLockLevelEnum.forNumber(mEvents.OVERFLOW_LEVEL),
+ overflowInfo.getType());
+ assertEquals("duration", (TS_4 - TS_1), overflowInfo.getUptimeMillis());
+ assertEquals("count", 0, overflowInfo.getCompletedCount());
+ }
+
+ @Test
+ public void testHardCap() throws Exception {
+ makeMetricsAlmostHardCap();
+
+ // This one gets tagged as a hard cap.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_2);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo hardCapInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.HARD_CAP_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", mEvents.HARD_CAP_UID, hardCapInfo.getAttributionUid());
+ assertEquals(
+ "type",
+ WakeLockLevelEnum.forNumber(mEvents.OVERFLOW_LEVEL),
+ hardCapInfo.getType());
+ assertEquals("duration", TS_2 - TS_1, hardCapInfo.getUptimeMillis());
+ assertEquals("count", 1, hardCapInfo.getCompletedCount());
+ }
+
+ @Test
+ public void testHardCapOpen() throws Exception {
+ makeMetricsAlmostHardCap();
+
+ // This is the open wakelock that overflows.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_1);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo hardCapInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.HARD_CAP_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", mEvents.HARD_CAP_UID, hardCapInfo.getAttributionUid());
+ assertEquals(
+ "type",
+ WakeLockLevelEnum.forNumber(mEvents.OVERFLOW_LEVEL),
+ hardCapInfo.getType());
+ assertEquals("duration", (TS_4 - TS_1), hardCapInfo.getUptimeMillis());
+ assertEquals("count", 0, hardCapInfo.getCompletedCount());
+ }
+
+ @Test
+ public void overlappingWakelocks() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_4);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_5);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).getAttributionUid());
+ assertEquals("tag", TAG_1, info.get(0).getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).getType());
+ assertEquals("duration", TS_4 - TS_1, info.get(0).getUptimeMillis());
+ assertEquals("count", 1, info.get(0).getCompletedCount());
+ }
+
+ @Test
+ public void diffUid() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStartWakeLock(UID_2, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ mEvents.noteStopWakeLock(UID_2, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_4);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_5);
+ assertEquals("size", 2, info.size());
+
+ FrameworkWakelockInfo uid1Info =
+ info.stream().filter(i -> i.getAttributionUid() == UID_1).findFirst().orElse(null);
+
+ assertTrue("UID_1 found", uid1Info != null);
+ assertEquals("uid", UID_1, uid1Info.getAttributionUid());
+ assertEquals("tag", TAG_1, uid1Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, uid1Info.getType());
+ assertEquals("duration", TS_4 - TS_1, uid1Info.getUptimeMillis());
+ assertEquals("count", 1, uid1Info.getCompletedCount());
+
+ FrameworkWakelockInfo uid2Info =
+ info.stream().filter(i -> i.getAttributionUid() == UID_2).findFirst().orElse(null);
+ assertTrue("UID_2 found", uid2Info != null);
+ assertEquals("uid", UID_2, uid2Info.getAttributionUid());
+ assertEquals("tag", TAG_1, uid2Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, uid2Info.getType());
+ assertEquals("duration", TS_3 - TS_2, uid2Info.getUptimeMillis());
+ assertEquals("count", 1, uid2Info.getCompletedCount());
+ }
+
+ @Test
+ public void diffTag() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_1.getNumber(), TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_4);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_5);
+ assertEquals("size", 2, info.size());
+
+ FrameworkWakelockInfo uid1Info =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(TAG_1))
+ .findFirst()
+ .orElse(null);
+
+ assertTrue("TAG_1 found", uid1Info != null);
+ assertEquals("uid", UID_1, uid1Info.getAttributionUid());
+ assertEquals("tag", TAG_1, uid1Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, uid1Info.getType());
+ assertEquals("duration", TS_4 - TS_1, uid1Info.getUptimeMillis());
+ assertEquals("count", 1, uid1Info.getCompletedCount());
+
+ FrameworkWakelockInfo uid2Info =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(TAG_2))
+ .findFirst()
+ .orElse(null);
+ assertTrue("TAG_2 found", uid2Info != null);
+ assertEquals("uid", UID_1, uid2Info.getAttributionUid());
+ assertEquals("tag", TAG_2, uid2Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, uid2Info.getType());
+ assertEquals("duration", TS_3 - TS_2, uid2Info.getUptimeMillis());
+ assertEquals("count", 1, uid2Info.getCompletedCount());
+ }
+
+ @Test
+ public void diffType() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_2.getNumber(), TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_2.getNumber(), TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_4);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_5);
+ assertEquals("size", 2, info.size());
+
+ FrameworkWakelockInfo uid1Info =
+ info.stream().filter(i -> i.getType() == WAKELOCK_TYPE_1).findFirst().orElse(null);
+
+ assertTrue("WAKELOCK_TYPE_1 found", uid1Info != null);
+ assertEquals("uid", UID_1, uid1Info.getAttributionUid());
+ assertEquals("tag", TAG_1, uid1Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, uid1Info.getType());
+ assertEquals("duration", TS_4 - TS_1, uid1Info.getUptimeMillis());
+ assertEquals("count", 1, uid1Info.getCompletedCount());
+
+ FrameworkWakelockInfo uid2Info =
+ info.stream().filter(i -> i.getType() == WAKELOCK_TYPE_2).findFirst().orElse(null);
+ assertTrue("WAKELOCK_TYPE_2 found", uid2Info != null);
+ assertEquals("uid", UID_1, uid2Info.getAttributionUid());
+ assertEquals("tag", TAG_1, uid2Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_2, uid2Info.getType());
+ assertEquals("duration", TS_3 - TS_2, uid2Info.getUptimeMillis());
+ assertEquals("count", 1, uid2Info.getCompletedCount());
+ }
+}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 2724149d859f..c645c0852f1b 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -113,6 +113,7 @@
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index c970a3e34d12..840e5c58078b 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -65,7 +65,6 @@ public class AppOpsActiveWatcherTest {
VirtualDeviceRule.withAdditionalPermissions(
Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
- Manifest.permission.CREATE_VIRTUAL_DEVICE,
Manifest.permission.GET_APP_OPS_STATS
);
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
index 7f2327aa4f24..e3eca6d5fd83 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
@@ -58,7 +58,6 @@ public class AppOpsDeviceAwareServiceTest {
VirtualDeviceRule.withAdditionalPermissions(
Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
- Manifest.permission.CREATE_VIRTUAL_DEVICE,
Manifest.permission.GET_APP_OPS_STATS);
private static final String ATTRIBUTION_TAG_1 = "attributionTag1";
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
index 1abd4eb6157f..b0846f62628c 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
@@ -22,16 +22,14 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpNotedListener;
import android.companion.virtual.VirtualDeviceManager;
-import android.companion.virtual.VirtualDeviceParams;
import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
-import android.virtualdevice.cts.common.FakeAssociationRule;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -42,8 +40,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
-import java.util.concurrent.atomic.AtomicInteger;
-
/**
* Tests watching noted ops.
*/
@@ -51,7 +47,7 @@ import java.util.concurrent.atomic.AtomicInteger;
@RunWith(AndroidJUnit4.class)
public class AppOpsNotedWatcherTest {
@Rule
- public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+ public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -119,19 +115,12 @@ public class AppOpsNotedWatcherTest {
public void testWatchNotedOpsForExternalDevice() {
final AppOpsManager.OnOpNotedListener listener = mock(
AppOpsManager.OnOpNotedListener.class);
- final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
- VirtualDeviceManager.class);
- AtomicInteger virtualDeviceId = new AtomicInteger();
- runWithShellPermissionIdentity(() -> {
- final VirtualDeviceManager.VirtualDevice virtualDevice =
- virtualDeviceManager.createVirtualDevice(
- mFakeAssociationRule.getAssociationInfo().getId(),
- new VirtualDeviceParams.Builder().setName("virtual_device").build());
- virtualDeviceId.set(virtualDevice.getDeviceId());
- });
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice();
+ final int virtualDeviceId = virtualDevice.getDeviceId();
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
getContext().getOpPackageName(), getContext().getAttributionTag(),
- virtualDeviceId.get());
+ virtualDeviceId);
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
appOpsManager.startWatchingNoted(new int[]{AppOpsManager.OP_FINE_LOCATION,
@@ -142,7 +131,7 @@ public class AppOpsNotedWatcherTest {
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getOpPackageName()),
- eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId),
eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index 8a6ba4d484f7..d46fb90f40d6 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -16,8 +16,6 @@
package com.android.server.appop;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -28,11 +26,10 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpStartedListener;
import android.companion.virtual.VirtualDeviceManager;
-import android.companion.virtual.VirtualDeviceParams;
import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
-import android.virtualdevice.cts.common.FakeAssociationRule;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -43,15 +40,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
-import java.util.concurrent.atomic.AtomicInteger;
-
/** Tests watching started ops. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppOpsStartedWatcherTest {
@Rule
- public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+ public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -124,20 +119,13 @@ public class AppOpsStartedWatcherTest {
@Test
public void testWatchStartedOpsForExternalDevice() {
- final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
- VirtualDeviceManager.class);
- AtomicInteger virtualDeviceId = new AtomicInteger();
- runWithShellPermissionIdentity(() -> {
- final VirtualDeviceManager.VirtualDevice virtualDevice =
- virtualDeviceManager.createVirtualDevice(
- mFakeAssociationRule.getAssociationInfo().getId(),
- new VirtualDeviceParams.Builder().setName("virtual_device").build());
- virtualDeviceId.set(virtualDevice.getDeviceId());
- });
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice();
+ final int virtualDeviceId = virtualDevice.getDeviceId();
final OnOpStartedListener listener = mock(OnOpStartedListener.class);
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
getContext().getOpPackageName(), getContext().getAttributionTag(),
- virtualDeviceId.get());
+ virtualDeviceId);
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
appOpsManager.startWatchingStarted(new int[]{AppOpsManager.OP_FINE_LOCATION,
@@ -150,7 +138,7 @@ public class AppOpsStartedWatcherTest {
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getOpPackageName()),
- eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId),
eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index e6c34ca68c75..98b11918c13b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -50,7 +50,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
-import android.Manifest;
import android.app.WindowConfiguration;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
@@ -113,10 +112,11 @@ import android.view.Display;
import android.view.DisplayInfo;
import android.view.KeyEvent;
import android.view.WindowManager;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.compatibility.common.util.SystemUtil;
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
@@ -224,9 +224,7 @@ public class VirtualDeviceManagerServiceTest {
public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule
- public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- Manifest.permission.CREATE_VIRTUAL_DEVICE);
+ public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
@@ -1069,64 +1067,65 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualDpad_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER)));
}
@Test
public void createVirtualKeyboard_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER)));
}
@Test
public void createVirtualMouse_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER)));
}
@Test
public void createVirtualTouchscreen_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER)));
}
@Test
public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
- BINDER));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualNavigationTouchpad(
+ NAVIGATION_TOUCHPAD_CONFIG,
+ BINDER)));
}
@Test
public void onAudioSessionStarting_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.onAudioSessionStarting(
- DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.onAudioSessionStarting(
+ DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback)));
}
@Test
public void onAudioSessionEnded_noPermission_failsSecurityException() {
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded());
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded()));
}
@Test
@@ -2001,18 +2000,4 @@ public class VirtualDeviceManagerServiceTest {
/* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
/* timeApprovedMs= */0, /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
}
-
- /** Helper class to drop permissions temporarily and restore them at the end of a test. */
- static final class DropShellPermissionsTemporarily implements AutoCloseable {
- DropShellPermissionsTemporarily() {
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .dropShellPermissionIdentity();
- }
-
- @Override
- public void close() {
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .adoptShellPermissionIdentity();
- }
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 425bb158f997..7e22d74c64e1 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -1256,7 +1256,8 @@ public class MediaProjectionManagerServiceTest {
Manifest.permission.BYPASS_ROLE_QUALIFICATION);
roleManager.setBypassingRoleQualification(true);
- roleManager.addRoleHolderAsUser(role, packageName, /* flags = */ 0, user,
+ roleManager.addRoleHolderAsUser(role, packageName,
+ /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
mContext.getMainExecutor(), success -> {
if (success) {
latch.countDown();
@@ -1271,9 +1272,9 @@ public class MediaProjectionManagerServiceTest {
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
- roleManager.removeRoleHolderAsUser(role, packageName, 0, user,
- mContext.getMainExecutor(), (aBool) -> {
- });
+ roleManager.removeRoleHolderAsUser(role, packageName,
+ /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
+ mContext.getMainExecutor(), (aBool) -> {});
roleManager.setBypassingRoleQualification(false);
instrumentation.getUiAutomation()
.dropShellPermissionIdentity();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 3bbc6b21cce2..48bc9d7c51a1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -63,13 +63,11 @@ import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
-import android.testing.TestableLooper;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -84,7 +82,6 @@ import com.android.server.UiServiceTestCase;
import com.google.android.collect.Lists;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -106,7 +103,6 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
-
public class ManagedServicesTest extends UiServiceTestCase {
@Mock
@@ -119,7 +115,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
private ManagedServices.UserProfiles mUserProfiles;
@Mock private DevicePolicyManager mDpm;
Object mLock = new Object();
- private TestableLooper mTestableLooper;
UserInfo mZero = new UserInfo(0, "zero", 0);
UserInfo mTen = new UserInfo(10, "ten", 0);
@@ -147,7 +142,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTestableLooper = new TestableLooper(Looper.getMainLooper());
mContext.setMockPackageManager(mPm);
mContext.addMockSystemService(Context.USER_SERVICE, mUm);
@@ -205,11 +199,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
mIpm, APPROVAL_BY_COMPONENT);
}
- @After
- public void tearDown() throws Exception {
- mTestableLooper.destroy();
- }
-
@Test
public void testBackupAndRestore_migration() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
@@ -899,7 +888,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, true);
service.reregisterService(cn, 0);
@@ -930,7 +919,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, false);
service.reregisterService(cn, 0);
@@ -961,7 +950,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, true);
service.reregisterService(cn, 0);
@@ -992,7 +981,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, false);
service.reregisterService(cn, 0);
@@ -1064,77 +1053,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
- public void registerService_bindingDied_rebindIsClearedOnUserSwitch() throws Exception {
- Context context = mock(Context.class);
- PackageManager pm = mock(PackageManager.class);
- ApplicationInfo ai = new ApplicationInfo();
- ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
- when(context.getPackageName()).thenReturn(mPkg);
- when(context.getUserId()).thenReturn(mUser.getIdentifier());
- when(context.getPackageManager()).thenReturn(pm);
- when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
-
- ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
- APPROVAL_BY_PACKAGE);
- service = spy(service);
- ComponentName cn = ComponentName.unflattenFromString("a/a");
-
- // Trigger onBindingDied for component when registering
- // => will schedule a rebind in 10 seconds
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- ServiceConnection sc = (ServiceConnection) args[1];
- sc.onBindingDied(cn);
- return true;
- });
- service.registerService(cn, 0);
- assertThat(service.isBound(cn, 0)).isFalse();
-
- // Switch to user 10
- service.onUserSwitched(10);
-
- // Check that the scheduled rebind for user 0 was cleared
- mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
- mTestableLooper.processAllMessages();
- verify(service, never()).reregisterService(any(), anyInt());
- }
-
- @Test
- public void registerService_bindingDied_rebindIsExecutedAfterTimeout() throws Exception {
- Context context = mock(Context.class);
- PackageManager pm = mock(PackageManager.class);
- ApplicationInfo ai = new ApplicationInfo();
- ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
- when(context.getPackageName()).thenReturn(mPkg);
- when(context.getUserId()).thenReturn(mUser.getIdentifier());
- when(context.getPackageManager()).thenReturn(pm);
- when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
-
- ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
- APPROVAL_BY_PACKAGE);
- service = spy(service);
- ComponentName cn = ComponentName.unflattenFromString("a/a");
-
- // Trigger onBindingDied for component when registering
- // => will schedule a rebind in 10 seconds
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- ServiceConnection sc = (ServiceConnection) args[1];
- sc.onBindingDied(cn);
- return true;
- });
- service.registerService(cn, 0);
- assertThat(service.isBound(cn, 0)).isFalse();
-
- // Check that the scheduled rebind is run
- mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
- mTestableLooper.processAllMessages();
- verify(service, times(1)).reregisterService(eq(cn), eq(0));
- }
-
- @Test
public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1293,64 +1211,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
- public void testUpgradeAppNoIntentFilterNoRebind() throws Exception {
- Context context = spy(getContext());
- doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
-
- ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
- mIpm, APPROVAL_BY_COMPONENT);
-
- List<String> packages = new ArrayList<>();
- packages.add("package");
- addExpectedServices(service, packages, 0);
-
- final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
- final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
-
- // Both components are approved initially
- mExpectedPrimaryComponentNames.clear();
- mExpectedPrimaryPackages.clear();
- mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
- mExpectedSecondaryComponentNames.clear();
- mExpectedSecondaryPackages.clear();
-
- loadXml(service);
-
- //Component package/C1 loses serviceInterface intent filter
- ManagedServices.Config config = service.getConfig();
- when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
- .thenAnswer(new Answer<List<ResolveInfo>>() {
- @Override
- public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
- throws Throwable {
- Object[] args = invocationOnMock.getArguments();
- Intent invocationIntent = (Intent) args[0];
- if (invocationIntent != null) {
- if (invocationIntent.getAction().equals(config.serviceInterface)
- && packages.contains(invocationIntent.getPackage())) {
- List<ResolveInfo> dummyServices = new ArrayList<>();
- ResolveInfo resolveInfo = new ResolveInfo();
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.packageName = invocationIntent.getPackage();
- serviceInfo.name = approvedComponent.getClassName();
- serviceInfo.permission = service.getConfig().bindPermission;
- resolveInfo.serviceInfo = serviceInfo;
- dummyServices.add(resolveInfo);
- return dummyServices;
- }
- }
- return new ArrayList<>();
- }
- });
-
- // Trigger package update
- service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
-
- assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
- assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
- }
-
- @Test
public void testSetPackageOrComponentEnabled() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1363,21 +1223,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
"user10package1/K", "user10.3/Component", "user10package2/L",
"user10.4/Component"}));
- // mock permissions for services
- PackageManager pm = mock(PackageManager.class);
- when(getContext().getPackageManager()).thenReturn(pm);
- List<ComponentName> enabledComponents = List.of(
- ComponentName.unflattenFromString("package/Comp"),
- ComponentName.unflattenFromString("package/C2"),
- ComponentName.unflattenFromString("again/M4"),
- ComponentName.unflattenFromString("user10package/B"),
- ComponentName.unflattenFromString("user10/Component"),
- ComponentName.unflattenFromString("user10package1/K"),
- ComponentName.unflattenFromString("user10.3/Component"),
- ComponentName.unflattenFromString("user10package2/L"),
- ComponentName.unflattenFromString("user10.4/Component"));
- mockServiceInfoWithMetaData(enabledComponents, service, pm, new ArrayMap<>());
-
for (int userId : expectedEnabled.keySet()) {
ArrayList<String> expectedForUser = expectedEnabled.get(userId);
for (int i = 0; i < expectedForUser.size(); i++) {
@@ -2099,7 +1944,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
metaDataAutobindAllow.putBoolean(META_DATA_DEFAULT_AUTOBIND, true);
metaDatas.put(cn_allowed, metaDataAutobindAllow);
- mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, metaDatas);
service.addApprovedList(cn_allowed.flattenToString(), 0, true);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2144,7 +1989,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
- mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, metaDatas);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2183,7 +2028,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
- mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, metaDatas);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2254,8 +2099,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
- ManagedServices service, PackageManager packageManager,
- ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException {
+ ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
+ throws RemoteException {
when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
(Answer<ServiceInfo>) invocation -> {
ComponentName invocationCn = invocation.getArgument(0);
@@ -2270,39 +2115,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
return null;
}
);
-
- // add components to queryIntentServicesAsUser response
- final List<String> packages = new ArrayList<>();
- for (ComponentName cn: componentNames) {
- packages.add(cn.getPackageName());
- }
- ManagedServices.Config config = service.getConfig();
- when(packageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())).
- thenAnswer(new Answer<List<ResolveInfo>>() {
- @Override
- public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
- throws Throwable {
- Object[] args = invocationOnMock.getArguments();
- Intent invocationIntent = (Intent) args[0];
- if (invocationIntent != null) {
- if (invocationIntent.getAction().equals(config.serviceInterface)
- && packages.contains(invocationIntent.getPackage())) {
- List<ResolveInfo> dummyServices = new ArrayList<>();
- for (ComponentName cn: componentNames) {
- ResolveInfo resolveInfo = new ResolveInfo();
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.packageName = invocationIntent.getPackage();
- serviceInfo.name = cn.getClassName();
- serviceInfo.permission = service.getConfig().bindPermission;
- resolveInfo.serviceInfo = serviceInfo;
- dummyServices.add(resolveInfo);
- }
- return dummyServices;
- }
- }
- return new ArrayList<>();
- }
- });
}
private void resetComponentsAndPackages() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 7e4ae679a22f..797b95b5dbe9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -25,7 +25,6 @@ import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNull;
-
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
@@ -194,8 +193,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
public void testWriteXml_userTurnedOffNAS() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
-
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
@@ -401,10 +398,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
public void testSetPackageOrComponentEnabled_onlyOnePackage() throws Exception {
ComponentName component1 = ComponentName.unflattenFromString("package/Component1");
ComponentName component2 = ComponentName.unflattenFromString("package/Component2");
-
- doReturn(true).when(mAssistants).isValidService(eq(component1), eq(mZero.id));
- doReturn(true).when(mAssistants).isValidService(eq(component2), eq(mZero.id));
-
mAssistants.setPackageOrComponentEnabled(component1.flattenToString(), mZero.id, true,
true, true);
verify(mNm, never()).setNotificationAssistantAccessGrantedForUserInternal(
@@ -550,7 +543,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
public void testSetAdjustmentTypeSupportedState() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
@@ -574,7 +566,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
public void testSetAdjustmentTypeSupportedState_readWriteXml_entries() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
@@ -598,7 +589,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
public void testSetAdjustmentTypeSupportedState_readWriteXml_empty() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
diff --git a/tests/graphics/SilkFX/res/layout/view_blur_behind.xml b/tests/graphics/SilkFX/res/layout/view_blur_behind.xml
new file mode 100644
index 000000000000..83b1fa4b73cb
--- /dev/null
+++ b/tests/graphics/SilkFX/res/layout/view_blur_behind.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="wowwowwowwowwowwowwowwowwowwowwowwowwowwowwow" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="I'm a little teapot" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="Something. Something." />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="/\\/\\/\\/\\/\\/\\/\\/\\/\\/" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="wowwowwowwowwowwowwowwowwowwowwowwowwowwowwow" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="I'm a little teapot" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="Something. Something." />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="/\\/\\/\\/\\/\\/\\/\\/\\/\\/" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^" />
+
+ </LinearLayout>
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="300dp" />
+
+ <com.android.test.silkfx.materials.BlurBehindContainer
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#33AAAAAA"
+ android:padding="32dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="48dp"
+ android:text="Blur!" />
+
+ </com.android.test.silkfx.materials.BlurBehindContainer>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1024dp" />
+
+ </LinearLayout>
+
+ </ScrollView>
+
+</FrameLayout> \ No newline at end of file
diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
index 59a6078376cf..6b6d3b8d3d12 100644
--- a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
+++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
@@ -61,7 +61,8 @@ private val AllDemos = listOf(
)),
DemoGroup("Materials", listOf(
Demo("Glass", GlassActivity::class),
- Demo("Background Blur", BackgroundBlurActivity::class)
+ Demo("Background Blur", BackgroundBlurActivity::class),
+ Demo("View blur behind", R.layout.view_blur_behind, commonControls = false)
))
)
diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt
new file mode 100644
index 000000000000..ce6348e32969
--- /dev/null
+++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 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.test.silkfx.materials
+
+import android.content.Context
+import android.graphics.RenderEffect
+import android.graphics.Shader
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+class BlurBehindContainer(context: Context, attributeSet: AttributeSet) : FrameLayout(context, attributeSet) {
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ setBackdropRenderEffect(
+ RenderEffect.createBlurEffect(16.0f, 16.0f, Shader.TileMode.CLAMP))
+ }
+} \ No newline at end of file