Home | History | Annotate | Download | only in screenshot
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.systemui.screenshot;
     18 
     19 import static android.content.Context.NOTIFICATION_SERVICE;
     20 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
     21 
     22 import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT;
     23 import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION;
     24 import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP;
     25 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
     26 
     27 import android.animation.Animator;
     28 import android.animation.AnimatorListenerAdapter;
     29 import android.animation.AnimatorSet;
     30 import android.animation.ValueAnimator;
     31 import android.animation.ValueAnimator.AnimatorUpdateListener;
     32 import android.app.ActivityOptions;
     33 import android.app.Notification;
     34 import android.app.Notification.BigPictureStyle;
     35 import android.app.NotificationManager;
     36 import android.app.PendingIntent;
     37 import android.app.admin.DevicePolicyManager;
     38 import android.content.BroadcastReceiver;
     39 import android.content.ClipData;
     40 import android.content.ClipDescription;
     41 import android.content.ComponentName;
     42 import android.content.ContentResolver;
     43 import android.content.Context;
     44 import android.content.Intent;
     45 import android.content.res.Configuration;
     46 import android.content.res.Resources;
     47 import android.graphics.Bitmap;
     48 import android.graphics.Canvas;
     49 import android.graphics.Color;
     50 import android.graphics.ColorMatrix;
     51 import android.graphics.ColorMatrixColorFilter;
     52 import android.graphics.Matrix;
     53 import android.graphics.Paint;
     54 import android.graphics.Picture;
     55 import android.graphics.PixelFormat;
     56 import android.graphics.PointF;
     57 import android.graphics.Rect;
     58 import android.media.MediaActionSound;
     59 import android.net.Uri;
     60 import android.os.AsyncTask;
     61 import android.os.Environment;
     62 import android.os.PowerManager;
     63 import android.os.Process;
     64 import android.os.UserHandle;
     65 import android.provider.MediaStore;
     66 import android.text.TextUtils;
     67 import android.util.DisplayMetrics;
     68 import android.util.Slog;
     69 import android.view.Display;
     70 import android.view.LayoutInflater;
     71 import android.view.MotionEvent;
     72 import android.view.SurfaceControl;
     73 import android.view.View;
     74 import android.view.ViewGroup;
     75 import android.view.WindowManager;
     76 import android.view.animation.Interpolator;
     77 import android.widget.ImageView;
     78 import android.widget.Toast;
     79 
     80 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
     81 import com.android.systemui.R;
     82 import com.android.systemui.SysUiServiceProvider;
     83 import com.android.systemui.SystemUI;
     84 import com.android.systemui.shared.system.ActivityManagerWrapper;
     85 import com.android.systemui.statusbar.phone.StatusBar;
     86 import com.android.systemui.util.NotificationChannels;
     87 
     88 import libcore.io.IoUtils;
     89 
     90 import java.io.IOException;
     91 import java.io.OutputStream;
     92 import java.text.DateFormat;
     93 import java.text.SimpleDateFormat;
     94 import java.util.Date;
     95 import java.util.concurrent.ExecutionException;
     96 import java.util.concurrent.TimeUnit;
     97 import java.util.concurrent.TimeoutException;
     98 
     99 
    100 
    101 /**
    102  * POD used in the AsyncTask which saves an image in the background.
    103  */
    104 class SaveImageInBackgroundData {
    105     Context context;
    106     Bitmap image;
    107     Uri imageUri;
    108     Runnable finisher;
    109     int iconSize;
    110     int previewWidth;
    111     int previewheight;
    112     int errorMsgResId;
    113 
    114     void clearImage() {
    115         image = null;
    116         imageUri = null;
    117         iconSize = 0;
    118     }
    119     void clearContext() {
    120         context = null;
    121     }
    122 }
    123 
    124 /**
    125  * An AsyncTask that saves an image to the media store in the background.
    126  */
    127 class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
    128     private static final String TAG = "SaveImageInBackgroundTask";
    129 
    130     private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
    131     private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
    132 
    133     private final SaveImageInBackgroundData mParams;
    134     private final NotificationManager mNotificationManager;
    135     private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
    136     private final String mImageFileName;
    137     private final long mImageTime;
    138     private final BigPictureStyle mNotificationStyle;
    139     private final int mImageWidth;
    140     private final int mImageHeight;
    141 
    142     SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
    143             NotificationManager nManager) {
    144         Resources r = context.getResources();
    145 
    146         // Prepare all the output metadata
    147         mParams = data;
    148         mImageTime = System.currentTimeMillis();
    149         String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
    150         mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
    151 
    152         // Create the large notification icon
    153         mImageWidth = data.image.getWidth();
    154         mImageHeight = data.image.getHeight();
    155         int iconSize = data.iconSize;
    156         int previewWidth = data.previewWidth;
    157         int previewHeight = data.previewheight;
    158 
    159         Paint paint = new Paint();
    160         ColorMatrix desat = new ColorMatrix();
    161         desat.setSaturation(0.25f);
    162         paint.setColorFilter(new ColorMatrixColorFilter(desat));
    163         Matrix matrix = new Matrix();
    164         int overlayColor = 0x40FFFFFF;
    165 
    166         matrix.setTranslate((previewWidth - mImageWidth) / 2, (previewHeight - mImageHeight) / 2);
    167         Bitmap picture = generateAdjustedHwBitmap(data.image, previewWidth, previewHeight, matrix,
    168                 paint, overlayColor);
    169 
    170         // Note, we can't use the preview for the small icon, since it is non-square
    171         float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight);
    172         matrix.setScale(scale, scale);
    173         matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2,
    174                 (iconSize - (scale * mImageHeight)) / 2);
    175         Bitmap icon = generateAdjustedHwBitmap(data.image, iconSize, iconSize, matrix, paint,
    176                 overlayColor);
    177 
    178         mNotificationManager = nManager;
    179         final long now = System.currentTimeMillis();
    180 
    181         // Setup the notification
    182         mNotificationStyle = new Notification.BigPictureStyle()
    183                 .bigPicture(picture.createAshmemBitmap());
    184 
    185         // The public notification will show similar info but with the actual screenshot omitted
    186         mPublicNotificationBuilder =
    187                 new Notification.Builder(context, NotificationChannels.SCREENSHOTS_HEADSUP)
    188                         .setContentTitle(r.getString(R.string.screenshot_saving_title))
    189                         .setSmallIcon(R.drawable.stat_notify_image)
    190                         .setCategory(Notification.CATEGORY_PROGRESS)
    191                         .setWhen(now)
    192                         .setShowWhen(true)
    193                         .setColor(r.getColor(
    194                                 com.android.internal.R.color.system_notification_accent_color));
    195         SystemUI.overrideNotificationAppName(context, mPublicNotificationBuilder, true);
    196 
    197         mNotificationBuilder = new Notification.Builder(context,
    198                 NotificationChannels.SCREENSHOTS_HEADSUP)
    199             .setContentTitle(r.getString(R.string.screenshot_saving_title))
    200             .setSmallIcon(R.drawable.stat_notify_image)
    201             .setWhen(now)
    202             .setShowWhen(true)
    203             .setColor(r.getColor(com.android.internal.R.color.system_notification_accent_color))
    204             .setStyle(mNotificationStyle)
    205             .setPublicVersion(mPublicNotificationBuilder.build());
    206         mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
    207         SystemUI.overrideNotificationAppName(context, mNotificationBuilder, true);
    208 
    209         mNotificationManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT,
    210                 mNotificationBuilder.build());
    211 
    212         /**
    213          * NOTE: The following code prepares the notification builder for updating the notification
    214          * after the screenshot has been written to disk.
    215          */
    216 
    217         // On the tablet, the large icon makes the notification appear as if it is clickable (and
    218         // on small devices, the large icon is not shown) so defer showing the large icon until
    219         // we compose the final post-save notification below.
    220         mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
    221         // But we still don't set it for the expanded view, allowing the smallIcon to show here.
    222         mNotificationStyle.bigLargeIcon((Bitmap) null);
    223     }
    224 
    225     /**
    226      * Generates a new hardware bitmap with specified values, copying the content from the passed
    227      * in bitmap.
    228      */
    229     private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
    230             Paint paint, int color) {
    231         Picture picture = new Picture();
    232         Canvas canvas = picture.beginRecording(width, height);
    233         canvas.drawColor(color);
    234         canvas.drawBitmap(bitmap, matrix, paint);
    235         picture.endRecording();
    236         return Bitmap.createBitmap(picture);
    237     }
    238 
    239     @Override
    240     protected Void doInBackground(Void... paramsUnused) {
    241         if (isCancelled()) {
    242             return null;
    243         }
    244 
    245         // By default, AsyncTask sets the worker thread to have background thread priority, so bump
    246         // it back up so that we save a little quicker.
    247         Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
    248 
    249         Context context = mParams.context;
    250         Bitmap image = mParams.image;
    251         Resources r = context.getResources();
    252 
    253         try {
    254             // Save the screenshot to the MediaStore
    255             final MediaStore.PendingParams params = new MediaStore.PendingParams(
    256                     MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
    257             params.setPrimaryDirectory(Environment.DIRECTORY_PICTURES);
    258             params.setSecondaryDirectory(Environment.DIRECTORY_SCREENSHOTS);
    259 
    260             final Uri uri = MediaStore.createPending(context, params);
    261             final MediaStore.PendingSession session = MediaStore.openPending(context, uri);
    262             try {
    263                 try (OutputStream out = session.openOutputStream()) {
    264                     if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
    265                         throw new IOException("Failed to compress");
    266                     }
    267                 }
    268                 session.publish();
    269             } catch (Exception e) {
    270                 session.abandon();
    271                 throw e;
    272             } finally {
    273                 IoUtils.closeQuietly(session);
    274             }
    275 
    276             // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
    277             // order to do some common work like dismissing the keyguard and sending
    278             // closeSystemWindows
    279 
    280             // Create a share intent, this will always go through the chooser activity first which
    281             // should not trigger auto-enter PiP
    282             String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
    283             String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
    284             Intent sharingIntent = new Intent(Intent.ACTION_SEND);
    285             sharingIntent.setType("image/png");
    286             sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
    287             // Include URI in ClipData also, so that grantPermission picks it up.
    288             // We don't use setData here because some apps interpret this as "to:".
    289             ClipData clipdata = new ClipData(new ClipDescription("content",
    290                     new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
    291                     new ClipData.Item(uri));
    292             sharingIntent.setClipData(clipdata);
    293             sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
    294             sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    295 
    296             PendingIntent chooserAction = PendingIntent.getBroadcast(context, 0,
    297                     new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
    298                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
    299             Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null,
    300                     chooserAction.getIntentSender())
    301                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
    302                     .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    303 
    304             // Create a share action for the notification
    305             PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0,
    306                     new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
    307                             .putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
    308                             .putExtra(EXTRA_DISALLOW_ENTER_PIP, true),
    309                     PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
    310             Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
    311                     R.drawable.ic_screenshot_share,
    312                     r.getString(com.android.internal.R.string.share), shareAction);
    313             mNotificationBuilder.addAction(shareActionBuilder.build());
    314 
    315             // Create an edit intent, if a specific package is provided as the editor, then launch
    316             // that directly
    317             String editorPackage = context.getString(R.string.config_screenshotEditor);
    318             Intent editIntent = new Intent(Intent.ACTION_EDIT);
    319             if (!TextUtils.isEmpty(editorPackage)) {
    320                 editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
    321             }
    322             editIntent.setType("image/png");
    323             editIntent.setData(uri);
    324             editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    325             editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    326 
    327             // Create a edit action
    328             PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, 1,
    329                     new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
    330                             .putExtra(EXTRA_ACTION_INTENT, editIntent)
    331                             .putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null),
    332                     PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
    333             Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
    334                     R.drawable.ic_screenshot_edit,
    335                     r.getString(com.android.internal.R.string.screenshot_edit), editAction);
    336             mNotificationBuilder.addAction(editActionBuilder.build());
    337 
    338             // Create a delete action for the notification
    339             PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
    340                     new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
    341                             .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
    342                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
    343             Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
    344                     R.drawable.ic_screenshot_delete,
    345                     r.getString(com.android.internal.R.string.delete), deleteAction);
    346             mNotificationBuilder.addAction(deleteActionBuilder.build());
    347 
    348             mParams.imageUri = uri;
    349             mParams.image = null;
    350             mParams.errorMsgResId = 0;
    351         } catch (Exception e) {
    352             // IOException/UnsupportedOperationException may be thrown if external storage is not
    353             // mounted
    354             Slog.e(TAG, "unable to save screenshot", e);
    355             mParams.clearImage();
    356             mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
    357         }
    358 
    359         // Recycle the bitmap data
    360         if (image != null) {
    361             image.recycle();
    362         }
    363 
    364         return null;
    365     }
    366 
    367     @Override
    368     protected void onPostExecute(Void params) {
    369         if (mParams.errorMsgResId != 0) {
    370             // Show a message that we've failed to save the image to disk
    371             GlobalScreenshot.notifyScreenshotError(mParams.context, mNotificationManager,
    372                     mParams.errorMsgResId);
    373         } else {
    374             // Show the final notification to indicate screenshot saved
    375             Context context = mParams.context;
    376             Resources r = context.getResources();
    377 
    378             // Create the intent to show the screenshot in gallery
    379             Intent launchIntent = new Intent(Intent.ACTION_VIEW);
    380             launchIntent.setDataAndType(mParams.imageUri, "image/png");
    381             launchIntent.setFlags(
    382                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
    383 
    384             final long now = System.currentTimeMillis();
    385 
    386             // Update the text and the icon for the existing notification
    387             mPublicNotificationBuilder
    388                     .setContentTitle(r.getString(R.string.screenshot_saved_title))
    389                     .setContentText(r.getString(R.string.screenshot_saved_text))
    390                     .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
    391                     .setWhen(now)
    392                     .setAutoCancel(true)
    393                     .setColor(context.getColor(
    394                             com.android.internal.R.color.system_notification_accent_color));
    395             mNotificationBuilder
    396                 .setContentTitle(r.getString(R.string.screenshot_saved_title))
    397                 .setContentText(r.getString(R.string.screenshot_saved_text))
    398                 .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
    399                 .setWhen(now)
    400                 .setAutoCancel(true)
    401                 .setColor(context.getColor(
    402                         com.android.internal.R.color.system_notification_accent_color))
    403                 .setPublicVersion(mPublicNotificationBuilder.build())
    404                 .setFlag(Notification.FLAG_NO_CLEAR, false);
    405 
    406             mNotificationManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT,
    407                     mNotificationBuilder.build());
    408         }
    409         mParams.finisher.run();
    410         mParams.clearContext();
    411     }
    412 
    413     @Override
    414     protected void onCancelled(Void params) {
    415         // If we are cancelled while the task is running in the background, we may get null params.
    416         // The finisher is expected to always be called back, so just use the baked-in params from
    417         // the ctor in any case.
    418         mParams.finisher.run();
    419         mParams.clearImage();
    420         mParams.clearContext();
    421 
    422         // Cancel the posted notification
    423         mNotificationManager.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
    424     }
    425 }
    426 
    427 /**
    428  * An AsyncTask that deletes an image from the media store in the background.
    429  */
    430 class DeleteImageInBackgroundTask extends AsyncTask<Uri, Void, Void> {
    431     private Context mContext;
    432 
    433     DeleteImageInBackgroundTask(Context context) {
    434         mContext = context;
    435     }
    436 
    437     @Override
    438     protected Void doInBackground(Uri... params) {
    439         if (params.length != 1) return null;
    440 
    441         Uri screenshotUri = params[0];
    442         ContentResolver resolver = mContext.getContentResolver();
    443         resolver.delete(screenshotUri, null, null);
    444         return null;
    445     }
    446 }
    447 
    448 class GlobalScreenshot {
    449     static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
    450     static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
    451     static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
    452     static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
    453 
    454     private static final String TAG = "GlobalScreenshot";
    455 
    456     private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
    457     private static final int SCREENSHOT_DROP_IN_DURATION = 430;
    458     private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
    459     private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
    460     private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
    461     private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
    462     private static final float BACKGROUND_ALPHA = 0.5f;
    463     private static final float SCREENSHOT_SCALE = 1f;
    464     private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
    465     private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
    466     private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
    467     private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
    468     private final int mPreviewWidth;
    469     private final int mPreviewHeight;
    470 
    471     private Context mContext;
    472     private WindowManager mWindowManager;
    473     private WindowManager.LayoutParams mWindowLayoutParams;
    474     private NotificationManager mNotificationManager;
    475     private Display mDisplay;
    476     private DisplayMetrics mDisplayMetrics;
    477 
    478     private Bitmap mScreenBitmap;
    479     private View mScreenshotLayout;
    480     private ScreenshotSelectorView mScreenshotSelectorView;
    481     private ImageView mBackgroundView;
    482     private ImageView mScreenshotView;
    483     private ImageView mScreenshotFlash;
    484 
    485     private AnimatorSet mScreenshotAnimation;
    486 
    487     private int mNotificationIconSize;
    488     private float mBgPadding;
    489     private float mBgPaddingScale;
    490 
    491     private AsyncTask<Void, Void, Void> mSaveInBgTask;
    492 
    493     private MediaActionSound mCameraSound;
    494 
    495 
    496     /**
    497      * @param context everything needs a context :(
    498      */
    499     public GlobalScreenshot(Context context) {
    500         Resources r = context.getResources();
    501         mContext = context;
    502         LayoutInflater layoutInflater = (LayoutInflater)
    503                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    504 
    505         // Inflate the screenshot layout
    506         mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
    507         mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background);
    508         mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
    509         mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
    510         mScreenshotSelectorView = (ScreenshotSelectorView) mScreenshotLayout.findViewById(
    511                 R.id.global_screenshot_selector);
    512         mScreenshotLayout.setFocusable(true);
    513         mScreenshotSelectorView.setFocusable(true);
    514         mScreenshotSelectorView.setFocusableInTouchMode(true);
    515         mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() {
    516             @Override
    517             public boolean onTouch(View v, MotionEvent event) {
    518                 // Intercept and ignore all touch events
    519                 return true;
    520             }
    521         });
    522 
    523         // Setup the window that we are going to use
    524         mWindowLayoutParams = new WindowManager.LayoutParams(
    525                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
    526                 WindowManager.LayoutParams.TYPE_SCREENSHOT,
    527                 WindowManager.LayoutParams.FLAG_FULLSCREEN
    528                     | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    529                     | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
    530                 PixelFormat.TRANSLUCENT);
    531         mWindowLayoutParams.setTitle("ScreenshotAnimation");
    532         mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
    533         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    534         mNotificationManager =
    535             (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
    536         mDisplay = mWindowManager.getDefaultDisplay();
    537         mDisplayMetrics = new DisplayMetrics();
    538         mDisplay.getRealMetrics(mDisplayMetrics);
    539 
    540         // Get the various target sizes
    541         mNotificationIconSize =
    542             r.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
    543 
    544         // Scale has to account for both sides of the bg
    545         mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
    546         mBgPaddingScale = mBgPadding /  mDisplayMetrics.widthPixels;
    547 
    548         // determine the optimal preview size
    549         int panelWidth = 0;
    550         try {
    551             panelWidth = r.getDimensionPixelSize(R.dimen.notification_panel_width);
    552         } catch (Resources.NotFoundException e) {
    553         }
    554         if (panelWidth <= 0) {
    555             // includes notification_panel_width==match_parent (-1)
    556             panelWidth = mDisplayMetrics.widthPixels;
    557         }
    558         mPreviewWidth = panelWidth;
    559         mPreviewHeight = r.getDimensionPixelSize(R.dimen.notification_max_height);
    560 
    561         // Setup the Camera shutter sound
    562         mCameraSound = new MediaActionSound();
    563         mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
    564     }
    565 
    566     /**
    567      * Creates a new worker thread and saves the screenshot to the media store.
    568      */
    569     private void saveScreenshotInWorkerThread(Runnable finisher) {
    570         SaveImageInBackgroundData data = new SaveImageInBackgroundData();
    571         data.context = mContext;
    572         data.image = mScreenBitmap;
    573         data.iconSize = mNotificationIconSize;
    574         data.finisher = finisher;
    575         data.previewWidth = mPreviewWidth;
    576         data.previewheight = mPreviewHeight;
    577         if (mSaveInBgTask != null) {
    578             mSaveInBgTask.cancel(false);
    579         }
    580         mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)
    581                 .execute();
    582     }
    583 
    584     /**
    585      * Takes a screenshot of the current display and shows an animation.
    586      */
    587     private void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
    588             Rect crop) {
    589         int rot = mDisplay.getRotation();
    590         int width = crop.width();
    591         int height = crop.height();
    592 
    593         // Take the screenshot
    594         mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
    595         if (mScreenBitmap == null) {
    596             notifyScreenshotError(mContext, mNotificationManager,
    597                     R.string.screenshot_failed_to_capture_text);
    598             finisher.run();
    599             return;
    600         }
    601 
    602         // Optimizations
    603         mScreenBitmap.setHasAlpha(false);
    604         mScreenBitmap.prepareToDraw();
    605 
    606         // Start the post-screenshot animation
    607         startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
    608                 statusBarVisible, navBarVisible);
    609     }
    610 
    611     void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
    612         mDisplay.getRealMetrics(mDisplayMetrics);
    613         takeScreenshot(finisher, statusBarVisible, navBarVisible,
    614                 new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
    615     }
    616 
    617     /**
    618      * Displays a screenshot selector
    619      */
    620     void takeScreenshotPartial(final Runnable finisher, final boolean statusBarVisible,
    621             final boolean navBarVisible) {
    622         mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
    623         mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
    624             @Override
    625             public boolean onTouch(View v, MotionEvent event) {
    626                 ScreenshotSelectorView view = (ScreenshotSelectorView) v;
    627                 switch (event.getAction()) {
    628                     case MotionEvent.ACTION_DOWN:
    629                         view.startSelection((int) event.getX(), (int) event.getY());
    630                         return true;
    631                     case MotionEvent.ACTION_MOVE:
    632                         view.updateSelection((int) event.getX(), (int) event.getY());
    633                         return true;
    634                     case MotionEvent.ACTION_UP:
    635                         view.setVisibility(View.GONE);
    636                         mWindowManager.removeView(mScreenshotLayout);
    637                         final Rect rect = view.getSelectionRect();
    638                         if (rect != null) {
    639                             if (rect.width() != 0 && rect.height() != 0) {
    640                                 // Need mScreenshotLayout to handle it after the view disappears
    641                                 mScreenshotLayout.post(new Runnable() {
    642                                     public void run() {
    643                                         takeScreenshot(finisher, statusBarVisible, navBarVisible,
    644                                                 rect);
    645                                     }
    646                                 });
    647                             }
    648                         }
    649 
    650                         view.stopSelection();
    651                         return true;
    652                 }
    653 
    654                 return false;
    655             }
    656         });
    657         mScreenshotLayout.post(new Runnable() {
    658             @Override
    659             public void run() {
    660                 mScreenshotSelectorView.setVisibility(View.VISIBLE);
    661                 mScreenshotSelectorView.requestFocus();
    662             }
    663         });
    664     }
    665 
    666     /**
    667      * Cancels screenshot request
    668      */
    669     void stopScreenshot() {
    670         // If the selector layer still presents on screen, we remove it and resets its state.
    671         if (mScreenshotSelectorView.getSelectionRect() != null) {
    672             mWindowManager.removeView(mScreenshotLayout);
    673             mScreenshotSelectorView.stopSelection();
    674         }
    675     }
    676 
    677     /**
    678      * Starts the animation after taking the screenshot
    679      */
    680     private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
    681             boolean navBarVisible) {
    682         // If power save is on, show a toast so there is some visual indication that a screenshot
    683         // has been taken.
    684         PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
    685         if (powerManager.isPowerSaveMode()) {
    686             Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
    687         }
    688 
    689         // Add the view for the animation
    690         mScreenshotView.setImageBitmap(mScreenBitmap);
    691         mScreenshotLayout.requestFocus();
    692 
    693         // Setup the animation with the screenshot just taken
    694         if (mScreenshotAnimation != null) {
    695             if (mScreenshotAnimation.isStarted()) {
    696                 mScreenshotAnimation.end();
    697             }
    698             mScreenshotAnimation.removeAllListeners();
    699         }
    700 
    701         mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
    702         ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
    703         ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
    704                 statusBarVisible, navBarVisible);
    705         mScreenshotAnimation = new AnimatorSet();
    706         mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
    707         mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
    708             @Override
    709             public void onAnimationEnd(Animator animation) {
    710                 // Save the screenshot once we have a bit of time now
    711                 saveScreenshotInWorkerThread(finisher);
    712                 mWindowManager.removeView(mScreenshotLayout);
    713 
    714                 // Clear any references to the bitmap
    715                 mScreenBitmap = null;
    716                 mScreenshotView.setImageBitmap(null);
    717             }
    718         });
    719         mScreenshotLayout.post(new Runnable() {
    720             @Override
    721             public void run() {
    722                 // Play the shutter sound to notify that we've taken a screenshot
    723                 mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
    724 
    725                 mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    726                 mScreenshotView.buildLayer();
    727                 mScreenshotAnimation.start();
    728             }
    729         });
    730     }
    731     private ValueAnimator createScreenshotDropInAnimation() {
    732         final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
    733                 / SCREENSHOT_DROP_IN_DURATION);
    734         final float flashDurationPct = 2f * flashPeakDurationPct;
    735         final Interpolator flashAlphaInterpolator = new Interpolator() {
    736             @Override
    737             public float getInterpolation(float x) {
    738                 // Flash the flash view in and out quickly
    739                 if (x <= flashDurationPct) {
    740                     return (float) Math.sin(Math.PI * (x / flashDurationPct));
    741                 }
    742                 return 0;
    743             }
    744         };
    745         final Interpolator scaleInterpolator = new Interpolator() {
    746             @Override
    747             public float getInterpolation(float x) {
    748                 // We start scaling when the flash is at it's peak
    749                 if (x < flashPeakDurationPct) {
    750                     return 0;
    751                 }
    752                 return (x - flashDurationPct) / (1f - flashDurationPct);
    753             }
    754         };
    755 
    756         Resources r = mContext.getResources();
    757         if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
    758                 == Configuration.UI_MODE_NIGHT_YES) {
    759             mScreenshotView.getBackground().setTint(Color.BLACK);
    760         } else {
    761             mScreenshotView.getBackground().setTintList(null);
    762         }
    763 
    764         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
    765         anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
    766         anim.addListener(new AnimatorListenerAdapter() {
    767             @Override
    768             public void onAnimationStart(Animator animation) {
    769                 mBackgroundView.setAlpha(0f);
    770                 mBackgroundView.setVisibility(View.VISIBLE);
    771                 mScreenshotView.setAlpha(0f);
    772                 mScreenshotView.setTranslationX(0f);
    773                 mScreenshotView.setTranslationY(0f);
    774                 mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
    775                 mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
    776                 mScreenshotView.setVisibility(View.VISIBLE);
    777                 mScreenshotFlash.setAlpha(0f);
    778                 mScreenshotFlash.setVisibility(View.VISIBLE);
    779             }
    780             @Override
    781             public void onAnimationEnd(android.animation.Animator animation) {
    782                 mScreenshotFlash.setVisibility(View.GONE);
    783             }
    784         });
    785         anim.addUpdateListener(new AnimatorUpdateListener() {
    786             @Override
    787             public void onAnimationUpdate(ValueAnimator animation) {
    788                 float t = (Float) animation.getAnimatedValue();
    789                 float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
    790                     - scaleInterpolator.getInterpolation(t)
    791                         * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
    792                 mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
    793                 mScreenshotView.setAlpha(t);
    794                 mScreenshotView.setScaleX(scaleT);
    795                 mScreenshotView.setScaleY(scaleT);
    796                 mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
    797             }
    798         });
    799         return anim;
    800     }
    801     private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
    802             boolean navBarVisible) {
    803         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
    804         anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
    805         anim.addListener(new AnimatorListenerAdapter() {
    806             @Override
    807             public void onAnimationEnd(Animator animation) {
    808                 mBackgroundView.setVisibility(View.GONE);
    809                 mScreenshotView.setVisibility(View.GONE);
    810                 mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
    811             }
    812         });
    813 
    814         if (!statusBarVisible || !navBarVisible) {
    815             // There is no status bar/nav bar, so just fade the screenshot away in place
    816             anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
    817             anim.addUpdateListener(new AnimatorUpdateListener() {
    818                 @Override
    819                 public void onAnimationUpdate(ValueAnimator animation) {
    820                     float t = (Float) animation.getAnimatedValue();
    821                     float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
    822                             - t * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
    823                     mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
    824                     mScreenshotView.setAlpha(1f - t);
    825                     mScreenshotView.setScaleX(scaleT);
    826                     mScreenshotView.setScaleY(scaleT);
    827                 }
    828             });
    829         } else {
    830             // In the case where there is a status bar, animate to the origin of the bar (top-left)
    831             final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
    832                     / SCREENSHOT_DROP_OUT_DURATION;
    833             final Interpolator scaleInterpolator = new Interpolator() {
    834                 @Override
    835                 public float getInterpolation(float x) {
    836                     if (x < scaleDurationPct) {
    837                         // Decelerate, and scale the input accordingly
    838                         return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
    839                     }
    840                     return 1f;
    841                 }
    842             };
    843 
    844             // Determine the bounds of how to scale
    845             float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
    846             float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
    847             final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
    848             final PointF finalPos = new PointF(
    849                 -halfScreenWidth + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
    850                 -halfScreenHeight + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
    851 
    852             // Animate the screenshot to the status bar
    853             anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
    854             anim.addUpdateListener(new AnimatorUpdateListener() {
    855                 @Override
    856                 public void onAnimationUpdate(ValueAnimator animation) {
    857                     float t = (Float) animation.getAnimatedValue();
    858                     float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
    859                         - scaleInterpolator.getInterpolation(t)
    860                             * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
    861                     mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
    862                     mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
    863                     mScreenshotView.setScaleX(scaleT);
    864                     mScreenshotView.setScaleY(scaleT);
    865                     mScreenshotView.setTranslationX(t * finalPos.x);
    866                     mScreenshotView.setTranslationY(t * finalPos.y);
    867                 }
    868             });
    869         }
    870         return anim;
    871     }
    872 
    873     static void notifyScreenshotError(Context context, NotificationManager nManager, int msgResId) {
    874         Resources r = context.getResources();
    875         String errorMsg = r.getString(msgResId);
    876 
    877         // Repurpose the existing notification to notify the user of the error
    878         Notification.Builder b = new Notification.Builder(context, NotificationChannels.ALERTS)
    879             .setTicker(r.getString(R.string.screenshot_failed_title))
    880             .setContentTitle(r.getString(R.string.screenshot_failed_title))
    881             .setContentText(errorMsg)
    882             .setSmallIcon(R.drawable.stat_notify_image_error)
    883             .setWhen(System.currentTimeMillis())
    884             .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
    885             .setCategory(Notification.CATEGORY_ERROR)
    886             .setAutoCancel(true)
    887             .setColor(context.getColor(
    888                         com.android.internal.R.color.system_notification_accent_color));
    889         final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
    890                 Context.DEVICE_POLICY_SERVICE);
    891         final Intent intent = dpm.createAdminSupportIntent(
    892                 DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
    893         if (intent != null) {
    894             final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
    895                     context, 0, intent, 0, null, UserHandle.CURRENT);
    896             b.setContentIntent(pendingIntent);
    897         }
    898 
    899         SystemUI.overrideNotificationAppName(context, b, true);
    900 
    901         Notification n = new Notification.BigTextStyle(b)
    902                 .bigText(errorMsg)
    903                 .build();
    904         nManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
    905     }
    906 
    907     /**
    908      * Receiver to proxy the share or edit intent, used to clean up the notification and send
    909      * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
    910      */
    911     public static class ActionProxyReceiver extends BroadcastReceiver {
    912         static final int CLOSE_WINDOWS_TIMEOUT_MILLIS = 3000;
    913 
    914         @Override
    915         public void onReceive(Context context, final Intent intent) {
    916             Runnable startActivityRunnable = () -> {
    917                 try {
    918                     ActivityManagerWrapper.getInstance().closeSystemWindows(
    919                             SYSTEM_DIALOG_REASON_SCREENSHOT).get(
    920                             CLOSE_WINDOWS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    921                 } catch (TimeoutException | InterruptedException | ExecutionException e) {
    922                     Slog.e(TAG, "Unable to share screenshot", e);
    923                     return;
    924                 }
    925 
    926                 Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
    927                 if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
    928                     cancelScreenshotNotification(context);
    929                 }
    930                 ActivityOptions opts = ActivityOptions.makeBasic();
    931                 opts.setDisallowEnterPictureInPictureWhileLaunching(
    932                         intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
    933                 context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
    934             };
    935             StatusBar statusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
    936             statusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
    937                     true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
    938         }
    939     }
    940 
    941     /**
    942      * Removes the notification for a screenshot after a share target is chosen.
    943      */
    944     public static class TargetChosenReceiver extends BroadcastReceiver {
    945         @Override
    946         public void onReceive(Context context, Intent intent) {
    947             // Clear the notification only after the user has chosen a share action
    948             cancelScreenshotNotification(context);
    949         }
    950     }
    951 
    952     /**
    953      * Removes the last screenshot.
    954      */
    955     public static class DeleteScreenshotReceiver extends BroadcastReceiver {
    956         @Override
    957         public void onReceive(Context context, Intent intent) {
    958             if (!intent.hasExtra(SCREENSHOT_URI_ID)) {
    959                 return;
    960             }
    961 
    962             // Clear the notification when the image is deleted
    963             cancelScreenshotNotification(context);
    964 
    965             // And delete the image from the media store
    966             final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
    967             new DeleteImageInBackgroundTask(context).execute(uri);
    968         }
    969     }
    970 
    971     private static void cancelScreenshotNotification(Context context) {
    972         final NotificationManager nm =
    973                 (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
    974         nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
    975     }
    976 }
    977