Loading packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +218 −227 Original line number Diff line number Diff line Loading @@ -214,9 +214,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private Animator mScreenshotAnimation; private Runnable mOnCompleteRunnable; private Animator mDismissAnimation; private boolean mInDarkMode = false; private boolean mDirectionLTR = true; private boolean mOrientationPortrait = true; private boolean mInDarkMode; private boolean mDirectionLTR; private boolean mOrientationPortrait; private float mCornerSizeX; private float mDismissDeltaY; Loading Loading @@ -245,9 +245,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } }; /** * @param context everything needs a context :( */ @Inject public GlobalScreenshot( Context context, @Main Resources resources, Loading Loading @@ -320,6 +317,104 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset inoutInfo.touchableRegion.set(touchRegion); } void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) { mOnCompleteRunnable = onComplete; mDisplay.getRealMetrics(mDisplayMetrics); takeScreenshotInternal( finisher, new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); } void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, Insets visibleInsets, int taskId, int userId, ComponentName topComponent, Consumer<Uri> finisher, Runnable onComplete) { // TODO: use task Id, userId, topComponent for smart handler mOnCompleteRunnable = onComplete; if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) { saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false); } else { saveScreenshot(screenshot, finisher, new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE, true); } } /** * Displays a screenshot selector */ @SuppressLint("ClickableViewAccessibility") void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) { dismissScreenshot("new screenshot requested", true); mOnCompleteRunnable = onComplete; mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); mScreenshotSelectorView.setOnTouchListener((v, 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(() -> takeScreenshotInternal(finisher, rect)); } } view.stopSelection(); return true; } return false; }); mScreenshotLayout.post(() -> { 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 */ void dismissScreenshot(String reason, boolean immediate) { Log.v(TAG, "clearing screenshot: " + reason); mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); if (!immediate) { mDismissAnimation = createScreenshotDismissAnimation(); mDismissAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); clearScreenshot(); } }); mDismissAnimation.start(); } else { clearScreenshot(); } } private void onConfigChanged(Configuration newConfig) { boolean needsUpdate = false; // dark mode Loading Loading @@ -408,15 +503,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } return mScreenshotLayout.onApplyWindowInsets(insets); }); mScreenshotLayout.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { mScreenshotLayout.setOnKeyListener((v, keyCode, event) -> { if (keyCode == KeyEvent.KEYCODE_BACK) { dismissScreenshot("back pressed", true); return true; } return false; } }); // Get focus so that the key events go to the layout. mScreenshotLayout.setFocusableInTouchMode(true); Loading Loading @@ -470,60 +562,20 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } } /** * Updates the window focusability. If the window is already showing, then it updates the * window immediately, otherwise the layout params will be applied when the window is next * shown. */ private void setWindowFocusable(boolean focusable) { if (focusable) { mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE; } else { mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE; } if (mScreenshotLayout.isAttachedToWindow()) { mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams); } } /** * Creates a new worker thread and saves the screenshot to the media store. */ private void saveScreenshotInWorkerThread( Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) { SaveImageInBackgroundData data = new SaveImageInBackgroundData(); data.image = mScreenBitmap; data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; if (mSaveInBgTask != null) { // just log success/failure for the pre-existing screenshot mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() { @Override void onActionsReady(SavedImageData imageData) { logSuccessOnActionsReady(imageData); } }); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); mSaveInBgTask.execute(); } /** * Takes a screenshot of the current display and shows an animation. */ private void takeScreenshot(Consumer<Uri> finisher, Rect crop) { private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) { // copy the input Rect, since SurfaceControl.screenshot can mutate it Rect screenRect = new Rect(crop); int rot = mDisplay.getRotation(); int width = crop.width(); int height = crop.height(); takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect, saveScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect, Insets.NONE, true); } private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, boolean showFlash) { dismissScreenshot("new screenshot requested", true); Loading Loading @@ -561,85 +613,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset startAnimation(finisher, screenRect, screenInsets, showFlash); } void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) { mOnCompleteRunnable = onComplete; mDisplay.getRealMetrics(mDisplayMetrics); takeScreenshot( finisher, new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); } void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, Insets visibleInsets, int taskId, int userId, ComponentName topComponent, Consumer<Uri> finisher, Runnable onComplete) { // TODO: use task Id, userId, topComponent for smart handler mOnCompleteRunnable = onComplete; if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) { takeScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false); } else { takeScreenshot(screenshot, finisher, new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE, true); } } /** * Displays a screenshot selector */ @SuppressLint("ClickableViewAccessibility") void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) { dismissScreenshot("new screenshot requested", true); mOnCompleteRunnable = onComplete; 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, rect)); } } view.stopSelection(); return true; } return false; } }); mScreenshotLayout.post(() -> { 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(); } } /** * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on * failure). Loading Loading @@ -670,55 +643,78 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); } private boolean isUserSetupComplete() { return Settings.Secure.getInt(mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; } /** * Clears current screenshot * Starts the animation after taking the screenshot */ void dismissScreenshot(String reason, boolean immediate) { Log.v(TAG, "clearing screenshot: " + reason); mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); if (!immediate) { mDismissAnimation = createScreenshotDismissAnimation(); mDismissAnimation.addListener(new AnimatorListenerAdapter() { private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, boolean showFlash) { // 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(); } mScreenshotHandler.post(() -> { if (!mScreenshotLayout.isAttachedToWindow()) { mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); } mScreenshotAnimatedView.setImageDrawable( createScreenDrawable(mScreenBitmap, screenInsets)); setAnimatedViewSize(screenRect.width(), screenRect.height()); // Show when the animation starts mScreenshotAnimatedView.setVisibility(View.GONE); mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets)); // make static preview invisible (from gone) so we can query its location on screen mScreenshotPreview.setVisibility(View.INVISIBLE); mScreenshotHandler.post(() -> { mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); mScreenshotAnimation = createScreenshotDropInAnimation(screenRect, showFlash); saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); clearScreenshot(); void onActionsReady(SavedImageData imageData) { showUiOnActionsReady(imageData); } }); mDismissAnimation.start(); } else { clearScreenshot(); } // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null); mScreenshotPreview.buildLayer(); mScreenshotAnimation.start(); }); }); } private void clearScreenshot() { if (mScreenshotLayout.isAttachedToWindow()) { mWindowManager.removeView(mScreenshotLayout); /** * Creates a new worker thread and saves the screenshot to the media store. */ private void saveScreenshotInWorkerThread( Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) { SaveImageInBackgroundData data = new SaveImageInBackgroundData(); data.image = mScreenBitmap; data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; if (mSaveInBgTask != null) { // just log success/failure for the pre-existing screenshot mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() { @Override void onActionsReady(SavedImageData imageData) { logSuccessOnActionsReady(imageData); } }); } // Clear any references to the bitmap mScreenshotPreview.setImageDrawable(null); mScreenshotAnimatedView.setImageDrawable(null); mScreenshotAnimatedView.setVisibility(View.GONE); mActionsContainerBackground.setVisibility(View.GONE); mActionsContainer.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); mDismissButton.setVisibility(View.GONE); mScreenshotPreview.setVisibility(View.GONE); mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null); mScreenshotPreview.setContentDescription( mContext.getResources().getString(R.string.screenshot_preview_description)); mScreenshotLayout.setAlpha(1); mDismissButton.setTranslationY(0); mActionsContainer.setTranslationY(0); mActionsContainerBackground.setTranslationY(0); mScreenshotPreview.setTranslationY(0); mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); mSaveInBgTask.execute(); } /** Loading Loading @@ -768,56 +764,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } } /** * Starts the animation after taking the screenshot */ private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, boolean showFlash) { // 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(); } mScreenshotHandler.post(() -> { if (!mScreenshotLayout.isAttachedToWindow()) { mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); } mScreenshotAnimatedView.setImageDrawable( createScreenDrawable(mScreenBitmap, screenInsets)); setAnimatedViewSize(screenRect.width(), screenRect.height()); // Show when the animation starts mScreenshotAnimatedView.setVisibility(View.GONE); mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets)); // make static preview invisible (from gone) so we can query its location on screen mScreenshotPreview.setVisibility(View.INVISIBLE); mScreenshotHandler.post(() -> { mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); mScreenshotAnimation = createScreenshotDropInAnimation(screenRect, showFlash); saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { @Override void onActionsReady(SavedImageData imageData) { showUiOnActionsReady(imageData); } }); // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null); mScreenshotPreview.buildLayer(); mScreenshotAnimation.start(); }); }); } private AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) { Rect previewBounds = new Rect(); mScreenshotPreview.getBoundsOnScreen(previewBounds); Loading Loading @@ -1070,6 +1016,30 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return animSet; } private void clearScreenshot() { if (mScreenshotLayout.isAttachedToWindow()) { mWindowManager.removeView(mScreenshotLayout); } // Clear any references to the bitmap mScreenshotPreview.setImageDrawable(null); mScreenshotAnimatedView.setImageDrawable(null); mScreenshotAnimatedView.setVisibility(View.GONE); mActionsContainerBackground.setVisibility(View.GONE); mActionsContainer.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); mDismissButton.setVisibility(View.GONE); mScreenshotPreview.setVisibility(View.GONE); mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null); mScreenshotPreview.setContentDescription( mContext.getResources().getString(R.string.screenshot_preview_description)); mScreenshotLayout.setAlpha(1); mDismissButton.setTranslationY(0); mActionsContainer.setTranslationY(0); mActionsContainerBackground.setTranslationY(0); mScreenshotPreview.setTranslationY(0); } private void setAnimatedViewSize(int width, int height) { ViewGroup.LayoutParams layoutParams = mScreenshotAnimatedView.getLayoutParams(); layoutParams.width = width; Loading @@ -1077,6 +1047,27 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotAnimatedView.setLayoutParams(layoutParams); } /** * Updates the window focusability. If the window is already showing, then it updates the * window immediately, otherwise the layout params will be applied when the window is next * shown. */ private void setWindowFocusable(boolean focusable) { if (focusable) { mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE; } else { mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE; } if (mScreenshotLayout.isAttachedToWindow()) { mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams); } } private boolean isUserSetupComplete() { return Settings.Secure.getInt(mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; } /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ private boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) { int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right; Loading packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +1 −1 Original line number Diff line number Diff line Loading @@ -102,7 +102,7 @@ public class TakeScreenshotService extends Service { switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: mScreenshot.takeScreenshot(uriConsumer, onComplete); mScreenshot.takeScreenshotFullscreen(uriConsumer, onComplete); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: mScreenshot.takeScreenshotPartial(uriConsumer, onComplete); Loading Loading
packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +218 −227 Original line number Diff line number Diff line Loading @@ -214,9 +214,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private Animator mScreenshotAnimation; private Runnable mOnCompleteRunnable; private Animator mDismissAnimation; private boolean mInDarkMode = false; private boolean mDirectionLTR = true; private boolean mOrientationPortrait = true; private boolean mInDarkMode; private boolean mDirectionLTR; private boolean mOrientationPortrait; private float mCornerSizeX; private float mDismissDeltaY; Loading Loading @@ -245,9 +245,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } }; /** * @param context everything needs a context :( */ @Inject public GlobalScreenshot( Context context, @Main Resources resources, Loading Loading @@ -320,6 +317,104 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset inoutInfo.touchableRegion.set(touchRegion); } void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) { mOnCompleteRunnable = onComplete; mDisplay.getRealMetrics(mDisplayMetrics); takeScreenshotInternal( finisher, new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); } void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, Insets visibleInsets, int taskId, int userId, ComponentName topComponent, Consumer<Uri> finisher, Runnable onComplete) { // TODO: use task Id, userId, topComponent for smart handler mOnCompleteRunnable = onComplete; if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) { saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false); } else { saveScreenshot(screenshot, finisher, new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE, true); } } /** * Displays a screenshot selector */ @SuppressLint("ClickableViewAccessibility") void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) { dismissScreenshot("new screenshot requested", true); mOnCompleteRunnable = onComplete; mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); mScreenshotSelectorView.setOnTouchListener((v, 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(() -> takeScreenshotInternal(finisher, rect)); } } view.stopSelection(); return true; } return false; }); mScreenshotLayout.post(() -> { 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 */ void dismissScreenshot(String reason, boolean immediate) { Log.v(TAG, "clearing screenshot: " + reason); mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); if (!immediate) { mDismissAnimation = createScreenshotDismissAnimation(); mDismissAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); clearScreenshot(); } }); mDismissAnimation.start(); } else { clearScreenshot(); } } private void onConfigChanged(Configuration newConfig) { boolean needsUpdate = false; // dark mode Loading Loading @@ -408,15 +503,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } return mScreenshotLayout.onApplyWindowInsets(insets); }); mScreenshotLayout.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { mScreenshotLayout.setOnKeyListener((v, keyCode, event) -> { if (keyCode == KeyEvent.KEYCODE_BACK) { dismissScreenshot("back pressed", true); return true; } return false; } }); // Get focus so that the key events go to the layout. mScreenshotLayout.setFocusableInTouchMode(true); Loading Loading @@ -470,60 +562,20 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } } /** * Updates the window focusability. If the window is already showing, then it updates the * window immediately, otherwise the layout params will be applied when the window is next * shown. */ private void setWindowFocusable(boolean focusable) { if (focusable) { mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE; } else { mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE; } if (mScreenshotLayout.isAttachedToWindow()) { mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams); } } /** * Creates a new worker thread and saves the screenshot to the media store. */ private void saveScreenshotInWorkerThread( Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) { SaveImageInBackgroundData data = new SaveImageInBackgroundData(); data.image = mScreenBitmap; data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; if (mSaveInBgTask != null) { // just log success/failure for the pre-existing screenshot mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() { @Override void onActionsReady(SavedImageData imageData) { logSuccessOnActionsReady(imageData); } }); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); mSaveInBgTask.execute(); } /** * Takes a screenshot of the current display and shows an animation. */ private void takeScreenshot(Consumer<Uri> finisher, Rect crop) { private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) { // copy the input Rect, since SurfaceControl.screenshot can mutate it Rect screenRect = new Rect(crop); int rot = mDisplay.getRotation(); int width = crop.width(); int height = crop.height(); takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect, saveScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect, Insets.NONE, true); } private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, boolean showFlash) { dismissScreenshot("new screenshot requested", true); Loading Loading @@ -561,85 +613,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset startAnimation(finisher, screenRect, screenInsets, showFlash); } void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) { mOnCompleteRunnable = onComplete; mDisplay.getRealMetrics(mDisplayMetrics); takeScreenshot( finisher, new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); } void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, Insets visibleInsets, int taskId, int userId, ComponentName topComponent, Consumer<Uri> finisher, Runnable onComplete) { // TODO: use task Id, userId, topComponent for smart handler mOnCompleteRunnable = onComplete; if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) { takeScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false); } else { takeScreenshot(screenshot, finisher, new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE, true); } } /** * Displays a screenshot selector */ @SuppressLint("ClickableViewAccessibility") void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) { dismissScreenshot("new screenshot requested", true); mOnCompleteRunnable = onComplete; 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, rect)); } } view.stopSelection(); return true; } return false; } }); mScreenshotLayout.post(() -> { 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(); } } /** * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on * failure). Loading Loading @@ -670,55 +643,78 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); } private boolean isUserSetupComplete() { return Settings.Secure.getInt(mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; } /** * Clears current screenshot * Starts the animation after taking the screenshot */ void dismissScreenshot(String reason, boolean immediate) { Log.v(TAG, "clearing screenshot: " + reason); mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); if (!immediate) { mDismissAnimation = createScreenshotDismissAnimation(); mDismissAnimation.addListener(new AnimatorListenerAdapter() { private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, boolean showFlash) { // 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(); } mScreenshotHandler.post(() -> { if (!mScreenshotLayout.isAttachedToWindow()) { mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); } mScreenshotAnimatedView.setImageDrawable( createScreenDrawable(mScreenBitmap, screenInsets)); setAnimatedViewSize(screenRect.width(), screenRect.height()); // Show when the animation starts mScreenshotAnimatedView.setVisibility(View.GONE); mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets)); // make static preview invisible (from gone) so we can query its location on screen mScreenshotPreview.setVisibility(View.INVISIBLE); mScreenshotHandler.post(() -> { mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); mScreenshotAnimation = createScreenshotDropInAnimation(screenRect, showFlash); saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); clearScreenshot(); void onActionsReady(SavedImageData imageData) { showUiOnActionsReady(imageData); } }); mDismissAnimation.start(); } else { clearScreenshot(); } // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null); mScreenshotPreview.buildLayer(); mScreenshotAnimation.start(); }); }); } private void clearScreenshot() { if (mScreenshotLayout.isAttachedToWindow()) { mWindowManager.removeView(mScreenshotLayout); /** * Creates a new worker thread and saves the screenshot to the media store. */ private void saveScreenshotInWorkerThread( Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) { SaveImageInBackgroundData data = new SaveImageInBackgroundData(); data.image = mScreenBitmap; data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; if (mSaveInBgTask != null) { // just log success/failure for the pre-existing screenshot mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() { @Override void onActionsReady(SavedImageData imageData) { logSuccessOnActionsReady(imageData); } }); } // Clear any references to the bitmap mScreenshotPreview.setImageDrawable(null); mScreenshotAnimatedView.setImageDrawable(null); mScreenshotAnimatedView.setVisibility(View.GONE); mActionsContainerBackground.setVisibility(View.GONE); mActionsContainer.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); mDismissButton.setVisibility(View.GONE); mScreenshotPreview.setVisibility(View.GONE); mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null); mScreenshotPreview.setContentDescription( mContext.getResources().getString(R.string.screenshot_preview_description)); mScreenshotLayout.setAlpha(1); mDismissButton.setTranslationY(0); mActionsContainer.setTranslationY(0); mActionsContainerBackground.setTranslationY(0); mScreenshotPreview.setTranslationY(0); mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); mSaveInBgTask.execute(); } /** Loading Loading @@ -768,56 +764,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } } /** * Starts the animation after taking the screenshot */ private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, boolean showFlash) { // 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(); } mScreenshotHandler.post(() -> { if (!mScreenshotLayout.isAttachedToWindow()) { mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); } mScreenshotAnimatedView.setImageDrawable( createScreenDrawable(mScreenBitmap, screenInsets)); setAnimatedViewSize(screenRect.width(), screenRect.height()); // Show when the animation starts mScreenshotAnimatedView.setVisibility(View.GONE); mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets)); // make static preview invisible (from gone) so we can query its location on screen mScreenshotPreview.setVisibility(View.INVISIBLE); mScreenshotHandler.post(() -> { mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); mScreenshotAnimation = createScreenshotDropInAnimation(screenRect, showFlash); saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { @Override void onActionsReady(SavedImageData imageData) { showUiOnActionsReady(imageData); } }); // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null); mScreenshotPreview.buildLayer(); mScreenshotAnimation.start(); }); }); } private AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) { Rect previewBounds = new Rect(); mScreenshotPreview.getBoundsOnScreen(previewBounds); Loading Loading @@ -1070,6 +1016,30 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return animSet; } private void clearScreenshot() { if (mScreenshotLayout.isAttachedToWindow()) { mWindowManager.removeView(mScreenshotLayout); } // Clear any references to the bitmap mScreenshotPreview.setImageDrawable(null); mScreenshotAnimatedView.setImageDrawable(null); mScreenshotAnimatedView.setVisibility(View.GONE); mActionsContainerBackground.setVisibility(View.GONE); mActionsContainer.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); mDismissButton.setVisibility(View.GONE); mScreenshotPreview.setVisibility(View.GONE); mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null); mScreenshotPreview.setContentDescription( mContext.getResources().getString(R.string.screenshot_preview_description)); mScreenshotLayout.setAlpha(1); mDismissButton.setTranslationY(0); mActionsContainer.setTranslationY(0); mActionsContainerBackground.setTranslationY(0); mScreenshotPreview.setTranslationY(0); } private void setAnimatedViewSize(int width, int height) { ViewGroup.LayoutParams layoutParams = mScreenshotAnimatedView.getLayoutParams(); layoutParams.width = width; Loading @@ -1077,6 +1047,27 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotAnimatedView.setLayoutParams(layoutParams); } /** * Updates the window focusability. If the window is already showing, then it updates the * window immediately, otherwise the layout params will be applied when the window is next * shown. */ private void setWindowFocusable(boolean focusable) { if (focusable) { mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE; } else { mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE; } if (mScreenshotLayout.isAttachedToWindow()) { mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams); } } private boolean isUserSetupComplete() { return Settings.Secure.getInt(mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; } /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ private boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) { int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right; Loading
packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +1 −1 Original line number Diff line number Diff line Loading @@ -102,7 +102,7 @@ public class TakeScreenshotService extends Service { switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: mScreenshot.takeScreenshot(uriConsumer, onComplete); mScreenshot.takeScreenshotFullscreen(uriConsumer, onComplete); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: mScreenshot.takeScreenshotPartial(uriConsumer, onComplete); Loading