Loading packages/SystemUI/src/com/android/systemui/flags/Flags.kt +3 −0 Original line number Diff line number Diff line Loading @@ -493,6 +493,9 @@ object Flags { val SCREENSHOT_WORK_PROFILE_POLICY = unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true) // TODO(b/264916608): Tracking Bug @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata") // 1400 - columbus // TODO(b/254512756): Tracking Bug val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc") Loading packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt +56 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ class RequestProcessor @Inject constructor( * * @param request the request to process */ // TODO: Delete once SCREENSHOT_METADATA flag is launched suspend fun process(request: ScreenshotRequest): ScreenshotRequest { var result = request Loading Loading @@ -93,12 +94,67 @@ class RequestProcessor @Inject constructor( * @param request the request to process * @param callback the callback to provide the processed request, invoked from the main thread */ // TODO: Delete once SCREENSHOT_METADATA flag is launched fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) { mainScope.launch { val result = process(request) callback.accept(result) } } /** * Inspects the incoming ScreenshotData, potentially modifying it based upon policy. * * @param screenshot the screenshot to process */ suspend fun process(screenshot: ScreenshotData): ScreenshotData { var result = screenshot // Apply work profile screenshots policy: // // If the focused app belongs to a work profile, transforms a full screen // (or partial) screenshot request to a task snapshot (provided image) screenshot. // Whenever displayContentInfo is fetched, the topComponent is also populated // regardless of the managed profile status. if (screenshot.type != TAKE_SCREENSHOT_PROVIDED_IMAGE && flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY) ) { val info = policy.findPrimaryContent(policy.getDefaultDisplayId()) Log.d(TAG, "findPrimaryContent: $info") result.taskId = info.taskId result.topComponent = info.component result.userHandle = info.user if (policy.isManagedProfile(info.user.identifier)) { val image = capture.captureTask(info.taskId) ?: error("Task snapshot returned a null Bitmap!") // Provide the task snapshot as the screenshot result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE result.bitmap = image result.screenBounds = info.bounds } } return result } /** * Note: This is for compatibility with existing Java. Prefer the suspending function when * calling from a Coroutine context. * * @param screenshot the screenshot to process * @param callback the callback to provide the processed screenshot, invoked from the main * thread */ fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) { mainScope.launch { val result = process(screenshot) callback.accept(result) } } } private const val TAG = "RequestProcessor" packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +162 −29 Original line number Diff line number Diff line Loading @@ -390,16 +390,131 @@ public class ScreenshotController { ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED); } void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, RequestCallback requestCallback) { Assert.isMainThread(); mCurrentRequestCallback = requestCallback; if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) { Rect bounds = getFullScreenRect(); screenshot.setBitmap(mImageCapture.captureDisplay(DEFAULT_DISPLAY, bounds)); screenshot.setScreenBounds(bounds); } if (screenshot.getBitmap() == null) { Log.e(TAG, "handleScreenshot: Screenshot bitmap was null"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); if (mCurrentRequestCallback != null) { mCurrentRequestCallback.reportError(); } return; } if (!isUserSetupComplete(Process.myUserHandle())) { 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(screenshot.getUserHandle(), finisher); return; } mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), ClipboardOverlayController.SELF_PERMISSION); mScreenshotTakenInPortrait = mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; String oldPackageName = mPackageName; mPackageName = screenshot.getPackageNameString(); mScreenBitmap = screenshot.getBitmap(); // Optimizations mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); prepareViewForNewScreenshot(screenshot, oldPackageName); saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher, this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); // The window is focusable by default setWindowFocusable(true); mScreenshotView.requestFocus(); enqueueScrollCaptureRequest(screenshot.getUserHandle()); attachWindow(); boolean showFlash = true; if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { if (screenshot.getScreenBounds() != null && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(), screenshot.getScreenBounds())) { showFlash = false; } else { showFlash = true; screenshot.setInsets(Insets.NONE); screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(), screenshot.getBitmap().getHeight())); } } prepareAnimation(screenshot.getScreenBounds(), showFlash); if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( mContext.getDrawable(R.drawable.overlay_badge_background), screenshot.getUserHandle())); } mScreenshotView.setScreenshot(mScreenBitmap, screenshot.getInsets()); if (DEBUG_WINDOW) { Log.d(TAG, "setContentView: " + mScreenshotView); } setContentView(mScreenshotView); // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); mScreenshotHandler.cancelTimeout(); // restarted after animation } void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) { withWindowAttached(() -> { if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY) && mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) { mScreenshotView.announceForAccessibility(mContext.getResources().getString( R.string.screenshot_saving_work_profile_title)); } else { mScreenshotView.announceForAccessibility( mContext.getResources().getString(R.string.screenshot_saving_title)); } }); mScreenshotView.reset(); if (mScreenshotView.isAttachedToWindow()) { // if we didn't already dismiss for another reason if (!mScreenshotView.isDismissing()) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, oldPackageName); } if (DEBUG_WINDOW) { Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " + "(dismissing=" + mScreenshotView.isDismissing() + ")"); } } mScreenshotView.setPackageName(mPackageName); mScreenshotView.updateOrientation( mWindowManager.getCurrentWindowMetrics().getWindowInsets()); } @MainThread void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher, RequestCallback requestCallback) { Assert.isMainThread(); mCurrentRequestCallback = requestCallback; DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); takeScreenshotInternal( topComponent, finisher, new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)); takeScreenshotInternal(topComponent, finisher, getFullScreenRect()); } @MainThread Loading Loading @@ -641,6 +756,42 @@ public class ScreenshotController { setWindowFocusable(true); mScreenshotView.requestFocus(); enqueueScrollCaptureRequest(owner); attachWindow(); prepareAnimation(screenRect, showFlash); if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( mContext.getDrawable(R.drawable.overlay_badge_background), owner)); } mScreenshotView.setScreenshot(mScreenBitmap, screenInsets); if (DEBUG_WINDOW) { Log.d(TAG, "setContentView: " + mScreenshotView); } setContentView(mScreenshotView); // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); mScreenshotHandler.cancelTimeout(); // restarted after animation } private void prepareAnimation(Rect screenRect, boolean showFlash) { mScreenshotView.getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { if (DEBUG_WINDOW) { Log.d(TAG, "onPreDraw: startAnimation"); } mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this); startAnimation(screenRect, showFlash); return true; } }); } private void enqueueScrollCaptureRequest(UserHandle owner) { // Wait until this window is attached to request because it is // the reference used to locate the target window (below). withWindowAttached(() -> { Loading Loading @@ -678,30 +829,6 @@ public class ScreenshotController { } }); }); attachWindow(); mScreenshotView.getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { if (DEBUG_WINDOW) { Log.d(TAG, "onPreDraw: startAnimation"); } mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this); startAnimation(screenRect, showFlash); return true; } }); if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( mContext.getDrawable(R.drawable.overlay_badge_background), owner)); } mScreenshotView.setScreenshot(mScreenBitmap, screenInsets); // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); mScreenshotHandler.cancelTimeout(); // restarted after animation } private void requestScrollCapture(UserHandle owner) { Loading Loading @@ -1154,6 +1281,12 @@ public class ScreenshotController { return !mIsLowRamDevice; } private Rect getFullScreenRect() { DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels); } /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) { Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt 0 → 100644 +43 −0 Original line number Diff line number Diff line package com.android.systemui.screenshot import android.content.ComponentName import android.graphics.Bitmap import android.graphics.Insets import android.graphics.Rect import android.os.UserHandle import android.view.WindowManager.ScreenshotSource import android.view.WindowManager.ScreenshotType import com.android.internal.util.ScreenshotRequest /** ScreenshotData represents the current state of a single screenshot being acquired. */ data class ScreenshotData( @ScreenshotType var type: Int, @ScreenshotSource var source: Int, /** UserHandle for the owner of the app being screenshotted, if known. */ var userHandle: UserHandle?, /** ComponentName of the top-most app in the screenshot. */ var topComponent: ComponentName?, var screenBounds: Rect?, var taskId: Int, var insets: Insets, var bitmap: Bitmap?, ) { val packageNameString: String get() = if (topComponent == null) "" else topComponent!!.packageName companion object { @JvmStatic fun fromRequest(request: ScreenshotRequest): ScreenshotData { return ScreenshotData( request.type, request.source, if (request.userId >= 0) UserHandle.of(request.userId) else null, request.topComponent, request.boundsInScreen, request.taskId, request.insets, request.bitmap, ) } } } packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +28 −2 Original line number Diff line number Diff line Loading @@ -59,6 +59,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagListenable.FlagEvent; import com.android.systemui.flags.Flags; import java.util.concurrent.Executor; import java.util.function.Consumer; Loading Loading @@ -221,9 +222,34 @@ public class TakeScreenshotService extends Service { return; } if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA)) { Log.d(TAG, "Processing screenshot data"); ScreenshotData screenshotData = ScreenshotData.fromRequest(request); mProcessor.processAsync(screenshotData, (data) -> dispatchToController(data, onSaved, callback)); } else { mProcessor.processAsync(request, (r) -> dispatchToController(r, onSaved, callback)); } } private void dispatchToController(ScreenshotData screenshot, Consumer<Uri> uriConsumer, RequestCallback callback) { mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0, screenshot.getPackageNameString()); if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE && screenshot.getBitmap() == null) { Log.e(TAG, "Got null bitmap from screenshot message"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); callback.reportError(); return; } mScreenshot.handleScreenshot(screenshot, uriConsumer, callback); } private void dispatchToController(ScreenshotRequest request, Consumer<Uri> uriConsumer, RequestCallback callback) { Loading Loading
packages/SystemUI/src/com/android/systemui/flags/Flags.kt +3 −0 Original line number Diff line number Diff line Loading @@ -493,6 +493,9 @@ object Flags { val SCREENSHOT_WORK_PROFILE_POLICY = unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true) // TODO(b/264916608): Tracking Bug @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata") // 1400 - columbus // TODO(b/254512756): Tracking Bug val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc") Loading
packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt +56 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ class RequestProcessor @Inject constructor( * * @param request the request to process */ // TODO: Delete once SCREENSHOT_METADATA flag is launched suspend fun process(request: ScreenshotRequest): ScreenshotRequest { var result = request Loading Loading @@ -93,12 +94,67 @@ class RequestProcessor @Inject constructor( * @param request the request to process * @param callback the callback to provide the processed request, invoked from the main thread */ // TODO: Delete once SCREENSHOT_METADATA flag is launched fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) { mainScope.launch { val result = process(request) callback.accept(result) } } /** * Inspects the incoming ScreenshotData, potentially modifying it based upon policy. * * @param screenshot the screenshot to process */ suspend fun process(screenshot: ScreenshotData): ScreenshotData { var result = screenshot // Apply work profile screenshots policy: // // If the focused app belongs to a work profile, transforms a full screen // (or partial) screenshot request to a task snapshot (provided image) screenshot. // Whenever displayContentInfo is fetched, the topComponent is also populated // regardless of the managed profile status. if (screenshot.type != TAKE_SCREENSHOT_PROVIDED_IMAGE && flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY) ) { val info = policy.findPrimaryContent(policy.getDefaultDisplayId()) Log.d(TAG, "findPrimaryContent: $info") result.taskId = info.taskId result.topComponent = info.component result.userHandle = info.user if (policy.isManagedProfile(info.user.identifier)) { val image = capture.captureTask(info.taskId) ?: error("Task snapshot returned a null Bitmap!") // Provide the task snapshot as the screenshot result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE result.bitmap = image result.screenBounds = info.bounds } } return result } /** * Note: This is for compatibility with existing Java. Prefer the suspending function when * calling from a Coroutine context. * * @param screenshot the screenshot to process * @param callback the callback to provide the processed screenshot, invoked from the main * thread */ fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) { mainScope.launch { val result = process(screenshot) callback.accept(result) } } } private const val TAG = "RequestProcessor"
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +162 −29 Original line number Diff line number Diff line Loading @@ -390,16 +390,131 @@ public class ScreenshotController { ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED); } void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, RequestCallback requestCallback) { Assert.isMainThread(); mCurrentRequestCallback = requestCallback; if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) { Rect bounds = getFullScreenRect(); screenshot.setBitmap(mImageCapture.captureDisplay(DEFAULT_DISPLAY, bounds)); screenshot.setScreenBounds(bounds); } if (screenshot.getBitmap() == null) { Log.e(TAG, "handleScreenshot: Screenshot bitmap was null"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); if (mCurrentRequestCallback != null) { mCurrentRequestCallback.reportError(); } return; } if (!isUserSetupComplete(Process.myUserHandle())) { 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(screenshot.getUserHandle(), finisher); return; } mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), ClipboardOverlayController.SELF_PERMISSION); mScreenshotTakenInPortrait = mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; String oldPackageName = mPackageName; mPackageName = screenshot.getPackageNameString(); mScreenBitmap = screenshot.getBitmap(); // Optimizations mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); prepareViewForNewScreenshot(screenshot, oldPackageName); saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher, this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); // The window is focusable by default setWindowFocusable(true); mScreenshotView.requestFocus(); enqueueScrollCaptureRequest(screenshot.getUserHandle()); attachWindow(); boolean showFlash = true; if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { if (screenshot.getScreenBounds() != null && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(), screenshot.getScreenBounds())) { showFlash = false; } else { showFlash = true; screenshot.setInsets(Insets.NONE); screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(), screenshot.getBitmap().getHeight())); } } prepareAnimation(screenshot.getScreenBounds(), showFlash); if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( mContext.getDrawable(R.drawable.overlay_badge_background), screenshot.getUserHandle())); } mScreenshotView.setScreenshot(mScreenBitmap, screenshot.getInsets()); if (DEBUG_WINDOW) { Log.d(TAG, "setContentView: " + mScreenshotView); } setContentView(mScreenshotView); // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); mScreenshotHandler.cancelTimeout(); // restarted after animation } void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) { withWindowAttached(() -> { if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY) && mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) { mScreenshotView.announceForAccessibility(mContext.getResources().getString( R.string.screenshot_saving_work_profile_title)); } else { mScreenshotView.announceForAccessibility( mContext.getResources().getString(R.string.screenshot_saving_title)); } }); mScreenshotView.reset(); if (mScreenshotView.isAttachedToWindow()) { // if we didn't already dismiss for another reason if (!mScreenshotView.isDismissing()) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, oldPackageName); } if (DEBUG_WINDOW) { Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " + "(dismissing=" + mScreenshotView.isDismissing() + ")"); } } mScreenshotView.setPackageName(mPackageName); mScreenshotView.updateOrientation( mWindowManager.getCurrentWindowMetrics().getWindowInsets()); } @MainThread void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher, RequestCallback requestCallback) { Assert.isMainThread(); mCurrentRequestCallback = requestCallback; DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); takeScreenshotInternal( topComponent, finisher, new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)); takeScreenshotInternal(topComponent, finisher, getFullScreenRect()); } @MainThread Loading Loading @@ -641,6 +756,42 @@ public class ScreenshotController { setWindowFocusable(true); mScreenshotView.requestFocus(); enqueueScrollCaptureRequest(owner); attachWindow(); prepareAnimation(screenRect, showFlash); if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( mContext.getDrawable(R.drawable.overlay_badge_background), owner)); } mScreenshotView.setScreenshot(mScreenBitmap, screenInsets); if (DEBUG_WINDOW) { Log.d(TAG, "setContentView: " + mScreenshotView); } setContentView(mScreenshotView); // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); mScreenshotHandler.cancelTimeout(); // restarted after animation } private void prepareAnimation(Rect screenRect, boolean showFlash) { mScreenshotView.getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { if (DEBUG_WINDOW) { Log.d(TAG, "onPreDraw: startAnimation"); } mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this); startAnimation(screenRect, showFlash); return true; } }); } private void enqueueScrollCaptureRequest(UserHandle owner) { // Wait until this window is attached to request because it is // the reference used to locate the target window (below). withWindowAttached(() -> { Loading Loading @@ -678,30 +829,6 @@ public class ScreenshotController { } }); }); attachWindow(); mScreenshotView.getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { if (DEBUG_WINDOW) { Log.d(TAG, "onPreDraw: startAnimation"); } mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this); startAnimation(screenRect, showFlash); return true; } }); if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( mContext.getDrawable(R.drawable.overlay_badge_background), owner)); } mScreenshotView.setScreenshot(mScreenBitmap, screenInsets); // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); mScreenshotHandler.cancelTimeout(); // restarted after animation } private void requestScrollCapture(UserHandle owner) { Loading Loading @@ -1154,6 +1281,12 @@ public class ScreenshotController { return !mIsLowRamDevice; } private Rect getFullScreenRect() { DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels); } /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) { Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt 0 → 100644 +43 −0 Original line number Diff line number Diff line package com.android.systemui.screenshot import android.content.ComponentName import android.graphics.Bitmap import android.graphics.Insets import android.graphics.Rect import android.os.UserHandle import android.view.WindowManager.ScreenshotSource import android.view.WindowManager.ScreenshotType import com.android.internal.util.ScreenshotRequest /** ScreenshotData represents the current state of a single screenshot being acquired. */ data class ScreenshotData( @ScreenshotType var type: Int, @ScreenshotSource var source: Int, /** UserHandle for the owner of the app being screenshotted, if known. */ var userHandle: UserHandle?, /** ComponentName of the top-most app in the screenshot. */ var topComponent: ComponentName?, var screenBounds: Rect?, var taskId: Int, var insets: Insets, var bitmap: Bitmap?, ) { val packageNameString: String get() = if (topComponent == null) "" else topComponent!!.packageName companion object { @JvmStatic fun fromRequest(request: ScreenshotRequest): ScreenshotData { return ScreenshotData( request.type, request.source, if (request.userId >= 0) UserHandle.of(request.userId) else null, request.topComponent, request.boundsInScreen, request.taskId, request.insets, request.bitmap, ) } } }
packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +28 −2 Original line number Diff line number Diff line Loading @@ -59,6 +59,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagListenable.FlagEvent; import com.android.systemui.flags.Flags; import java.util.concurrent.Executor; import java.util.function.Consumer; Loading Loading @@ -221,9 +222,34 @@ public class TakeScreenshotService extends Service { return; } if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA)) { Log.d(TAG, "Processing screenshot data"); ScreenshotData screenshotData = ScreenshotData.fromRequest(request); mProcessor.processAsync(screenshotData, (data) -> dispatchToController(data, onSaved, callback)); } else { mProcessor.processAsync(request, (r) -> dispatchToController(r, onSaved, callback)); } } private void dispatchToController(ScreenshotData screenshot, Consumer<Uri> uriConsumer, RequestCallback callback) { mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0, screenshot.getPackageNameString()); if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE && screenshot.getBitmap() == null) { Log.e(TAG, "Got null bitmap from screenshot message"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); callback.reportError(); return; } mScreenshot.handleScreenshot(screenshot, uriConsumer, callback); } private void dispatchToController(ScreenshotRequest request, Consumer<Uri> uriConsumer, RequestCallback callback) { Loading