diff options
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 |