Fork GlobalScreenshot into notif vs corner flows

Moves GlobalScreenshot into two different files: GlobalScreenshot
(with the new corner flow) and GlobalScreenshotLegacy (with the
previous notifications flow). This is to allow easier iteration on
the new flow; once it is fully tested, the notification/legacy
version will eventually be removed.

Bug: 137153302
Bug: 147423001
Test: tested old and new flows to ensure no user-visible changes; also
tested smart screenshots in old flow to ensure no behavior change
Change-Id: Ia776e2a7c2e8d0d298ea3e0f18f3419aa2b64415
This commit is contained in:
Miranda Kephart
2020-01-10 11:18:51 -05:00
parent 8268f08062
commit 63e65a9626
6 changed files with 603 additions and 139 deletions

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
<!-- Copyright (C) 2020 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.

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView android:id="@+id/global_screenshot_legacy_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@android:color/black"
android:visibility="gone" />
<ImageView android:id="@+id/global_screenshot_legacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/screenshot_panel"
android:visibility="gone"
android:adjustViewBounds="true" />
<ImageView android:id="@+id/global_screenshot_legacy_flash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@android:color/white"
android:visibility="gone" />
<com.android.systemui.screenshot.ScreenshotSelectorView
android:id="@+id/global_screenshot_legacy_selector"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:pointerIcon="crosshair"/>
</FrameLayout>

View File

@@ -288,6 +288,7 @@
<!-- Dimensions related to screenshots -->
<!-- The padding on the global screenshot background image -->
<dimen name="global_screenshot_legacy_bg_padding">20dp</dimen>
<dimen name="global_screenshot_bg_padding">20dp</dimen>
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
<dimen name="screenshot_action_container_padding">20dp</dimen>

View File

