diff options
| author | 2018-03-05 15:28:35 -0800 | |
|---|---|---|
| committer | 2018-03-06 12:27:38 -0800 | |
| commit | 0315a1a86f9c48c5eb32b73cce100cd751d9526c (patch) | |
| tree | a2987d91a039118356bda2160116aaf679f4cc79 | |
| parent | 0db51ad50ef0791b196c33759f3ad2c59b2d9395 (diff) | |
Fix wallpaper screenshot
Wallpaper screenshot was broken since it would just screenshot the
entire screen. Updated wallpaper screenshot code to use the new
captureLayers API so the wallpaper layer can be specified for the
screenshot.
Change-Id: I594870583ddc2fb29c7eeafe003f20e4ee392a3a
Fixes: 69562019
Test: testWallpaperScreenshot
5 files changed, 191 insertions, 101 deletions
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 59bece051810..7b5e8b8139b4 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -137,7 +137,6 @@ import android.os.SystemClock; import android.os.Trace; import android.util.ArraySet; import android.util.DisplayMetrics; -import android.util.MutableBoolean; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.Display; @@ -156,7 +155,6 @@ import com.android.internal.view.IInputMethodClient; import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.RotationCache; -import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Comparator; @@ -2960,83 +2958,55 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * In portrait mode, it grabs the full screenshot. * * @param config of the output bitmap - * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot */ - Bitmap screenshotDisplay(Bitmap.Config config, boolean wallpaperOnly) { - synchronized (mService.mWindowMap) { - if (!mService.mPolicy.isScreenOn()) { - if (DEBUG_SCREENSHOT) { - Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); - } - return null; - } - - if (wallpaperOnly && !shouldScreenshotWallpaper()) { - return null; - } - - int dw = mDisplayInfo.logicalWidth; - int dh = mDisplayInfo.logicalHeight; - - if (dw <= 0 || dh <= 0) { - return null; - } - - final Rect frame = new Rect(0, 0, dw, dh); - - // The screenshot API does not apply the current screen rotation. - int rot = mDisplay.getRotation(); - - if (rot == ROTATION_90 || rot == ROTATION_270) { - rot = (rot == ROTATION_90) ? ROTATION_270 : ROTATION_90; + Bitmap screenshotDisplayLocked(Bitmap.Config config) { + if (!mService.mPolicy.isScreenOn()) { + if (DEBUG_SCREENSHOT) { + Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); } + return null; + } - // SurfaceFlinger is not aware of orientation, so convert our logical - // crop to SurfaceFlinger's portrait orientation. - convertCropForSurfaceFlinger(frame, rot, dw, dh); - - final ScreenRotationAnimation screenRotationAnimation = - mService.mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY); - final boolean inRotation = screenRotationAnimation != null && - screenRotationAnimation.isAnimating(); - if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating"); - - // TODO(b/68392460): We should screenshot Task controls directly - // but it's difficult at the moment as the Task doesn't have the - // correct size set. - final Bitmap bitmap = SurfaceControl.screenshot(frame, dw, dh, 0, 1, inRotation, rot); - if (bitmap == null) { - Slog.w(TAG_WM, "Failed to take screenshot"); - return null; - } + int dw = mDisplayInfo.logicalWidth; + int dh = mDisplayInfo.logicalHeight; - // Create a copy of the screenshot that is immutable and backed in ashmem. - // This greatly reduces the overhead of passing the bitmap between processes. - final Bitmap ret = bitmap.createAshmemBitmap(config); - bitmap.recycle(); - return ret; + if (dw <= 0 || dh <= 0) { + return null; } - } - private boolean shouldScreenshotWallpaper() { - MutableBoolean screenshotReady = new MutableBoolean(false); + final Rect frame = new Rect(0, 0, dw, dh); - forAllWindows(w -> { - if (!w.mIsWallpaper) { - return false; - } + // The screenshot API does not apply the current screen rotation. + int rot = mDisplay.getRotation(); - // Found the wallpaper window - final WindowStateAnimator winAnim = w.mWinAnimator; + if (rot == ROTATION_90 || rot == ROTATION_270) { + rot = (rot == ROTATION_90) ? ROTATION_270 : ROTATION_90; + } - if (winAnim.getShown() && winAnim.mLastAlpha > 0f) { - screenshotReady.value = true; - } + // SurfaceFlinger is not aware of orientation, so convert our logical + // crop to SurfaceFlinger's portrait orientation. + convertCropForSurfaceFlinger(frame, rot, dw, dh); - return true; - }, true /* traverseTopToBottom */); + final ScreenRotationAnimation screenRotationAnimation = + mService.mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY); + final boolean inRotation = screenRotationAnimation != null && + screenRotationAnimation.isAnimating(); + if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating"); + + // TODO(b/68392460): We should screenshot Task controls directly + // but it's difficult at the moment as the Task doesn't have the + // correct size set. + final Bitmap bitmap = SurfaceControl.screenshot(frame, dw, dh, 0, 1, inRotation, rot); + if (bitmap == null) { + Slog.w(TAG_WM, "Failed to take screenshot"); + return null; + } - return screenshotReady.value; + // Create a copy of the screenshot that is immutable and backed in ashmem. + // This greatly reduces the overhead of passing the bitmap between processes. + final Bitmap ret = bitmap.createAshmemBitmap(config); + bitmap.recycle(); + return ret; } // TODO: Can this use createRotationMatrix()? diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 2873b6db1a20..c509980e1e9e 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -27,12 +27,16 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT; +import android.graphics.Bitmap; +import android.graphics.GraphicBuffer; +import android.graphics.Rect; import android.os.Bundle; import android.os.Debug; import android.os.IBinder; @@ -41,6 +45,7 @@ import android.os.SystemClock; import android.util.ArraySet; import android.util.Slog; import android.view.DisplayInfo; +import android.view.SurfaceControl; import android.view.WindowManager; import android.view.animation.Animation; @@ -95,6 +100,12 @@ class WallpaperController { private static final int WALLPAPER_DRAW_TIMEOUT = 2; private int mWallpaperDrawState = WALLPAPER_DRAW_NORMAL; + /** + * Temporary storage for taking a screenshot of the wallpaper. + * @see #screenshotWallpaperLocked() + */ + private WindowState mTmpTopWallpaper; + private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult(); private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> { @@ -679,6 +690,58 @@ class WallpaperController { mWallpaperTokens.remove(token); } + /** + * Take a screenshot of the wallpaper if it's visible. + * + * @return Bitmap of the wallpaper + */ + Bitmap screenshotWallpaperLocked() { + if (!mService.mPolicy.isScreenOn()) { + if (DEBUG_SCREENSHOT) { + Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); + } + return null; + } + + final WindowState wallpaperWindowState = getTopVisibleWallpaper(); + if (wallpaperWindowState == null) { + if (DEBUG_SCREENSHOT) { + Slog.i(TAG_WM, "No visible wallpaper to screenshot"); + } + return null; + } + + final Rect bounds = wallpaperWindowState.getBounds(); + bounds.offsetTo(0, 0); + + GraphicBuffer wallpaperBuffer = SurfaceControl.captureLayers( + wallpaperWindowState.getSurfaceControl().getHandle(), bounds, 1 /* frameScale */); + + if (wallpaperBuffer == null) { + Slog.w(TAG_WM, "Failed to screenshot wallpaper"); + return null; + } + return Bitmap.createHardwareBitmap(wallpaperBuffer); + } + + private WindowState getTopVisibleWallpaper() { + mTmpTopWallpaper = null; + + for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { + final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); + token.forAllWindows(w -> { + final WindowStateAnimator winAnim = w.mWinAnimator; + if (winAnim != null && winAnim.getShown() && winAnim.mLastAlpha > 0f) { + mTmpTopWallpaper = w; + return true; + } + return false; + }, true /* traverseTopToBottom */); + } + + return mTmpTopWallpaper; + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mWallpaperTarget="); pw.println(mWallpaperTarget); if (mPrevWallpaperTarget != null) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 78c04e8e6a2c..bdd64d504607 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -28,8 +28,6 @@ import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_USER_HANDLE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.Process.ROOT_UID; -import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Process.myPid; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; @@ -3607,14 +3605,14 @@ public class WindowManagerService extends IWindowManager.Stub @Override public Bitmap screenshotWallpaper() { - if (!checkCallingPermission(READ_FRAME_BUFFER, - "screenshotWallpaper()")) { + if (!checkCallingPermission(READ_FRAME_BUFFER, "screenshotWallpaper()")) { throw new SecurityException("Requires READ_FRAME_BUFFER permission"); } try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper"); - return screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888, - true /* wallpaperOnly */); + synchronized (mWindowMap) { + return mRoot.mWallpaperController.screenshotWallpaperLocked(); + } } finally { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -3627,14 +3625,25 @@ public class WindowManagerService extends IWindowManager.Stub */ @Override public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) { - if (!checkCallingPermission(READ_FRAME_BUFFER, - "requestAssistScreenshot()")) { + if (!checkCallingPermission(READ_FRAME_BUFFER, "requestAssistScreenshot()")) { throw new SecurityException("Requires READ_FRAME_BUFFER permission"); } + final Bitmap bm; + synchronized (mWindowMap) { + final DisplayContent displayContent = mRoot.getDisplayContent(DEFAULT_DISPLAY); + if (displayContent == null) { + if (DEBUG_SCREENSHOT) { + Slog.i(TAG_WM, "Screenshot returning null. No Display for displayId=" + + DEFAULT_DISPLAY); + } + bm = null; + } else { + bm = displayContent.screenshotDisplayLocked(Bitmap.Config.ARGB_8888); + } + } + FgThread.getHandler().post(() -> { - Bitmap bm = screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888, - false /* wallpaperOnly */); try { receiver.onHandleAssistScreenshot(bm); } catch (RemoteException e) { @@ -3664,28 +3673,6 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Takes a snapshot of the screen. In landscape mode this grabs the whole screen. - * In portrait mode, it grabs the full screenshot. - * - * @param displayId the Display to take a screenshot of. - * @param config of the output bitmap - * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot - */ - private Bitmap screenshotApplications(int displayId, Bitmap.Config config, - boolean wallpaperOnly) { - final DisplayContent displayContent; - synchronized(mWindowMap) { - displayContent = mRoot.getDisplayContent(displayId); - if (displayContent == null) { - if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot returning null. No Display for " - + "displayId=" + displayId); - return null; - } - } - return displayContent.screenshotDisplay(config, wallpaperOnly); - } - - /** * Freeze rotation changes. (Enable "rotation lock".) * Persists across reboots. * @param rotation The desired rotation to freeze to, or -1 to use the diff --git a/services/tests/servicestests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/WallpaperControllerTests.java new file mode 100644 index 000000000000..71ead204c9df --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/WallpaperControllerTests.java @@ -0,0 +1,67 @@ +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; + +import static junit.framework.TestCase.assertNotNull; + +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.graphics.Bitmap; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link WallpaperController} class. + * + * Build/Install/Run: + * atest com.android.server.wm.WallpaperControllerTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WallpaperControllerTests extends WindowTestsBase { + @Test + public void testWallpaperScreenshot() { + WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class); + + synchronized (sWm.mWindowMap) { + // No wallpaper + final DisplayContent dc = createNewDisplay(); + Bitmap wallpaperBitmap = sWm.mRoot.mWallpaperController.screenshotWallpaperLocked(); + assertNull(wallpaperBitmap); + + // No wallpaper WSA Surface + WindowToken wallpaperWindowToken = new WallpaperWindowToken(sWm, mock(IBinder.class), + true, dc, true /* ownerCanManageAppTokens */); + WindowState wallpaperWindow = createWindow(null /* parent */, TYPE_WALLPAPER, + wallpaperWindowToken, "wallpaperWindow"); + wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); + assertNull(wallpaperBitmap); + + // Wallpaper with not visible WSA surface. + wallpaperWindow.mWinAnimator.mSurfaceController = windowSurfaceController; + wallpaperWindow.mWinAnimator.mLastAlpha = 1; + wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); + assertNull(wallpaperBitmap); + + when(windowSurfaceController.getShown()).thenReturn(true); + + // Wallpaper with WSA alpha set to 0. + wallpaperWindow.mWinAnimator.mLastAlpha = 0; + wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); + assertNull(wallpaperBitmap); + + // Wallpaper window with WSA Surface + wallpaperWindow.mWinAnimator.mLastAlpha = 1; + wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); + assertNotNull(wallpaperBitmap); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java index 91d5ea463bbe..2890cbd5b6f5 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -84,6 +84,7 @@ class WindowTestsBase { WindowState mChildAppWindowAbove; WindowState mChildAppWindowBelow; HashSet<WindowState> mCommonWindows; + WallpaperController mWallpaperController; @Mock static WindowState.PowerManagerWrapper mPowerManagerWrapper; @@ -105,6 +106,8 @@ class WindowTestsBase { sWm = TestWindowManagerPolicy.getWindowManagerService(context); beforeCreateDisplay(); + mWallpaperController = new WallpaperController(sWm); + context.getDisplay().getDisplayInfo(mDisplayInfo); mDisplayContent = createNewDisplay(); sWm.mDisplayEnabled = true; @@ -288,7 +291,7 @@ class WindowTestsBase { final int displayId = sNextDisplayId++; final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, mDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); - return new DisplayContent(display, sWm, new WallpaperController(sWm), + return new DisplayContent(display, sWm, mWallpaperController, mock(DisplayWindowController.class)); } |