Screenshot helper - add method that allows the caller to supply bitmap.

Add a method to screenshot helper that allows the caller to provide the
bitmap to be treated as a screen shot.
First use Overview can provide a task snapshot to use as a screenshot.

Test: update tests, local
Bug: 145297320

Change-Id: I4d21906212797bf394094d7a29208be02a6bfd7e
This commit is contained in:
Zak Cohen
2020-01-17 11:16:00 -08:00
parent 45a91b17f7
commit ebd3b8b1e9
8 changed files with 259 additions and 16 deletions

View File

@@ -452,13 +452,48 @@ public interface WindowManager extends ViewManager {
* Message for taking fullscreen screenshot
* @hide
*/
final int TAKE_SCREENSHOT_FULLSCREEN = 1;
int TAKE_SCREENSHOT_FULLSCREEN = 1;
/**
* Message for taking screenshot of selected region.
* @hide
*/
final int TAKE_SCREENSHOT_SELECTED_REGION = 2;
int TAKE_SCREENSHOT_SELECTED_REGION = 2;
/**
* Message for handling a screenshot flow with an image provided by the caller.
* @hide
*/
int TAKE_SCREENSHOT_PROVIDED_IMAGE = 3;
/**
* Parcel key for the screen shot bitmap sent with messages of type
* {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}, type {@link android.graphics.Bitmap}
* @hide
*/
String PARCEL_KEY_SCREENSHOT_BITMAP = "screenshot_screen_bitmap";
/**
* Parcel key for the screen bounds of the image sent with messages of type
* [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link Rect} in screen coordinates.
* @hide
*/
String PARCEL_KEY_SCREENSHOT_BOUNDS = "screenshot_screen_bounds";
/**
* Parcel key for the task id of the task that the screen shot was taken of, sent with messages
* of type [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type int.
* @hide
*/
String PARCEL_KEY_SCREENSHOT_TASK_ID = "screenshot_task_id";
/**
* Parcel key for the visible insets of the image sent with messages of type
* [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link android.graphics.Insets} in
* screen coordinates.
* @hide
*/
String PARCEL_KEY_SCREENSHOT_INSETS = "screenshot_insets";
/**
* @hide

View File

@@ -6,7 +6,11 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -14,6 +18,7 @@ import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.WindowManager;
import java.util.function.Consumer;
@@ -38,9 +43,9 @@ public class ScreenshotHelper {
* is recommended for general use.
*
* @param screenshotType The type of screenshot, for example either
* {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN}
* {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
* or
* {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION}
* {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
* @param hasStatus {@code true} if the status bar is currently showing. {@code false}
* if
* not.
@@ -65,9 +70,9 @@ public class ScreenshotHelper {
* is recommended for general use.
*
* @param screenshotType The type of screenshot, for example either
* {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN}
* {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
* or
* {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION}
* {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
* @param hasStatus {@code true} if the status bar is currently showing. {@code false}
* if
* not.
@@ -84,6 +89,40 @@ public class ScreenshotHelper {
public void takeScreenshot(final int screenshotType, final boolean hasStatus,
final boolean hasNav, long timeoutMs, @NonNull Handler handler,
@Nullable Consumer<Uri> completionConsumer) {
takeScreenshot(screenshotType, hasStatus, hasNav, timeoutMs, handler, null,
completionConsumer
);
}
/**
* Request that provided image be handled as if it was a screenshot.
*
* @param screenshot The bitmap to treat as the screen shot.
* @param boundsInScreen The bounds in screen coordinates that the bitmap orginated from.
* @param insets The insets that the image was shown with, inside the screenbounds.
* @param taskId The taskId of the task that the screen shot was taken of.
* @param handler A handler used in case the screenshot times out
* @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
* screenshot was taken.
*/
public void provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen,
@NonNull Insets insets, int taskId, @NonNull Handler handler,
@Nullable Consumer<Uri> completionConsumer) {
Bundle imageBundle = new Bundle();
imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP, screenshot);
imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS, boundsInScreen);
imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_INSETS, insets);
imageBundle.putInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID, taskId);
takeScreenshot(
WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE,
false, false, // ignored when image bundle is set
SCREENSHOT_TIMEOUT_MS, handler, imageBundle, completionConsumer);
}
private void takeScreenshot(final int screenshotType, final boolean hasStatus,
final boolean hasNav, long timeoutMs, @NonNull Handler handler,
@Nullable Bundle providedImage, @Nullable Consumer<Uri> completionConsumer) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
return;
@@ -139,6 +178,10 @@ public class ScreenshotHelper {
msg.arg1 = hasStatus ? 1 : 0;
msg.arg2 = hasNav ? 1 : 0;
if (screenshotType == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
msg.setData(providedImage);
}
try {
messenger.send(msg);
} catch (RemoteException e) {

View File

@@ -31,6 +31,9 @@ import static org.mockito.Mockito.mock;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
@@ -84,6 +87,13 @@ public final class ScreenshotHelperTest {
null);
}
@Test
public void testProvidedImageScreenshot() {
mScreenshotHelper.provideScreenshot(
Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), new Rect(),
Insets.of(0, 0, 0, 0), 1, mHandler, null);
}
@Test
public void testScreenshotTimesOut() {
long timeoutMs = 10;

View File

@@ -16,12 +16,15 @@
package com.android.systemui.shared.recents;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.MotionEvent;
/**
* Temporary callbacks into SystemUI.
* Next id = 22
*/
interface ISystemUiProxy {
@@ -114,4 +117,10 @@ interface ISystemUiProxy {
* Sets the shelf height and visibility.
*/
void setShelfHeight(boolean visible, int shelfHeight) = 20;
/**
* Handle the provided image as if it was a screenshot.
*/
void handleImageAsScreenshot(in Bitmap screenImage, in Rect locationInScreen,
in Insets visibleInsets, int taskId) = 21;
}

View File

@@ -38,6 +38,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
@@ -55,6 +57,7 @@ import android.view.MotionEvent;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.Dumpable;
import com.android.systemui.model.SysUiState;
import com.android.systemui.pip.PipUI;
@@ -115,6 +118,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private final DeviceProvisionedController mDeviceProvisionedController;
private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
private final Intent mQuickStepIntent;
private final ScreenshotHelper mScreenshotHelper;
private Region mActiveNavBarRegion;
@@ -365,6 +369,13 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
}
@Override
public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
Insets visibleInsets, int taskId) {
mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets,
taskId, mHandler, null);
}
private boolean verifyCaller(String reason) {
final int callerId = Binder.getCallingUserHandle().getIdentifier();
if (callerId != mCurrentBoundedUserId) {
@@ -518,6 +529,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
// Listen for status bar state changes
statusBarWinController.registerCallback(mStatusBarWindowCallback);
mScreenshotHelper = new ScreenshotHelper(context);
}
public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,

View File

@@ -29,6 +29,7 @@ import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.Notification;
@@ -38,6 +39,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.PixelFormat;
import android.graphics.PointF;
@@ -300,8 +302,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
int width = crop.width();
int height = crop.height();
// Take the screenshot
mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, null);
}
private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect) {
mScreenBitmap = screenshot;
if (mScreenBitmap == null) {
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
@@ -317,7 +322,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
// Start the post-screenshot animation
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
screenRect);
}
void takeScreenshot(Consumer<Uri> finisher) {
@@ -327,9 +333,16 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
}
void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
Insets visibleInsets, int taskId, Consumer<Uri> finisher) {
// TODO use taskId and visibleInsets
takeScreenshot(screenshot, finisher, screenshotScreenBounds);
}
/**
* Displays a screenshot selector
*/
@SuppressLint("ClickableViewAccessibility")
void takeScreenshotPartial(final Consumer<Uri> finisher) {
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
@@ -402,7 +415,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
/**
* Starts the animation after taking the screenshot
*/
private void startAnimation(final Consumer<Uri> finisher, int w, int h) {
private void startAnimation(final Consumer<Uri> finisher, int w, int h,
@Nullable Rect screenRect) {
// If power save is on, show a toast so there is some visual indication that a screenshot
// has been taken.
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -422,7 +436,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenshotAnimation.removeAllListeners();
}
ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
ValueAnimator screenshotDropInAnim = screenRect != null ? createRectAnimation(screenRect)
: createScreenshotDropInAnimation();
ValueAnimator screenshotFadeOutAnim = createScreenshotToCornerAnimation(w, h);
mScreenshotAnimation = new AnimatorSet();
mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
@@ -460,6 +475,46 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
});
}
private ValueAnimator createRectAnimation(Rect rect) {
mScreenshotView.setAdjustViewBounds(true);
mScreenshotView.setMaxHeight(rect.height());
mScreenshotView.setMaxWidth(rect.width());
final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
/ SCREENSHOT_DROP_IN_DURATION);
final float flashDurationPct = 2f * flashPeakDurationPct;
final Interpolator scaleInterpolator = x -> {
// We start scaling when the flash is at it's peak
if (x < flashPeakDurationPct) {
return 0;
}
return (x - flashDurationPct) / (1f - flashDurationPct);
};
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mBackgroundView.setAlpha(0f);
mBackgroundView.setVisibility(View.VISIBLE);
mScreenshotView.setAlpha(0f);
mScreenshotView.setElevation(0f);
mScreenshotView.setTranslationX(0f);
mScreenshotView.setTranslationY(0f);
mScreenshotView.setScaleX(1f);
mScreenshotView.setScaleY(1f);
mScreenshotView.setVisibility(View.VISIBLE);
}
});
anim.addUpdateListener(animation -> {
float t = (Float) animation.getAnimatedValue();
mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
mScreenshotView.setAlpha(t);
});
return anim;
}
private ValueAnimator createScreenshotDropInAnimation() {
final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
/ SCREENSHOT_DROP_IN_DURATION);