@@ -16,11 +16,9 @@
package com.android.systemui.screenshot;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
import android.animation.Animator;
@@ -50,7 +48,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
@@ -246,20 +243,13 @@ public class GlobalScreenshot {
mSaveInBgTask.cancel(false);
}
if (!DeviceConfig.getBoolean(
NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false)) {
mNotificationsController.reset();
mNotificationsController.setImage(mScreenBitmap);
mNotificationsController.showSavingScreenshotNotification();
}
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
}
/**
* Takes a screenshot of the current display and shows an animation.
*/
private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
boolean navBarVisible, Rect crop) {
private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
int rot = mDisplay.getRotation();
int width = crop.width();
int height = crop.height();
@@ -278,21 +268,20 @@ public class GlobalScreenshot {
mScreenBitmap.prepareToDraw();
// Start the post-screenshot animation
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
statusBarVisible, navBarVisible);
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
}
void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
void takeScreenshot(Consumer<Uri> finisher) {
mDisplay.getRealMetrics(mDisplayMetrics);
takeScreenshot(finisher, statusBarVisible, navBarVisible,
takeScreenshot(
finisher,
new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
}
/**
* Displays a screenshot selector
*/
void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
final boolean navBarVisible) {
void takeScreenshotPartial(final Consumer<Uri> finisher) {
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
@Override
@@ -312,8 +301,7 @@ public class GlobalScreenshot {
if (rect != null) {
if (rect.width() != 0 && rect.height() != 0) {
// Need mScreenshotLayout to handle it after the view disappears
mScreenshotLayout.post(() -> takeScreenshot(
finisher, statusBarVisible, navBarVisible, rect));
mScreenshotLayout.post(() -> takeScreenshot(finisher, rect));
}
}
@@ -364,8 +352,7 @@ public class GlobalScreenshot {
/**
* Starts the animation after taking the screenshot
*/
private void startAnimation(final Consumer<Uri> finisher, int w, int h,
boolean statusBarVisible, boolean navBarVisible) {
private void startAnimation(final Consumer<Uri> finisher, int w, int h) {
// 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);
@@ -385,50 +372,30 @@ public class GlobalScreenshot {
mScreenshotAnimation.removeAllListeners();
}
boolean useCornerFlow =
DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
ValueAnimator screenshotFadeOutAnim = useCornerFlow
? createScreenshotToCornerAnimation(w, h)
: createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible);
ValueAnimator screenshotFadeOutAnim = createScreenshotToCornerAnimation(w, h);
mScreenshotAnimation = new AnimatorSet();
mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Save the screenshot once we have a bit of time now
if (!useCornerFlow) {
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
@Override
void onActionsReady(Uri uri, List<Notification.Action> actions) {
if (uri == null) {
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
} else {
mNotificationsController
.showScreenshotActionsNotification(uri, actions);
}
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
@Override
void onActionsReady(Uri uri, List<Notification.Action> actions) {
if (uri == null) {
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
} else {
mScreenshotHandler.post(() ->
createScreenshotActionsShadeAnimation(actions).start());
}
});
clearScreenshot();
} else {
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
@Override
void onActionsReady(Uri uri, List<Notification.Action> actions) {
if (uri == null) {
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
} else {
mScreenshotHandler.post(() ->
createScreenshotActionsShadeAnimation(actions).start());
}
}
});
mScreenshotHandler.sendMessageDelayed(
mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
SCREENSHOT_CORNER_TIMEOUT_MILLIS);
}
}
});
mScreenshotHandler.sendMessageDelayed(
mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
SCREENSHOT_CORNER_TIMEOUT_MILLIS);
}
});
mScreenshotHandler.post(() -> {
@@ -492,7 +459,7 @@ public class GlobalScreenshot {
}
@Override
public void onAnimationEnd(android.animation.Animator animation) {
public void onAnimationEnd(Animator animation) {
mScreenshotFlash.setVisibility(View.GONE);
}
});
@@ -513,81 +480,6 @@ public class GlobalScreenshot {
return anim;
}
private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
boolean navBarVisible) {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBackgroundView.setVisibility(View.GONE);
mScreenshotView.setVisibility(View.GONE);
mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
if (!statusBarVisible || !navBarVisible) {
// There is no status bar/nav bar, so just fade the screenshot away in place
anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float t = (Float) animation.getAnimatedValue();
float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
- t * (SCREENSHOT_DROP_IN_MIN_SCALE
- SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
mScreenshotView.setAlpha(1f - t);
mScreenshotView.setScaleX(scaleT);
mScreenshotView.setScaleY(scaleT);
}
});
} else {
// In the case where there is a status bar, animate to the origin of the bar (top-left)
final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
/ SCREENSHOT_DROP_OUT_DURATION;
final Interpolator scaleInterpolator = new Interpolator() {
@Override
public float getInterpolation(float x) {
if (x < scaleDurationPct) {
// Decelerate, and scale the input accordingly
return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
}
return 1f;
}
};
// Determine the bounds of how to scale
float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
final PointF finalPos = new PointF(
-halfScreenWidth
+ (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
-halfScreenHeight
+ (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
// Animate the screenshot to the status bar
anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float t = (Float) animation.getAnimatedValue();
float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
- scaleInterpolator.getInterpolation(t)
* (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
mScreenshotView.setScaleX(scaleT);
mScreenshotView.setScaleY(scaleT);
mScreenshotView.setTranslationX(t * finalPos.x);
mScreenshotView.setTranslationY(t * finalPos.y);
}
});
}
return anim;
}
private ValueAnimator createScreenshotToCornerAnimation(int w, int h) {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
@@ -776,8 +668,7 @@ public class GlobalScreenshot {
String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
Slog.d(TAG, "Executing smart action [" + actionType + "]:" + actionIntent);
ActivityOptions opts = ActivityOptions.makeBasic();
context.startActivityAsUser(actionIntent, opts.toBundle(),
UserHandle.CURRENT);
context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
ScreenshotSmartActions.notifyScreenshotAction(
context, intent.getStringExtra(EXTRA_ID), actionType, true);

View File

@@ -0,0 +1,508 @@
/*
* Copyright (C) 2011 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 static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
import android.media.MediaActionSound;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.PowerManager;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.Toast;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import java.util.List;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Class for handling device screen shots
*
* @deprecated will be removed when corner flow is complete and tested
*/
@Singleton
@Deprecated
public class GlobalScreenshotLegacy {
// These strings are used for communicating the action invoked to
// ScreenshotNotificationSmartActionsProvider.
static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
static final String EXTRA_ID = "android:screenshot_id";
static final String ACTION_TYPE_DELETE = "Delete";
static final String ACTION_TYPE_SHARE = "Share";
static final String ACTION_TYPE_EDIT = "Edit";
static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
private static final String TAG = "GlobalScreenshot";
private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
private static final int SCREENSHOT_DROP_IN_DURATION = 430;
private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
private static final float BACKGROUND_ALPHA = 0.5f;
private static final float SCREENSHOT_SCALE = 1f;
private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
private final ScreenshotNotificationsController mNotificationsController;
private Context mContext;
private WindowManager mWindowManager;
private WindowManager.LayoutParams mWindowLayoutParams;
private Display mDisplay;
private DisplayMetrics mDisplayMetrics;
private Bitmap mScreenBitmap;
private View mScreenshotLayout;
private ScreenshotSelectorView mScreenshotSelectorView;
private ImageView mBackgroundView;
private ImageView mScreenshotView;
private ImageView mScreenshotFlash;
private AnimatorSet mScreenshotAnimation;
private float mBgPadding;
private float mBgPaddingScale;
private AsyncTask<Void, Void, Void> mSaveInBgTask;
private MediaActionSound mCameraSound;
/**
* @param context everything needs a context :(
*/
@Inject
public GlobalScreenshotLegacy(
Context context, @Main Resources resources, LayoutInflater layoutInflater,
ScreenshotNotificationsController screenshotNotificationsController) {
mContext = context;
mNotificationsController = screenshotNotificationsController;
// Inflate the screenshot layout
mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot_legacy, null);
mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_background);
mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy);
mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_flash);
mScreenshotSelectorView = mScreenshotLayout.findViewById(
R.id.global_screenshot_legacy_selector);
mScreenshotLayout.setFocusable(true);
mScreenshotSelectorView.setFocusable(true);
mScreenshotSelectorView.setFocusableInTouchMode(true);
mScreenshotLayout.setOnTouchListener((v, event) -> {
// Intercept and ignore all touch events
return true;
});
// 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_SCREENSHOT,
WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
PixelFormat.TRANSLUCENT);
mWindowLayoutParams.setTitle("ScreenshotAnimation");
mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */);
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mDisplay = mWindowManager.getDefaultDisplay();
mDisplayMetrics = new DisplayMetrics();
mDisplay.getRealMetrics(mDisplayMetrics);
// Scale has to account for both sides of the bg
mBgPadding = (float) resources.getDimensionPixelSize(
R.dimen.global_screenshot_legacy_bg_padding);
mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
// Setup the Camera shutter sound
mCameraSound = new MediaActionSound();
mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
}
/**
* Creates a new worker thread and saves the screenshot to the media store.
*/
private void saveScreenshotInWorkerThread(
Consumer<Uri> finisher,
@Nullable GlobalScreenshot.ActionsReadyListener actionsReadyListener) {
GlobalScreenshot.SaveImageInBackgroundData data =
new GlobalScreenshot.SaveImageInBackgroundData();
data.image = mScreenBitmap;
data.finisher = finisher;
data.mActionsReadyListener = actionsReadyListener;
if (mSaveInBgTask != null) {
mSaveInBgTask.cancel(false);
}
mNotificationsController.reset();
mNotificationsController.setImage(mScreenBitmap);
mNotificationsController.showSavingScreenshotNotification();
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
}
/**
* Takes a screenshot of the current display and shows an animation.
*/
private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
boolean navBarVisible, Rect crop) {
int rot = mDisplay.getRotation();
int width = crop.width();
int height = crop.height();
// Take the screenshot
mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
if (mScreenBitmap == null) {
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
finisher.accept(null);
return;
}
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
// Start the post-screenshot animation
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
statusBarVisible, navBarVisible);
}
void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
mDisplay.getRealMetrics(mDisplayMetrics);
takeScreenshot(finisher, statusBarVisible, navBarVisible,
new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
}
/**
* Displays a screenshot selector
*/
void takeScreenshotPartial(final Consumer<Uri> 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(() -> takeScreenshot(
finisher, statusBarVisible, navBarVisible, rect));
}
}
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();
}
}
/**
* Clears current screenshot
*/
private void clearScreenshot() {
if (mScreenshotLayout.isAttachedToWindow()) {
mWindowManager.removeView(mScreenshotLayout);
}
// Clear any references to the bitmap
mScreenBitmap = null;
mScreenshotView.setImageBitmap(null);
mBackgroundView.setVisibility(View.GONE);
mScreenshotView.setVisibility(View.GONE);
mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
}
/**
* Starts the animation after taking the screenshot
*/
private void startAnimation(final Consumer<Uri> finisher, int w, int h,
boolean statusBarVisible, boolean navBarVisible) {
// 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);
if (powerManager.isPowerSaveMode()) {
Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
}
// Add the view for the animation
mScreenshotView.setImageBitmap(mScreenBitmap);
mScreenshotLayout.requestFocus();
// Setup the animation with the screenshot just taken
if (mScreenshotAnimation != null) {
if (mScreenshotAnimation.isStarted()) {
mScreenshotAnimation.end();
}
mScreenshotAnimation.removeAllListeners();
}
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
statusBarVisible, navBarVisible);
mScreenshotAnimation = new AnimatorSet();
mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Save the screenshot once we have a bit of time now
saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
@Override
void onActionsReady(Uri uri, List<Notification.Action> actions) {
if (uri == null) {
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
} else {
mNotificationsController.showScreenshotActionsNotification(
uri, actions);
}
}
});
clearScreenshot();
}
});
mScreenshotLayout.post(() -> {
// Play the shutter sound to notify that we've taken a screenshot
mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mScreenshotView.buildLayer();
mScreenshotAnimation.start();
});
}
private ValueAnimator createScreenshotDropInAnimation() {
final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
/ SCREENSHOT_DROP_IN_DURATION);
final float flashDurationPct = 2f * flashPeakDurationPct;
final Interpolator flashAlphaInterpolator = new Interpolator() {
@Override
public float getInterpolation(float x) {
// Flash the flash view in and out quickly
if (x <= flashDurationPct) {
return (float) Math.sin(Math.PI * (x / flashDurationPct));
}
return 0;
}
};
final Interpolator scaleInterpolator = new Interpolator() {
@Override
public float getInterpolation(float x) {
// We start scaling when the flash is at it's peak
if (x < flashPeakDurationPct) {
return 0;
}
return (x - flashDurationPct) / (1f - flashDurationPct);
}
};
Resources r = mContext.getResources();
if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
== Configuration.UI_MODE_NIGHT_YES) {
mScreenshotView.getBackground().setTint(Color.BLACK);
} else {
mScreenshotView.getBackground().setTintList(null);
}
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.setTranslationX(0f);
mScreenshotView.setTranslationY(0f);
mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
mScreenshotView.setVisibility(View.VISIBLE);
mScreenshotFlash.setAlpha(0f);
mScreenshotFlash.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(android.animation.Animator animation) {
mScreenshotFlash.setVisibility(View.GONE);
}
});
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator 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);
mScreenshotView.setScaleX(scaleT);
mScreenshotView.setScaleY(scaleT);
mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
}
});
return anim;
}
private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
boolean navBarVisible) {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBackgroundView.setVisibility(View.GONE);
mScreenshotView.setVisibility(View.GONE);
mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
if (!statusBarVisible || !navBarVisible) {
// There is no status bar/nav bar, so just fade the screenshot away in place
anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float t = (Float) animation.getAnimatedValue();
float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
- t * (SCREENSHOT_DROP_IN_MIN_SCALE
- SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
mScreenshotView.setAlpha(1f - t);
mScreenshotView.setScaleX(scaleT);
mScreenshotView.setScaleY(scaleT);
}
});
} else {
// In the case where there is a status bar, animate to the origin of the bar (top-left)
final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
/ SCREENSHOT_DROP_OUT_DURATION;
final Interpolator scaleInterpolator = new Interpolator() {
@Override
public float getInterpolation(float x) {
if (x < scaleDurationPct) {
// Decelerate, and scale the input accordingly
return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
}
return 1f;
}
};
// Determine the bounds of how to scale
float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
final PointF finalPos = new PointF(
-halfScreenWidth
+ (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
-halfScreenHeight
+ (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
// Animate the screenshot to the status bar
anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float t = (Float) animation.getAnimatedValue();
float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
- scaleInterpolator.getInterpolation(t)
* (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
mScreenshotView.setScaleX(scaleT);
mScreenshotView.setScaleY(scaleT);
mScreenshotView.setTranslationX(t * finalPos.x);
mScreenshotView.setTranslationY(t * finalPos.y);
}
});
}
return anim;
}
}

