diff options
| author | 2020-01-10 11:18:51 -0500 | |
|---|---|---|
| committer | 2020-01-13 16:23:06 +0000 | |
| commit | 63e65a9626eca0bedc1e43dfe8c97e4b1cbae544 (patch) | |
| tree | 13c06b94d248084448ec0e5102fa4a41997df020 | |
| parent | 8268f08062e8c54f9546fe60881aca908bc8086d (diff) | |
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
6 files changed, 603 insertions, 139 deletions
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index 6ac9da4291c6..995cb7d48e60 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -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. diff --git a/packages/SystemUI/res/layout/global_screenshot_legacy.xml b/packages/SystemUI/res/layout/global_screenshot_legacy.xml new file mode 100644 index 000000000000..791c7ea875fe --- /dev/null +++ b/packages/SystemUI/res/layout/global_screenshot_legacy.xml @@ -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> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index da0323ac7357..5a1151eb9f6e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -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> diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 02c4beb7ac1b..91d5457c790c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -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); - } - } - }); - 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()); - } + 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); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java new file mode 100644 index 000000000000..11aa80bbea35 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java @@ -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; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 29b96a90a734..4f045d5c1b71 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -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; } } |