From 368c5adaf7a1141bfee3cf622efbc7285e370979 Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Tue, 22 Jul 2014 16:47:26 -0700 Subject: [PATCH] Refactor window manager screenshot For readability, and add rotation compensation. Fixes bug 16142514. Change-Id: I1156ec2e0f052f6dec56457d56efe95368f60d91 --- .../server/wm/WindowManagerService.java | 175 +++++++++--------- 1 file changed, 84 insertions(+), 91 deletions(-) diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 29ef9c1631c5f..eb90085734f2f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5675,13 +5675,28 @@ public class WindowManagerService extends IWindowManager.Stub throw new SecurityException("Requires READ_FRAME_BUFFER permission"); } + final DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent == null) { + if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken + + ": returning null. No Display for displayId=" + displayId); + return null; + } + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + int dw = displayInfo.logicalWidth; + int dh = displayInfo.logicalHeight; + if (dw == 0 || dh == 0) { + if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken + + ": returning null. logical widthxheight=" + dw + "x" + dh); + return null; + } + Bitmap rawss = null; int maxLayer = 0; final Rect frame = new Rect(); + final Rect stackBounds = new Rect(); float scale = 0; - int dw, dh; int rot = Surface.ROTATION_0; boolean screenshotReady; @@ -5697,7 +5712,15 @@ public class WindowManagerService extends IWindowManager.Stub int retryCount = 0; WindowState appWin = null; - do { + final boolean appIsImTarget = mInputMethodTarget != null + && mInputMethodTarget.mAppToken != null + && mInputMethodTarget.mAppToken.appToken != null + && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken; + + final int aboveAppLayer = (mPolicy.windowTypeToLayerLw(TYPE_APPLICATION) + 1) + * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET; + + while (true) { if (retryCount++ > 0) { try { Thread.sleep(100); @@ -5705,28 +5728,9 @@ public class WindowManagerService extends IWindowManager.Stub } } synchronized(mWindowMap) { - final DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent == null) { - return null; - } - final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - dw = displayInfo.logicalWidth; - dh = displayInfo.logicalHeight; - - int aboveAppLayer = mPolicy.windowTypeToLayerLw(TYPE_APPLICATION) - * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET; - aboveAppLayer += TYPE_LAYER_MULTIPLIER; - - boolean isImeTarget = mInputMethodTarget != null - && mInputMethodTarget.mAppToken != null - && mInputMethodTarget.mAppToken.appToken != null - && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken; - // Figure out the part of the screen that is actually the app. - boolean including = false; appWin = null; final WindowList windows = displayContent.getWindowList(); - final Rect stackBounds = new Rect(); for (int i = windows.size() - 1; i >= 0; i--) { WindowState ws = windows.get(i); if (!ws.mHasSurface) { @@ -5735,27 +5739,22 @@ public class WindowManagerService extends IWindowManager.Stub if (ws.mLayer >= aboveAppLayer) { continue; } - // When we will skip windows: when we are not including - // ones behind a window we didn't skip, and we are actually - // taking a screenshot of a specific app. - if (!including && appToken != null) { - // Also, we can possibly skip this window if it is not - // an IME target or the application for the screenshot - // is not the current IME target. - if (!ws.mIsImWindow || !isImeTarget) { - // And finally, this window is of no interest if it - // is not associated with the screenshot app. - if (ws.mAppToken == null || ws.mAppToken.token != appToken) { - continue; - } - appWin = ws; - ws.getStackBounds(stackBounds); + if (ws.mIsImWindow) { + if (!appIsImTarget) { + continue; } + } else if (ws.mIsWallpaper) { + // Fall through. + } else if (appToken != null) { + if (ws.mAppToken == null || ws.mAppToken.token != appToken) { + // This app window is of no interest if it is not associated with the + // screenshot app. + continue; + } + appWin = ws; } - // We keep on including windows until we go past a full-screen - // window. - including = !ws.mIsImWindow && !ws.isFullscreen(dw, dh); + // Include this window. final WindowStateAnimator winAnim = ws.mWinAnimator; if (maxLayer < winAnim.mSurfaceLayer) { @@ -5774,6 +5773,7 @@ public class WindowManagerService extends IWindowManager.Stub int right = wf.right - cr.right; int bottom = wf.bottom - cr.bottom; frame.union(left, top, right, bottom); + ws.getStackBounds(stackBounds); frame.intersect(stackBounds); } @@ -5789,60 +5789,49 @@ public class WindowManagerService extends IWindowManager.Stub "Screenshot: Couldn't find a surface matching " + appToken); return null; } + if (!screenshotReady) { + if (retryCount > MAX_SCREENSHOT_RETRIES) { + Slog.i(TAG, "Screenshot max retries " + retryCount + " of " + appToken + + " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" + + appWin.mWinAnimator.mDrawState))); + return null; + } + // Delay and hope that window gets drawn. if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot: No image ready for " + appToken + ", " + appWin + " drawState=" + appWin.mWinAnimator.mDrawState); continue; } - // Constrain frame to the screen size. - frame.intersect(0, 0, dw, dh); + // Screenshot is ready to be taken. Everything from here below will continue + // through the bottom of the loop and return a value. We only stay in the loop + // because we don't want to release the mWindowMap lock until the screenshot is + // taken. - if (frame.isEmpty() || maxLayer == 0) { + if (maxLayer == 0) { if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken - + ": returning null frame=" + frame.toShortString() + " maxLayer=" - + maxLayer); + + ": returning null maxLayer=" + maxLayer); return null; } - // The screenshot API does not apply the current screen rotation. - rot = getDefaultDisplayContentLocked().getDisplay().getRotation(); - int fw = frame.width(); - int fh = frame.height(); - + // Constrain frame to the screen size. + frame.intersect(0, 0, dw, dh); // Constrain thumbnail to smaller of screen width or height. Assumes aspect // of thumbnail is the same as the screen (in landscape) or square. - scale = Math.max(width / (float) fw, height / (float) fh); - /* - float targetWidthScale = width / (float) fw; - float targetHeightScale = height / (float) fh; - if (fw <= fh) { - scale = targetWidthScale; - // If aspect of thumbnail is the same as the screen (in landscape), - // select the slightly larger value so we fill the entire bitmap - if (targetHeightScale > scale && (int) (targetHeightScale * fw) == width) { - scale = targetHeightScale; - } - } else { - scale = targetHeightScale; - // If aspect of thumbnail is the same as the screen (in landscape), - // select the slightly larger value so we fill the entire bitmap - if (targetWidthScale > scale && (int) (targetWidthScale * fh) == height) { - scale = targetWidthScale; - } - } - */ + scale = Math.max(width / (float) frame.width(), height / (float) frame.height()); + dw = (int)(dw * scale); + dh = (int)(dh * scale); - // The screen shot will contain the entire screen. - dw = (int)(dw*scale); - dh = (int)(dh*scale); + // The screenshot API does not apply the current screen rotation. + rot = getDefaultDisplayContentLocked().getDisplay().getRotation(); if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { - int tmp = dw; + final int tmp = dw; dw = dh; dh = tmp; rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90; } + if (DEBUG_SCREENSHOT) { Slog.i(TAG, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to " + maxLayer + " appToken=" + appToken); @@ -5853,27 +5842,31 @@ public class WindowManagerService extends IWindowManager.Stub + " surfaceLayer=" + win.mWinAnimator.mSurfaceLayer); } } - // TODO: Replace 'false' in the following line with a variable that indicates - // whether the screenshot should use the identity transformation matrix - // (e.g., enable it when taking a screenshot for recents, since we might be in - // the middle of the rotation animation, but don't want a rotated recent image). - // TODO: Replace 'new Rect()' with the portion of the screen to capture for the - // screenshot. - rawss = SurfaceControl.screenshot(new Rect(), dw, dh, minLayer, maxLayer, false); - } - } while (!screenshotReady && retryCount <= MAX_SCREENSHOT_RETRIES); - if (retryCount > MAX_SCREENSHOT_RETRIES) Slog.i(TAG, "Screenshot max retries " + - retryCount + " of " + appToken + " appWin=" + (appWin == null ? - "null" : (appWin + " drawState=" + appWin.mWinAnimator.mDrawState))); - if (rawss == null) { - Slog.w(TAG, "Screenshot failure taking screenshot for (" + dw + "x" + dh - + ") to layer " + maxLayer); - return null; + ScreenRotationAnimation screenRotationAnimation = + mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY); + final boolean inRotation = screenRotationAnimation != null && + screenRotationAnimation.isAnimating(); + if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG, + "Taking screenshot while rotating"); + + rawss = SurfaceControl.screenshot(new Rect(), dw, dh, minLayer, maxLayer, + inRotation); + if (rawss == null) { + Slog.w(TAG, "Screenshot failure taking screenshot for (" + dw + "x" + dh + + ") to layer " + maxLayer); + return null; + } + } + + break; } - Bitmap bm = Bitmap.createBitmap(width, height, force565 ? Config.RGB_565 : rawss.getConfig()); - bm.eraseColor(0xFF000000); + Bitmap bm = Bitmap.createBitmap(width, height, force565 ? + Config.RGB_565 : rawss.getConfig()); + if (DEBUG_SCREENSHOT) { + bm.eraseColor(0xFF000000); + } frame.scale(scale); Matrix matrix = new Matrix(); ScreenRotationAnimation.createRotationMatrix(rot, dw, dh, matrix);