View File

@@ -30,6 +30,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -205,8 +206,13 @@ public class GlobalScreenshotLegacy {
int width = crop.width();
int height = crop.height();
// Take the screenshot
mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher,
statusBarVisible, navBarVisible, null);
}
private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, boolean statusBarVisible,
boolean navBarVisible, Rect screenboundsOfBitmap) {
mScreenBitmap = screenshot;
if (mScreenBitmap == null) {
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
@@ -220,7 +226,7 @@ public class GlobalScreenshotLegacy {
// Start the post-screenshot animation
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
statusBarVisible, navBarVisible);
statusBarVisible, navBarVisible, screenboundsOfBitmap);
}
void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
@@ -229,6 +235,12 @@ public class GlobalScreenshotLegacy {
new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
}
void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
Insets visibleInsets, int taskId, Consumer<Uri> finisher) {
// TODO use taskId and visibleInsets
takeScreenshot(screenshot, finisher, false, false, screenshotScreenBounds);
}
/**
* Displays a screenshot selector
*/
@@ -302,7 +314,7 @@ public class GlobalScreenshotLegacy {
* Starts the animation after taking the screenshot
*/
private void startAnimation(final Consumer<Uri> finisher, int w, int h,
boolean statusBarVisible, boolean navBarVisible) {
boolean statusBarVisible, boolean navBarVisible, @Nullable Rect screenBoundsOfBitmap) {
// If power save is on, show a toast so there is some visual indication that a screenshot
// has been taken.
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -323,7 +335,8 @@ public class GlobalScreenshotLegacy {
}
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
ValueAnimator screenshotDropInAnim = screenBoundsOfBitmap != null
? createRectAnimation(screenBoundsOfBitmap) : createScreenshotDropInAnimation();
ValueAnimator screenshotFadeOutAnim =
createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible);
mScreenshotAnimation = new AnimatorSet();
@@ -430,6 +443,53 @@ public class GlobalScreenshotLegacy {
return anim;
}
/**
* If a bitmap was supplied to be used as the screenshot, animated from where that bitmap was
* on screen, rather than using the whole screen.
*/
private ValueAnimator createRectAnimation(Rect rect) {
mScreenshotView.setAdjustViewBounds(true);
mScreenshotView.setMaxHeight(rect.height());
mScreenshotView.setMaxWidth(rect.width());
final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
/ SCREENSHOT_DROP_IN_DURATION);
final float flashDurationPct = 2f * flashPeakDurationPct;
final Interpolator scaleInterpolator = x -> {
// We start scaling when the flash is at it's peak
if (x < flashPeakDurationPct) {
return 0;
}
return (x - flashDurationPct) / (1f - flashDurationPct);
};
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mBackgroundView.setAlpha(0f);
mBackgroundView.setVisibility(View.VISIBLE);
mScreenshotView.setAlpha(0f);
mScreenshotView.setElevation(0f);
mScreenshotView.setTranslationX(0f);
mScreenshotView.setTranslationY(0f);
mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
mScreenshotView.setVisibility(View.VISIBLE);
}
});
anim.addUpdateListener(animation -> {
float t = (Float) animation.getAnimatedValue();
float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
- scaleInterpolator.getInterpolation(t)
* (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
mScreenshotView.setAlpha(t);
});
return anim;
}
private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
boolean navBarVisible) {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);

View File

@@ -22,6 +22,9 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREEN
import android.app.Service;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
@@ -85,6 +88,22 @@ public class TakeScreenshotService extends Service {
finisher, msg.arg1 > 0, msg.arg2 > 0);
}
break;
case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
Bitmap screenshot = msg.getData().getParcelable(
WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP);
Rect screenBounds = msg.getData().getParcelable(
WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS);
Insets insets = msg.getData().getParcelable(
WindowManager.PARCEL_KEY_SCREENSHOT_INSETS);
int taskId = msg.getData().getInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID);
if (useCornerFlow) {
mScreenshot.handleImageAsScreenshot(
screenshot, screenBounds, insets, taskId, finisher);
} else {
mScreenshotLegacy.handleImageAsScreenshot(
screenshot, screenBounds, insets, taskId, finisher);
}
break;
default:
Log.d(TAG, "Invalid screenshot option: " + msg.what);
}