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