View File

@@ -16,15 +16,21 @@
package com.android.systemui.screenshot;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.util.Log;
import android.view.WindowManager;
@@ -36,9 +42,10 @@ public class TakeScreenshotService extends Service {
private static final String TAG = "TakeScreenshotService";
private final GlobalScreenshot mScreenshot;
private final GlobalScreenshotLegacy mScreenshotLegacy;
private final UserManager mUserManager;
private Handler mHandler = new Handler() {
private Handler mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
final Messenger callback = msg.replyTo;
@@ -59,12 +66,24 @@ public class TakeScreenshotService extends Service {
return;
}
// TODO (mkephart): clean up once notifications flow is fully deprecated
boolean useCornerFlow = DeviceConfig.getBoolean(
NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
if (useCornerFlow) {
mScreenshot.takeScreenshot(finisher);
} else {
mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
}
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
if (useCornerFlow) {
mScreenshot.takeScreenshotPartial(finisher);
} else {
mScreenshotLegacy.takeScreenshotPartial(
finisher, msg.arg1 > 0, msg.arg2 > 0);
}
break;
default:
Log.d(TAG, "Invalid screenshot option: " + msg.what);
@@ -73,8 +92,10 @@ public class TakeScreenshotService extends Service {
};
@Inject
public TakeScreenshotService(GlobalScreenshot globalScreenshot, UserManager userManager) {
public TakeScreenshotService(GlobalScreenshot globalScreenshot,
GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) {
mScreenshot = globalScreenshot;
mScreenshotLegacy = globalScreenshotLegacy;
mUserManager = userManager;
}
@@ -86,6 +107,7 @@ public class TakeScreenshotService extends Service {
@Override
public boolean onUnbind(Intent intent) {
if (mScreenshot != null) mScreenshot.stopScreenshot();
if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot();
return true;
}
}