Loading packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java 0 → 100644 +64 −0 Original line number Diff line number Diff line /* * 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. * 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; @SuppressWarnings("PointlessBooleanExpression") class LogConfig { /** Log ALL the things... */ private static final boolean DEBUG_ALL = false; /** Default log logTag for screenshot code */ private static final String TAG_SS = "Screenshot"; /** Use class name as Log tag instead of the default */ private static final boolean TAG_WITH_CLASS_NAME = false; /** Action creation and user selection: Share, Save, Edit, Delete, Smart action, etc */ static final boolean DEBUG_ACTIONS = DEBUG_ALL || false; /** Debug info about animations such as start, complete and cancel */ static final boolean DEBUG_ANIM = DEBUG_ALL || false; /** Whenever Uri is supplied to consumer, or onComplete runnable is run() */ static final boolean DEBUG_CALLBACK = DEBUG_ALL || false; /** Logs information about dismissing the screenshot tool */ static final boolean DEBUG_DISMISS = DEBUG_ALL || false; /** Touch or key event driven action or side effects */ static final boolean DEBUG_INPUT = DEBUG_ALL || false; /** Scroll capture usage */ static final boolean DEBUG_SCROLL = DEBUG_ALL || false; /** Service lifecycle events and callbacks */ static final boolean DEBUG_SERVICE = DEBUG_ALL || false; /** Storage related actions, Bitmap.compress, ContentManager, etc */ static final boolean DEBUG_STORAGE = DEBUG_ALL || false; /** High level logical UI actions: timeout, onConfigChanged, insets, show actions, reset */ static final boolean DEBUG_UI = DEBUG_ALL || false; /** Interactions with Window and WindowManager */ static final boolean DEBUG_WINDOW = DEBUG_ALL || false; static String logTag(Class<?> cls) { return TAG_WITH_CLASS_NAME ? cls.getSimpleName() : TAG_SS; } } packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +43 −8 Original line number Diff line number Diff line Loading @@ -16,6 +16,11 @@ package com.android.systemui.screenshot; import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE; import static com.android.systemui.screenshot.LogConfig.logTag; import android.app.ActivityTaskManager; import android.app.Notification; import android.app.PendingIntent; Loading Loading @@ -45,7 +50,7 @@ import android.provider.MediaStore; import android.provider.MediaStore.MediaColumns; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Slog; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; Loading Loading @@ -74,7 +79,7 @@ import java.util.concurrent.CompletableFuture; * An AsyncTask that saves an image to the media store in the background. */ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private static final String TAG = "SaveImageInBackgroundTask"; private static final String TAG = logTag(SaveImageInBackgroundTask.class); private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png"; private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s"; Loading Loading @@ -121,6 +126,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... paramsUnused) { if (isCancelled()) { if (DEBUG_STORAGE) { Log.d(TAG, "cancelled! returning null"); } return null; } Thread.currentThread().setPriority(Thread.MAX_PRIORITY); Loading Loading @@ -151,9 +159,19 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { try { // First, write the actual data for our screenshot try (OutputStream out = resolver.openOutputStream(uri)) { if (DEBUG_STORAGE) { Log.d(TAG, "Compressing PNG:" + " w=" + image.getWidth() + " h=" + image.getHeight()); } if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) { if (DEBUG_STORAGE) { Log.d(TAG, "Bitmap.compress returned false"); } throw new IOException("Failed to compress"); } if (DEBUG_STORAGE) { Log.d(TAG, "Done compressing PNG"); } } // Next, write metadata to help index the screenshot Loading Loading @@ -181,7 +199,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, DateTimeFormatter.ofPattern("XXX").format(time)); } if (DEBUG_STORAGE) { Log.d(TAG, "Writing EXIF metadata"); } exif.saveAttributes(); } Loading @@ -190,6 +210,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { values.put(MediaColumns.IS_PENDING, 0); values.putNull(MediaColumns.DATE_EXPIRES); resolver.update(uri, values, null, null); if (DEBUG_STORAGE) { Log.d(TAG, "Completed writing to ContentManager"); } } catch (Exception e) { resolver.delete(uri, null); throw e; Loading @@ -215,15 +238,24 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri); mParams.mActionsReadyListener.onActionsReady(mImageData); if (DEBUG_CALLBACK) { Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) " + "finisher.accept(\"" + mImageData.uri + "\""); } mParams.finisher.accept(mImageData.uri); mParams.image = null; } catch (Exception e) { // IOException/UnsupportedOperationException may be thrown if external storage is // not mounted Slog.e(TAG, "unable to save screenshot", e); if (DEBUG_STORAGE) { Log.d(TAG, "Failed to store screenshot", e); } mParams.clearImage(); mImageData.reset(); mParams.mActionsReadyListener.onActionsReady(mImageData); if (DEBUG_CALLBACK) { Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)"); } mParams.finisher.accept(null); } Loading @@ -245,6 +277,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // params from the ctor in any case. mImageData.reset(); mParams.mActionsReadyListener.onActionsReady(mImageData); if (DEBUG_CALLBACK) { Log.d(TAG, "onCancelled, calling (Consumer<Uri>) finisher.accept(null)"); } mParams.finisher.accept(null); mParams.clearImage(); } Loading Loading @@ -380,7 +415,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { try { return ActivityTaskManager.getService().getLastResumedActivityUserId(); } catch (RemoteException e) { Slog.w(TAG, "getUserHandleOfForegroundApplication: ", e); if (DEBUG_ACTIONS) { Log.d(TAG, "Failed to get UserHandle of foreground app: ", e); } return context.getUserId(); } } Loading Loading @@ -421,6 +458,4 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { .putExtra(ScreenshotController.EXTRA_ID, screenshotId) .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled); } } packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +95 −52 Original line number Diff line number Diff line Loading @@ -21,6 +21,14 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT; import static com.android.systemui.screenshot.LogConfig.DEBUG_UI; import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW; import static com.android.systemui.screenshot.LogConfig.logTag; import static java.util.Objects.requireNonNull; import android.animation.Animator; Loading Loading @@ -75,6 +83,7 @@ import javax.inject.Inject; * Controls the state and flow for screenshots. */ public class ScreenshotController { private static final String TAG = logTag(ScreenshotController.class); /** * POD used in the AsyncTask which saves an image in the background. */ Loading Loading @@ -110,12 +119,10 @@ public class ScreenshotController { } } abstract static class ActionsReadyListener { abstract void onActionsReady(ScreenshotController.SavedImageData imageData); interface ActionsReadyListener { void onActionsReady(ScreenshotController.SavedImageData imageData); } private static final String TAG = "ScreenshotController"; // These strings are used for communicating the action invoked to // ScreenshotNotificationSmartActionsProvider. static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; Loading Loading @@ -166,6 +173,9 @@ public class ScreenshotController { public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_CORNER_TIMEOUT: if (DEBUG_UI) { Log.d(TAG, "Corner timeout hit"); } mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT); ScreenshotController.this.dismissScreenshot(false); break; Loading Loading @@ -292,13 +302,17 @@ public class ScreenshotController { * Clears current screenshot */ void dismissScreenshot(boolean immediate) { if (DEBUG_DISMISS) { Log.d(TAG, "dismissScreenshot(immediate=" + immediate + ")"); } // If we're already animating out, don't restart the animation // (but do obey an immediate dismissal) if (!immediate && mScreenshotView.isDismissing()) { if (DEBUG_DISMISS) { Log.v(TAG, "Already dismissing, ignoring duplicate command"); } return; } Log.v(TAG, "Clearing screenshot"); mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); if (immediate) { resetScreenshotView(); Loading @@ -308,12 +322,17 @@ public class ScreenshotController { } /** * Update assets (called when the dark theme status changes). We only need to update the dismiss * button and the actions container background, since the buttons are re-inflated on demand. * Update resources on configuration change. Reinflate for theme/color changes. */ private void reloadAssets() { if (DEBUG_UI) { Log.d(TAG, "reloadAssets()"); } boolean wasAttached = mDecorView.isAttachedToWindow(); if (wasAttached) { if (DEBUG_WINDOW) { Log.d(TAG, "Removing screenshot window"); } mWindowManager.removeView(mDecorView); } Loading @@ -336,6 +355,9 @@ public class ScreenshotController { // TODO(159460485): Remove this when focus is handled properly in the system mScreenshotView.setOnTouchListener((v, event) -> { if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) { if (DEBUG_INPUT) { Log.d(TAG, "onTouch: ACTION_OUTSIDE"); } // Once the user touches outside, stop listening for input setWindowFocusable(false); } Loading @@ -344,6 +366,9 @@ public class ScreenshotController { mScreenshotView.setOnKeyListener((v, keyCode, event) -> { if (keyCode == KeyEvent.KEYCODE_BACK) { if (DEBUG_INPUT) { Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK"); } dismissScreenshot(false); return true; } Loading Loading @@ -373,10 +398,16 @@ public class ScreenshotController { Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap(); if (screenshot == null) { Log.e(TAG, "Screenshot bitmap was null"); Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); if (DEBUG_CALLBACK) { Log.d(TAG, "Supplying null to Consumer<Uri>"); } finisher.accept(null); if (DEBUG_CALLBACK) { Log.d(TAG, "Calling mOnCompleteRunnable.run()"); } mOnCompleteRunnable.run(); return; } Loading @@ -399,12 +430,17 @@ public class ScreenshotController { if (!mScreenshotView.isDismissing()) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED); } if (DEBUG_WINDOW) { Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " + "(dismissing=" + mScreenshotView.isDismissing() + ")"); } mScreenshotView.reset(); } mScreenBitmap = screenshot; if (!isUserSetupComplete()) { Log.w(TAG, "User setup not complete, displaying toast only"); // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing // and sharing shouldn't be exposed to the user. saveScreenshotAndToast(finisher); Loading @@ -416,6 +452,9 @@ public class ScreenshotController { mScreenBitmap.prepareToDraw(); if (mConfigChanges.applyNewConfig(mContext.getResources())) { if (DEBUG_UI) { Log.d(TAG, "saveScreenshot: reloading assets"); } reloadAssets(); } Loading Loading @@ -450,10 +489,10 @@ public class ScreenshotController { mCameraSound.play(MediaActionSound.SHUTTER_CLICK); }); saveScreenshotInWorkerThread(finisher, new ScreenshotController.ActionsReadyListener() { @Override void onActionsReady(ScreenshotController.SavedImageData imageData) { saveScreenshotInWorkerThread(finisher, imageData -> { if (DEBUG_CALLBACK) { Log.d(TAG, "returning URI to finisher (Consumer<URI>): " + imageData.uri); } finisher.accept(imageData.uri); if (imageData.uri == null) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED); Loading @@ -461,12 +500,8 @@ public class ScreenshotController { R.string.screenshot_failed_to_save_text); } else { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED); mScreenshotHandler.post(() -> { Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); }); } mScreenshotHandler.post(() -> Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show()); } }); } Loading @@ -479,37 +514,46 @@ public class ScreenshotController { mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); mScreenshotHandler.post(() -> { if (!mScreenshotView.isAttachedToWindow()) { if (DEBUG_WINDOW) { Log.d(TAG, "Adding screenshot window"); } mWindowManager.addView(mWindow.getDecorView(), mWindowLayoutParams); } mScreenshotView.prepareForAnimation(mScreenBitmap, screenInsets); mScreenshotHandler.post(() -> { if (DEBUG_WINDOW) { Log.d(TAG, "adding OnComputeInternalInsetsListener"); } mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener( mScreenshotView); mScreenshotAnimation = mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash); saveScreenshotInWorkerThread(finisher, new ScreenshotController.ActionsReadyListener() { @Override void onActionsReady( ScreenshotController.SavedImageData imageData) { showUiOnActionsReady(imageData); } }); saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady); // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); if (DEBUG_ANIM) { Log.d(TAG, "starting post-screenshot animation"); } mScreenshotAnimation.start(); }); }); } /** Reset screenshot view and then call onCompleteRunnable */ private void resetScreenshotView() { if (DEBUG_UI) { Log.d(TAG, "resetScreenshotView"); } if (mScreenshotView.isAttachedToWindow()) { if (DEBUG_WINDOW) { Log.d(TAG, "Removing screenshot window"); } mWindowManager.removeView(mDecorView); } mScreenshotView.reset(); Loading @@ -519,8 +563,7 @@ public class ScreenshotController { /** * Creates a new worker thread and saves the screenshot to the media store. */ private void saveScreenshotInWorkerThread( Consumer<Uri> finisher, private void saveScreenshotInWorkerThread(Consumer<Uri> finisher, @Nullable ScreenshotController.ActionsReadyListener actionsReadyListener) { ScreenshotController.SaveImageInBackgroundData data = new ScreenshotController.SaveImageInBackgroundData(); Loading @@ -530,13 +573,7 @@ public class ScreenshotController { if (mSaveInBgTask != null) { // just log success/failure for the pre-existing screenshot mSaveInBgTask.setActionsReadyListener( new ScreenshotController.ActionsReadyListener() { @Override void onActionsReady(ScreenshotController.SavedImageData imageData) { logSuccessOnActionsReady(imageData); } }); mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); Loading @@ -555,6 +592,9 @@ public class ScreenshotController { SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS, AccessibilityManager.FLAG_CONTENT_CONTROLS); if (DEBUG_UI) { Log.d(TAG, "Showing UI actions, dismiss timeout: " + timeoutMs + " ms"); } mScreenshotHandler.sendMessageDelayed( mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT), timeoutMs); Loading Loading @@ -600,6 +640,9 @@ public class ScreenshotController { * shown. */ private void setWindowFocusable(boolean focusable) { if (DEBUG_WINDOW) { Log.d(TAG, "setWindowFocusable: " + focusable); } if (focusable) { mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; } else { Loading @@ -618,9 +661,10 @@ public class ScreenshotController { if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0 || bitmap.getHeight() == 0) { Log.e(TAG, String.format( "Provided bitmap and insets create degenerate region: %dx%d %s", bitmap.getWidth(), bitmap.getHeight(), bitmapInsets)); if (DEBUG_UI) { Log.e(TAG, "Provided bitmap and insets create degenerate region: " + bitmap.getWidth() + "x" + bitmap.getHeight() + " " + bitmapInsets); } return false; } Loading @@ -628,11 +672,10 @@ public class ScreenshotController { float boundsAspect = ((float) screenBounds.width()) / screenBounds.height(); boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f; if (!matchWithinTolerance) { Log.d(TAG, String.format("aspectRatiosMatch: don't match bitmap: %f, bounds: %f", insettedBitmapAspect, boundsAspect)); if (DEBUG_UI) { Log.d(TAG, "aspectRatiosMatch: don't match bitmap: " + insettedBitmapAspect + ", bounds: " + boundsAspect); } return matchWithinTolerance; } } packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java +22 −20 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.systemui.screenshot; import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS; import static com.android.systemui.screenshot.LogConfig.logTag; import android.app.Notification; import android.content.ComponentName; import android.graphics.Bitmap; Loading @@ -32,6 +35,9 @@ import java.util.concurrent.CompletableFuture; * in order to provide smart actions in the screenshot notification. */ public class ScreenshotNotificationSmartActionsProvider { private static final String TAG = logTag(ScreenshotNotificationSmartActionsProvider.class); /* Key provided in the notification action to get the type of smart action. */ public static final String ACTION_TYPE = "action_type"; public static final String DEFAULT_ACTION_TYPE = "Smart Action"; Loading @@ -51,29 +57,21 @@ public class ScreenshotNotificationSmartActionsProvider { ERROR, TIMEOUT } private static final String TAG = "ScreenshotActions"; /** * Default implementation that returns an empty list. * This method is overridden in vendor-specific Sys UI implementation. * * @param screenshotId A generated random unique id for the screenshot. * @param screenshotFileName name of the file where the screenshot will be written. * @param bitmap The bitmap of the screenshot. The bitmap config must be {@link * HARDWARE}. * @param componentName Contains package and activity class names where the screenshot was * taken. This is used as an additional signal to generate and rank * more relevant actions. * @param userHandle The user handle of the app where the screenshot was taken. * @param screenshotId a unique id for the screenshot * @param screenshotUri uri where the screenshot has been stored * @param bitmap the screenshot, config must be {@link Bitmap.Config#HARDWARE} * @param componentName name of the foreground component when the screenshot was taken * @param userHandle user handle of the foreground task owner */ public CompletableFuture<List<Notification.Action>> getActions( String screenshotId, Uri screenshotUri, Bitmap bitmap, ComponentName componentName, UserHandle userHandle) { public CompletableFuture<List<Notification.Action>> getActions(String screenshotId, Uri screenshotUri, Bitmap bitmap, ComponentName componentName, UserHandle userHandle) { if (DEBUG_ACTIONS) { Log.d(TAG, "Returning empty smart action list."); } return CompletableFuture.completedFuture(Collections.emptyList()); } Loading @@ -88,7 +86,9 @@ public class ScreenshotNotificationSmartActionsProvider { */ public void notifyOp(String screenshotId, ScreenshotOp op, ScreenshotOpStatus status, long durationMs) { Log.d(TAG, "Return without notify."); if (DEBUG_ACTIONS) { Log.d(TAG, "SmartActions: notifyOp() - return without notify"); } } /** Loading @@ -100,6 +100,8 @@ public class ScreenshotNotificationSmartActionsProvider { * @param isSmartAction whether action invoked was a smart action. */ public void notifyAction(String screenshotId, String action, boolean isSmartAction) { Log.d(TAG, "Return without notify."); if (DEBUG_ACTIONS) { Log.d(TAG, "SmartActions: notifyAction: return without notify"); } } } packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java +42 −15 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java 0 → 100644 +64 −0 Original line number Diff line number Diff line /* * 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. * 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; @SuppressWarnings("PointlessBooleanExpression") class LogConfig { /** Log ALL the things... */ private static final boolean DEBUG_ALL = false; /** Default log logTag for screenshot code */ private static final String TAG_SS = "Screenshot"; /** Use class name as Log tag instead of the default */ private static final boolean TAG_WITH_CLASS_NAME = false; /** Action creation and user selection: Share, Save, Edit, Delete, Smart action, etc */ static final boolean DEBUG_ACTIONS = DEBUG_ALL || false; /** Debug info about animations such as start, complete and cancel */ static final boolean DEBUG_ANIM = DEBUG_ALL || false; /** Whenever Uri is supplied to consumer, or onComplete runnable is run() */ static final boolean DEBUG_CALLBACK = DEBUG_ALL || false; /** Logs information about dismissing the screenshot tool */ static final boolean DEBUG_DISMISS = DEBUG_ALL || false; /** Touch or key event driven action or side effects */ static final boolean DEBUG_INPUT = DEBUG_ALL || false; /** Scroll capture usage */ static final boolean DEBUG_SCROLL = DEBUG_ALL || false; /** Service lifecycle events and callbacks */ static final boolean DEBUG_SERVICE = DEBUG_ALL || false; /** Storage related actions, Bitmap.compress, ContentManager, etc */ static final boolean DEBUG_STORAGE = DEBUG_ALL || false; /** High level logical UI actions: timeout, onConfigChanged, insets, show actions, reset */ static final boolean DEBUG_UI = DEBUG_ALL || false; /** Interactions with Window and WindowManager */ static final boolean DEBUG_WINDOW = DEBUG_ALL || false; static String logTag(Class<?> cls) { return TAG_WITH_CLASS_NAME ? cls.getSimpleName() : TAG_SS; } }
packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +43 −8 Original line number Diff line number Diff line Loading @@ -16,6 +16,11 @@ package com.android.systemui.screenshot; import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE; import static com.android.systemui.screenshot.LogConfig.logTag; import android.app.ActivityTaskManager; import android.app.Notification; import android.app.PendingIntent; Loading Loading @@ -45,7 +50,7 @@ import android.provider.MediaStore; import android.provider.MediaStore.MediaColumns; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Slog; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; Loading Loading @@ -74,7 +79,7 @@ import java.util.concurrent.CompletableFuture; * An AsyncTask that saves an image to the media store in the background. */ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private static final String TAG = "SaveImageInBackgroundTask"; private static final String TAG = logTag(SaveImageInBackgroundTask.class); private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png"; private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s"; Loading Loading @@ -121,6 +126,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... paramsUnused) { if (isCancelled()) { if (DEBUG_STORAGE) { Log.d(TAG, "cancelled! returning null"); } return null; } Thread.currentThread().setPriority(Thread.MAX_PRIORITY); Loading Loading @@ -151,9 +159,19 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { try { // First, write the actual data for our screenshot try (OutputStream out = resolver.openOutputStream(uri)) { if (DEBUG_STORAGE) { Log.d(TAG, "Compressing PNG:" + " w=" + image.getWidth() + " h=" + image.getHeight()); } if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) { if (DEBUG_STORAGE) { Log.d(TAG, "Bitmap.compress returned false"); } throw new IOException("Failed to compress"); } if (DEBUG_STORAGE) { Log.d(TAG, "Done compressing PNG"); } } // Next, write metadata to help index the screenshot Loading Loading @@ -181,7 +199,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, DateTimeFormatter.ofPattern("XXX").format(time)); } if (DEBUG_STORAGE) { Log.d(TAG, "Writing EXIF metadata"); } exif.saveAttributes(); } Loading @@ -190,6 +210,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { values.put(MediaColumns.IS_PENDING, 0); values.putNull(MediaColumns.DATE_EXPIRES); resolver.update(uri, values, null, null); if (DEBUG_STORAGE) { Log.d(TAG, "Completed writing to ContentManager"); } } catch (Exception e) { resolver.delete(uri, null); throw e; Loading @@ -215,15 +238,24 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri); mParams.mActionsReadyListener.onActionsReady(mImageData); if (DEBUG_CALLBACK) { Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) " + "finisher.accept(\"" + mImageData.uri + "\""); } mParams.finisher.accept(mImageData.uri); mParams.image = null; } catch (Exception e) { // IOException/UnsupportedOperationException may be thrown if external storage is // not mounted Slog.e(TAG, "unable to save screenshot", e); if (DEBUG_STORAGE) { Log.d(TAG, "Failed to store screenshot", e); } mParams.clearImage(); mImageData.reset(); mParams.mActionsReadyListener.onActionsReady(mImageData); if (DEBUG_CALLBACK) { Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)"); } mParams.finisher.accept(null); } Loading @@ -245,6 +277,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // params from the ctor in any case. mImageData.reset(); mParams.mActionsReadyListener.onActionsReady(mImageData); if (DEBUG_CALLBACK) { Log.d(TAG, "onCancelled, calling (Consumer<Uri>) finisher.accept(null)"); } mParams.finisher.accept(null); mParams.clearImage(); } Loading Loading @@ -380,7 +415,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { try { return ActivityTaskManager.getService().getLastResumedActivityUserId(); } catch (RemoteException e) { Slog.w(TAG, "getUserHandleOfForegroundApplication: ", e); if (DEBUG_ACTIONS) { Log.d(TAG, "Failed to get UserHandle of foreground app: ", e); } return context.getUserId(); } } Loading Loading @@ -421,6 +458,4 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { .putExtra(ScreenshotController.EXTRA_ID, screenshotId) .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled); } }
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +95 −52 Original line number Diff line number Diff line Loading @@ -21,6 +21,14 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT; import static com.android.systemui.screenshot.LogConfig.DEBUG_UI; import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW; import static com.android.systemui.screenshot.LogConfig.logTag; import static java.util.Objects.requireNonNull; import android.animation.Animator; Loading Loading @@ -75,6 +83,7 @@ import javax.inject.Inject; * Controls the state and flow for screenshots. */ public class ScreenshotController { private static final String TAG = logTag(ScreenshotController.class); /** * POD used in the AsyncTask which saves an image in the background. */ Loading Loading @@ -110,12 +119,10 @@ public class ScreenshotController { } } abstract static class ActionsReadyListener { abstract void onActionsReady(ScreenshotController.SavedImageData imageData); interface ActionsReadyListener { void onActionsReady(ScreenshotController.SavedImageData imageData); } private static final String TAG = "ScreenshotController"; // These strings are used for communicating the action invoked to // ScreenshotNotificationSmartActionsProvider. static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; Loading Loading @@ -166,6 +173,9 @@ public class ScreenshotController { public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_CORNER_TIMEOUT: if (DEBUG_UI) { Log.d(TAG, "Corner timeout hit"); } mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT); ScreenshotController.this.dismissScreenshot(false); break; Loading Loading @@ -292,13 +302,17 @@ public class ScreenshotController { * Clears current screenshot */ void dismissScreenshot(boolean immediate) { if (DEBUG_DISMISS) { Log.d(TAG, "dismissScreenshot(immediate=" + immediate + ")"); } // If we're already animating out, don't restart the animation // (but do obey an immediate dismissal) if (!immediate && mScreenshotView.isDismissing()) { if (DEBUG_DISMISS) { Log.v(TAG, "Already dismissing, ignoring duplicate command"); } return; } Log.v(TAG, "Clearing screenshot"); mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); if (immediate) { resetScreenshotView(); Loading @@ -308,12 +322,17 @@ public class ScreenshotController { } /** * Update assets (called when the dark theme status changes). We only need to update the dismiss * button and the actions container background, since the buttons are re-inflated on demand. * Update resources on configuration change. Reinflate for theme/color changes. */ private void reloadAssets() { if (DEBUG_UI) { Log.d(TAG, "reloadAssets()"); } boolean wasAttached = mDecorView.isAttachedToWindow(); if (wasAttached) { if (DEBUG_WINDOW) { Log.d(TAG, "Removing screenshot window"); } mWindowManager.removeView(mDecorView); } Loading @@ -336,6 +355,9 @@ public class ScreenshotController { // TODO(159460485): Remove this when focus is handled properly in the system mScreenshotView.setOnTouchListener((v, event) -> { if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) { if (DEBUG_INPUT) { Log.d(TAG, "onTouch: ACTION_OUTSIDE"); } // Once the user touches outside, stop listening for input setWindowFocusable(false); } Loading @@ -344,6 +366,9 @@ public class ScreenshotController { mScreenshotView.setOnKeyListener((v, keyCode, event) -> { if (keyCode == KeyEvent.KEYCODE_BACK) { if (DEBUG_INPUT) { Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK"); } dismissScreenshot(false); return true; } Loading Loading @@ -373,10 +398,16 @@ public class ScreenshotController { Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap(); if (screenshot == null) { Log.e(TAG, "Screenshot bitmap was null"); Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); if (DEBUG_CALLBACK) { Log.d(TAG, "Supplying null to Consumer<Uri>"); } finisher.accept(null); if (DEBUG_CALLBACK) { Log.d(TAG, "Calling mOnCompleteRunnable.run()"); } mOnCompleteRunnable.run(); return; } Loading @@ -399,12 +430,17 @@ public class ScreenshotController { if (!mScreenshotView.isDismissing()) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED); } if (DEBUG_WINDOW) { Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " + "(dismissing=" + mScreenshotView.isDismissing() + ")"); } mScreenshotView.reset(); } mScreenBitmap = screenshot; if (!isUserSetupComplete()) { Log.w(TAG, "User setup not complete, displaying toast only"); // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing // and sharing shouldn't be exposed to the user. saveScreenshotAndToast(finisher); Loading @@ -416,6 +452,9 @@ public class ScreenshotController { mScreenBitmap.prepareToDraw(); if (mConfigChanges.applyNewConfig(mContext.getResources())) { if (DEBUG_UI) { Log.d(TAG, "saveScreenshot: reloading assets"); } reloadAssets(); } Loading Loading @@ -450,10 +489,10 @@ public class ScreenshotController { mCameraSound.play(MediaActionSound.SHUTTER_CLICK); }); saveScreenshotInWorkerThread(finisher, new ScreenshotController.ActionsReadyListener() { @Override void onActionsReady(ScreenshotController.SavedImageData imageData) { saveScreenshotInWorkerThread(finisher, imageData -> { if (DEBUG_CALLBACK) { Log.d(TAG, "returning URI to finisher (Consumer<URI>): " + imageData.uri); } finisher.accept(imageData.uri); if (imageData.uri == null) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED); Loading @@ -461,12 +500,8 @@ public class ScreenshotController { R.string.screenshot_failed_to_save_text); } else { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED); mScreenshotHandler.post(() -> { Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); }); } mScreenshotHandler.post(() -> Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show()); } }); } Loading @@ -479,37 +514,46 @@ public class ScreenshotController { mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); mScreenshotHandler.post(() -> { if (!mScreenshotView.isAttachedToWindow()) { if (DEBUG_WINDOW) { Log.d(TAG, "Adding screenshot window"); } mWindowManager.addView(mWindow.getDecorView(), mWindowLayoutParams); } mScreenshotView.prepareForAnimation(mScreenBitmap, screenInsets); mScreenshotHandler.post(() -> { if (DEBUG_WINDOW) { Log.d(TAG, "adding OnComputeInternalInsetsListener"); } mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener( mScreenshotView); mScreenshotAnimation = mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash); saveScreenshotInWorkerThread(finisher, new ScreenshotController.ActionsReadyListener() { @Override void onActionsReady( ScreenshotController.SavedImageData imageData) { showUiOnActionsReady(imageData); } }); saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady); // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); if (DEBUG_ANIM) { Log.d(TAG, "starting post-screenshot animation"); } mScreenshotAnimation.start(); }); }); } /** Reset screenshot view and then call onCompleteRunnable */ private void resetScreenshotView() { if (DEBUG_UI) { Log.d(TAG, "resetScreenshotView"); } if (mScreenshotView.isAttachedToWindow()) { if (DEBUG_WINDOW) { Log.d(TAG, "Removing screenshot window"); } mWindowManager.removeView(mDecorView); } mScreenshotView.reset(); Loading @@ -519,8 +563,7 @@ public class ScreenshotController { /** * Creates a new worker thread and saves the screenshot to the media store. */ private void saveScreenshotInWorkerThread( Consumer<Uri> finisher, private void saveScreenshotInWorkerThread(Consumer<Uri> finisher, @Nullable ScreenshotController.ActionsReadyListener actionsReadyListener) { ScreenshotController.SaveImageInBackgroundData data = new ScreenshotController.SaveImageInBackgroundData(); Loading @@ -530,13 +573,7 @@ public class ScreenshotController { if (mSaveInBgTask != null) { // just log success/failure for the pre-existing screenshot mSaveInBgTask.setActionsReadyListener( new ScreenshotController.ActionsReadyListener() { @Override void onActionsReady(ScreenshotController.SavedImageData imageData) { logSuccessOnActionsReady(imageData); } }); mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); Loading @@ -555,6 +592,9 @@ public class ScreenshotController { SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS, AccessibilityManager.FLAG_CONTENT_CONTROLS); if (DEBUG_UI) { Log.d(TAG, "Showing UI actions, dismiss timeout: " + timeoutMs + " ms"); } mScreenshotHandler.sendMessageDelayed( mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT), timeoutMs); Loading Loading @@ -600,6 +640,9 @@ public class ScreenshotController { * shown. */ private void setWindowFocusable(boolean focusable) { if (DEBUG_WINDOW) { Log.d(TAG, "setWindowFocusable: " + focusable); } if (focusable) { mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; } else { Loading @@ -618,9 +661,10 @@ public class ScreenshotController { if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0 || bitmap.getHeight() == 0) { Log.e(TAG, String.format( "Provided bitmap and insets create degenerate region: %dx%d %s", bitmap.getWidth(), bitmap.getHeight(), bitmapInsets)); if (DEBUG_UI) { Log.e(TAG, "Provided bitmap and insets create degenerate region: " + bitmap.getWidth() + "x" + bitmap.getHeight() + " " + bitmapInsets); } return false; } Loading @@ -628,11 +672,10 @@ public class ScreenshotController { float boundsAspect = ((float) screenBounds.width()) / screenBounds.height(); boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f; if (!matchWithinTolerance) { Log.d(TAG, String.format("aspectRatiosMatch: don't match bitmap: %f, bounds: %f", insettedBitmapAspect, boundsAspect)); if (DEBUG_UI) { Log.d(TAG, "aspectRatiosMatch: don't match bitmap: " + insettedBitmapAspect + ", bounds: " + boundsAspect); } return matchWithinTolerance; } }
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java +22 −20 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.systemui.screenshot; import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS; import static com.android.systemui.screenshot.LogConfig.logTag; import android.app.Notification; import android.content.ComponentName; import android.graphics.Bitmap; Loading @@ -32,6 +35,9 @@ import java.util.concurrent.CompletableFuture; * in order to provide smart actions in the screenshot notification. */ public class ScreenshotNotificationSmartActionsProvider { private static final String TAG = logTag(ScreenshotNotificationSmartActionsProvider.class); /* Key provided in the notification action to get the type of smart action. */ public static final String ACTION_TYPE = "action_type"; public static final String DEFAULT_ACTION_TYPE = "Smart Action"; Loading @@ -51,29 +57,21 @@ public class ScreenshotNotificationSmartActionsProvider { ERROR, TIMEOUT } private static final String TAG = "ScreenshotActions"; /** * Default implementation that returns an empty list. * This method is overridden in vendor-specific Sys UI implementation. * * @param screenshotId A generated random unique id for the screenshot. * @param screenshotFileName name of the file where the screenshot will be written. * @param bitmap The bitmap of the screenshot. The bitmap config must be {@link * HARDWARE}. * @param componentName Contains package and activity class names where the screenshot was * taken. This is used as an additional signal to generate and rank * more relevant actions. * @param userHandle The user handle of the app where the screenshot was taken. * @param screenshotId a unique id for the screenshot * @param screenshotUri uri where the screenshot has been stored * @param bitmap the screenshot, config must be {@link Bitmap.Config#HARDWARE} * @param componentName name of the foreground component when the screenshot was taken * @param userHandle user handle of the foreground task owner */ public CompletableFuture<List<Notification.Action>> getActions( String screenshotId, Uri screenshotUri, Bitmap bitmap, ComponentName componentName, UserHandle userHandle) { public CompletableFuture<List<Notification.Action>> getActions(String screenshotId, Uri screenshotUri, Bitmap bitmap, ComponentName componentName, UserHandle userHandle) { if (DEBUG_ACTIONS) { Log.d(TAG, "Returning empty smart action list."); } return CompletableFuture.completedFuture(Collections.emptyList()); } Loading @@ -88,7 +86,9 @@ public class ScreenshotNotificationSmartActionsProvider { */ public void notifyOp(String screenshotId, ScreenshotOp op, ScreenshotOpStatus status, long durationMs) { Log.d(TAG, "Return without notify."); if (DEBUG_ACTIONS) { Log.d(TAG, "SmartActions: notifyOp() - return without notify"); } } /** Loading @@ -100,6 +100,8 @@ public class ScreenshotNotificationSmartActionsProvider { * @param isSmartAction whether action invoked was a smart action. */ public void notifyAction(String screenshotId, String action, boolean isSmartAction) { Log.d(TAG, "Return without notify."); if (DEBUG_ACTIONS) { Log.d(TAG, "SmartActions: notifyAction: return without notify"); } } }
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java +42 −15 File changed.Preview size limit exceeded, changes collapsed. Show changes