Partial screenshot Added a partial screenshot function in TakeScreenshotService also added corresponding shortcut keys in PhoneWindowManager Bug: 26820467 Change-Id: Id67cd3b4b0eed848eb4665056766546500bdac88 (cherry picked from commit 03e45541e9d54a2f285906ac7b5bcb374db14495)
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 6e02516..17f1991 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java
@@ -135,6 +135,18 @@ } /** + * Message for taking fullscreen screenshot + * @hide + */ + final int TAKE_SCREENSHOT_FULLSCREEN = 1; + + /** + * Message for taking screenshot of selected region. + * @hide + */ + final int TAKE_SCREENSHOT_SELECTED_REGION = 2; + + /** * @hide */ public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array"; @@ -269,6 +281,7 @@ @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING, to = "TYPE_VOICE_INTERACTION_STARTING"), @ViewDebug.IntToString(from = TYPE_DOCK_DIVIDER, to = "TYPE_DOCK_DIVIDER"), @ViewDebug.IntToString(from = TYPE_QS_DIALOG, to = "TYPE_QS_DIALOG"), + @ViewDebug.IntToString(from = TYPE_SCREENSHOT, to = "TYPE_SCREENSHOT") }) public int type; @@ -622,6 +635,13 @@ public static final int TYPE_QS_DIALOG = FIRST_SYSTEM_WINDOW+35; /** + * Window type: shares similar characteristics with {@link #TYPE_DREAM}. The layer is + * reserved for screenshot region selection. + * @hide + */ + public static final int TYPE_SCREENSHOT = FIRST_SYSTEM_WINDOW + 36; + + /** * End of types of system windows. */ public static final int LAST_SYSTEM_WINDOW = 2999;
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index 8b337ea..c1fe1a8 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -33,4 +33,10 @@ android:layout_height="match_parent" android:src="@android:color/white" android:visibility="gone" /> + <com.android.systemui.screenshot.ScreenshotSelectorView + android:id="@+id/global_screenshot_selector" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" + android:pointerShape="crosshair"/> </FrameLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index a06700d..e64354c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -39,6 +39,7 @@ import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PointF; +import android.graphics.Rect; import android.media.MediaActionSound; import android.net.Uri; import android.os.AsyncTask; @@ -407,6 +408,7 @@ private Bitmap mScreenBitmap; private View mScreenshotLayout; + private ScreenshotSelectorView mScreenshotSelectorView; private ImageView mBackgroundView; private ImageView mScreenshotView; private ImageView mScreenshotFlash; @@ -437,7 +439,11 @@ mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background); mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot); mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash); + mScreenshotSelectorView = (ScreenshotSelectorView) mScreenshotLayout.findViewById( + R.id.global_screenshot_selector); mScreenshotLayout.setFocusable(true); + mScreenshotSelectorView.setFocusable(true); + mScreenshotSelectorView.setFocusableInTouchMode(true); mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -449,7 +455,7 @@ // Setup the window that we are going to use mWindowLayoutParams = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, - WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.TYPE_SCREENSHOT, WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN @@ -525,7 +531,8 @@ /** * Takes a screenshot of the current display and shows an animation. */ - void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) { + void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible, + int x, int y, int width, int height) { // We need to orient the screenshot correctly (and the Surface api seems to take screenshots // only in the natural orientation of the device :!) mDisplay.getRealMetrics(mDisplayMetrics); @@ -565,6 +572,13 @@ mScreenBitmap = ss; } + if (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels) { + // Crop the screenshot to selected region + Bitmap cropped = Bitmap.createBitmap(mScreenBitmap, x, y, width, height); + mScreenBitmap.recycle(); + mScreenBitmap = cropped; + } + // Optimizations mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); @@ -574,6 +588,71 @@ statusBarVisible, navBarVisible); } + void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) { + mDisplay.getRealMetrics(mDisplayMetrics); + takeScreenshot(finisher, statusBarVisible, navBarVisible, 0, 0, mDisplayMetrics.widthPixels, + mDisplayMetrics.heightPixels); + } + + /** + * Displays a screenshot selector + */ + void takeScreenshotPartial(final Runnable finisher, final boolean statusBarVisible, + final boolean navBarVisible) { + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + ScreenshotSelectorView view = (ScreenshotSelectorView) v; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + view.startSelection((int) event.getX(), (int) event.getY()); + return true; + case MotionEvent.ACTION_MOVE: + view.updateSelection((int) event.getX(), (int) event.getY()); + return true; + case MotionEvent.ACTION_UP: + view.setVisibility(View.GONE); + mWindowManager.removeView(mScreenshotLayout); + final Rect rect = view.getSelectionRect(); + if (rect != null) { + if (rect.width() != 0 && rect.height() != 0) { + // Need mScreenshotLayout to handle it after the view disappears + mScreenshotLayout.post(new Runnable() { + public void run() { + takeScreenshot(finisher, statusBarVisible, navBarVisible, + rect.left, rect.top, rect.width(), rect.height()); + } + }); + } + } + + view.stopSelection(); + return true; + } + + return false; + } + }); + mScreenshotLayout.post(new Runnable() { + @Override + public void run() { + mScreenshotSelectorView.setVisibility(View.VISIBLE); + mScreenshotSelectorView.requestFocus(); + } + }); + } + + /** + * Cancels screenshot request + */ + void stopScreenshot() { + // If the selector layer still presents on screen, we remove it and resets its state. + if (mScreenshotSelectorView.getSelectionRect() != null) { + mWindowManager.removeView(mScreenshotLayout); + mScreenshotSelectorView.stopSelection(); + } + } /** * Starts the animation after taking the screenshot
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java new file mode 100644 index 0000000..07a9246 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java
@@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 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.screenshot; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; + +/** + * Draws a selection rectangle while taking screenshot + */ +public class ScreenshotSelectorView extends View { + private Point mStartPoint; + private Rect mSelectionRect; + private final Paint mPaintSelection, mPaintBackground; + + public ScreenshotSelectorView(Context context) { + this(context, null); + } + + public ScreenshotSelectorView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mPaintBackground = new Paint(Color.BLACK); + mPaintBackground.setAlpha(160); + mPaintSelection = new Paint(Color.TRANSPARENT); + mPaintSelection.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + } + + public void startSelection(int x, int y) { + mStartPoint = new Point(x, y); + mSelectionRect = new Rect(x, y, x, y); + } + + public void updateSelection(int x, int y) { + if (mSelectionRect != null) { + mSelectionRect.left = Math.min(mStartPoint.x, x); + mSelectionRect.right = Math.max(mStartPoint.x, x); + mSelectionRect.top = Math.min(mStartPoint.y, y); + mSelectionRect.bottom = Math.max(mStartPoint.y, y); + invalidate(); + } + } + + public Rect getSelectionRect() { + return mSelectionRect; + } + + public void stopSelection() { + mStartPoint = null; + mSelectionRect = null; + } + + @Override + public void draw(Canvas canvas) { + canvas.drawRect(mLeft, mTop, mRight, mBottom, mPaintBackground); + if (mSelectionRect != null) { + canvas.drawRect(mSelectionRect, mPaintSelection); + } + } +}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 456b5fa..4badc42 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -23,6 +23,7 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.view.WindowManager; public class TakeScreenshotService extends Service { private static final String TAG = "TakeScreenshotService"; @@ -32,21 +33,28 @@ private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { - switch (msg.what) { - case 1: - final Messenger callback = msg.replyTo; - if (mScreenshot == null) { - mScreenshot = new GlobalScreenshot(TakeScreenshotService.this); + final Messenger callback = msg.replyTo; + Runnable finisher = new Runnable() { + @Override + public void run() { + Message reply = Message.obtain(null, 1); + try { + callback.send(reply); + } catch (RemoteException e) { } - mScreenshot.takeScreenshot(new Runnable() { - @Override public void run() { - Message reply = Message.obtain(null, 1); - try { - callback.send(reply); - } catch (RemoteException e) { - } - } - }, msg.arg1 > 0, msg.arg2 > 0); + } + }; + if (mScreenshot == null) { + mScreenshot = new GlobalScreenshot(TakeScreenshotService.this); + } + + switch (msg.what) { + case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: + mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0); + break; + case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: + mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0); + break; } } }; @@ -55,4 +63,10 @@ public IBinder onBind(Intent intent) { return new Messenger(mHandler).getBinder(); } + + @Override + public boolean onUnbind(Intent intent) { + if (mScreenshot != null) mScreenshot.stopScreenshot(); + return true; + } }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 9e6c21c..b4b40ae 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3436,7 +3436,8 @@ case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT: case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG: case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: - case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: { + case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: + case WindowManager.LayoutParams.TYPE_SCREENSHOT: { return AccessibilityWindowInfo.TYPE_SYSTEM; }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 83a5ef5..1603f1c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -28,6 +28,8 @@ import static android.view.WindowManager.DOCKED_TOP; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; +import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; +import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION; import static android.view.WindowManager.LayoutParams.*; import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED; import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT; @@ -1239,7 +1241,7 @@ + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) { mScreenshotChordVolumeDownKeyConsumed = true; cancelPendingPowerKeyAction(); - + mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay()); } } @@ -1269,12 +1271,20 @@ } }; - private final Runnable mScreenshotRunnable = new Runnable() { + private class ScreenshotRunnable implements Runnable { + private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN; + + public void setScreenshotType(int screenshotType) { + mScreenshotType = screenshotType; + } + @Override public void run() { - takeScreenshot(); + takeScreenshot(mScreenshotType); } - }; + } + + private final ScreenshotRunnable mScreenshotRunnable = new ScreenshotRunnable(); @Override public void showGlobalActions() { @@ -2304,29 +2314,33 @@ case TYPE_NAVIGATION_BAR_PANEL: // some panels (e.g. search) need to show on top of the navigation bar return 22; + case TYPE_SCREENSHOT: + // screenshot selection layer shouldn't go above system error, but it should cover + // navigation bars at the very least. + return 23; case TYPE_SYSTEM_ERROR: // system-level error dialogs - return 23; + return 24; case TYPE_MAGNIFICATION_OVERLAY: // used to highlight the magnified portion of a display - return 24; + return 25; case TYPE_DISPLAY_OVERLAY: // used to simulate secondary display devices - return 25; + return 26; case TYPE_DRAG: // the drag layer: input for drag-and-drop is associated with this window, // which sits above all other focusable windows - return 26; + return 27; case TYPE_ACCESSIBILITY_OVERLAY: // overlay put by accessibility services to intercept user interaction - return 27; - case TYPE_SECURE_SYSTEM_OVERLAY: return 28; - case TYPE_BOOT_PROGRESS: + case TYPE_SECURE_SYSTEM_OVERLAY: return 29; + case TYPE_BOOT_PROGRESS: + return 30; case TYPE_POINTER: // the (mouse) pointer layer - return 30; + return 31; } Log.e(TAG, "Unknown window type: " + type); return 2; @@ -3026,6 +3040,15 @@ } } } + } else if (keyCode == KeyEvent.KEYCODE_S && event.isMetaPressed() + && event.isCtrlPressed()) { + if (down && repeatCount == 0) { + int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION + : TAKE_SCREENSHOT_FULLSCREEN; + mScreenshotRunnable.setScreenshotType(type); + mHandler.post(mScreenshotRunnable); + return -1; + } } else if (keyCode == KeyEvent.KEYCODE_SLASH && event.isMetaPressed()) { if (down) { if (repeatCount == 0) { @@ -3073,6 +3096,7 @@ } } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) { if (down && repeatCount == 0) { + mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); mHandler.post(mScreenshotRunnable); } return -1; @@ -4402,9 +4426,11 @@ "Laying out navigation bar window: (%d,%d - %d,%d)", pf.left, pf.top, pf.right, pf.bottom)); } else if ((attrs.type == TYPE_SECURE_SYSTEM_OVERLAY - || attrs.type == TYPE_BOOT_PROGRESS) + || attrs.type == TYPE_BOOT_PROGRESS + || attrs.type == TYPE_SCREENSHOT) && ((fl & FLAG_FULLSCREEN) != 0)) { - // Fullscreen secure system overlays get what they ask for. + // Fullscreen secure system overlays get what they ask for. Screenshot region + // selection overlay should also expand to full screen. pf.left = df.left = of.left = cf.left = mOverscanScreenLeft; pf.top = df.top = of.top = cf.top = mOverscanScreenTop; pf.right = df.right = of.right = cf.right = mOverscanScreenLeft @@ -5159,7 +5185,7 @@ }; // Assume this is called from the Handler thread. - private void takeScreenshot() { + private void takeScreenshot(final int screenshotType) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { return; @@ -5176,7 +5202,7 @@ return; } Messenger messenger = new Messenger(service); - Message msg = Message.obtain(null, 1); + Message msg = Message.obtain(null, screenshotType); final ServiceConnection myConn = this; Handler h = new Handler(mHandler.getLooper()) { @Override