Loading packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +18 −32 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.screenshot; import static android.os.FileUtils.closeQuietly; import android.annotation.IntRange; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.graphics.Bitmap; Loading @@ -29,6 +30,7 @@ import android.os.Environment; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.provider.MediaStore; import android.util.Log; Loading Loading @@ -142,8 +144,9 @@ class ImageExporter { * * @return a listenable future result */ ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap) { return export(executor, requestId, bitmap, ZonedDateTime.now()); ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, UserHandle owner) { return export(executor, requestId, bitmap, ZonedDateTime.now(), owner); } /** Loading @@ -155,10 +158,10 @@ class ImageExporter { * @return a listenable future result */ ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime) { ZonedDateTime captureTime, UserHandle owner) { final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, mQuality, /* publish */ true); mQuality, /* publish */ true, owner); return CallbackToFutureAdapter.getFuture( (completer) -> { Loading @@ -174,28 +177,6 @@ class ImageExporter { ); } /** * Delete the entry. * * @param executor the thread for execution * @param uri the uri of the image to publish * * @return a listenable future result */ ListenableFuture<Result> delete(Executor executor, Uri uri) { return CallbackToFutureAdapter.getFuture((completer) -> { executor.execute(() -> { mResolver.delete(uri, null); Result result = new Result(); result.uri = uri; result.deleted = true; completer.set(result); }); return "ContentResolver#delete"; }); } static class Result { Uri uri; UUID requestId; Loading @@ -203,7 +184,6 @@ class ImageExporter { long timestamp; CompressFormat format; boolean published; boolean deleted; @Override public String toString() { Loading @@ -214,7 +194,6 @@ class ImageExporter { sb.append(", timestamp=").append(timestamp); sb.append(", format=").append(format); sb.append(", published=").append(published); sb.append(", deleted=").append(deleted); sb.append('}'); return sb.toString(); } Loading @@ -227,17 +206,19 @@ class ImageExporter { private final ZonedDateTime mCaptureTime; private final CompressFormat mFormat; private final int mQuality; private final UserHandle mOwner; private final String mFileName; private final boolean mPublish; Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, CompressFormat format, int quality, boolean publish) { CompressFormat format, int quality, boolean publish, UserHandle owner) { mResolver = resolver; mRequestId = requestId; mBitmap = bitmap; mCaptureTime = captureTime; mFormat = format; mQuality = quality; mOwner = owner; mFileName = createFilename(mCaptureTime, mFormat); mPublish = publish; } Loading @@ -253,7 +234,7 @@ class ImageExporter { start = Instant.now(); } uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName); uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner); throwIfInterrupted(); writeImage(mResolver, mBitmap, mFormat, mQuality, uri); Loading Loading @@ -297,15 +278,20 @@ class ImageExporter { } private static Uri createEntry(ContentResolver resolver, CompressFormat format, ZonedDateTime time, String fileName) throws ImageExportException { ZonedDateTime time, String fileName, UserHandle owner) throws ImageExportException { Trace.beginSection("ImageExporter_createEntry"); try { final ContentValues values = createMetadata(time, format, fileName); Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; if (UserHandle.myUserId() != owner.getIdentifier()) { baseUri = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier()); } Uri uri = resolver.insert(baseUri, values); if (uri == null) { throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL); } Log.d(TAG, "Inserted new URI: " + uri); return uri; } finally { Trace.endSection(); Loading packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +4 −1 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; Loading Loading @@ -387,7 +388,9 @@ public class LongScreenshotActivity extends Activity { mOutputBitmap = renderBitmap(drawable, bounds); ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export( mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now()); mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now(), // TODO: Owner must match the owner of the captured window. Process.myUserHandle()); exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor); } Loading packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +13 −3 Original line number Diff line number Diff line Loading @@ -48,6 +48,8 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.google.common.util.concurrent.ListenableFuture; Loading @@ -71,6 +73,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"; private final Context mContext; private FeatureFlags mFlags; private final ScreenshotSmartActions mScreenshotSmartActions; private final ScreenshotController.SaveImageInBackgroundData mParams; private final ScreenshotController.SavedImageData mImageData; Loading @@ -84,7 +87,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final ImageExporter mImageExporter; private long mImageTime; SaveImageInBackgroundTask(Context context, ImageExporter exporter, SaveImageInBackgroundTask( Context context, FeatureFlags flags, ImageExporter exporter, ScreenshotSmartActions screenshotSmartActions, ScreenshotController.SaveImageInBackgroundData data, Supplier<ActionTransition> sharedElementTransition, Loading @@ -92,6 +98,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { screenshotNotificationSmartActionsProvider ) { mContext = context; mFlags = flags; mScreenshotSmartActions = screenshotSmartActions; mImageData = new ScreenshotController.SavedImageData(); mQuickShareData = new ScreenshotController.QuickShareData(); Loading @@ -117,7 +124,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { } // TODO: move to constructor / from ScreenshotRequest final UUID requestId = UUID.randomUUID(); final UserHandle user = getUserHandleOfForegroundApplication(mContext); final UserHandle user = mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY) ? mParams.owner : getUserHandleOfForegroundApplication(mContext); Thread.currentThread().setPriority(Thread.MAX_PRIORITY); Loading @@ -133,8 +141,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Call synchronously here since already on a background thread. ListenableFuture<ImageExporter.Result> future = mImageExporter.export(Runnable::run, requestId, image); mImageExporter.export(Runnable::run, requestId, image, mParams.owner); ImageExporter.Result result = future.get(); Log.d(TAG, "Saved screenshot: " + result); final Uri uri = result.uri; mImageTime = result.timestamp; Loading @@ -157,6 +166,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { } mImageData.uri = uri; mImageData.owner = user; mImageData.smartActions = smartActions; mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri); mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri); Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +38 −15 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import static java.util.Objects.requireNonNull; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; Loading @@ -57,7 +58,9 @@ import android.media.AudioSystem; import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; Loading Loading @@ -90,6 +93,7 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.clipboardoverlay.ClipboardOverlayController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; import com.android.systemui.util.Assert; Loading Loading @@ -151,6 +155,7 @@ public class ScreenshotController { public Consumer<Uri> finisher; public ScreenshotController.ActionsReadyListener mActionsReadyListener; public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener; public UserHandle owner; void clearImage() { image = null; Loading @@ -167,6 +172,8 @@ public class ScreenshotController { public Notification.Action deleteAction; public List<Notification.Action> smartActions; public Notification.Action quickShareAction; public UserHandle owner; /** * POD for shared element transition. Loading Loading @@ -242,6 +249,7 @@ public class ScreenshotController { private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000; private final WindowContext mContext; private final FeatureFlags mFlags; private final ScreenshotNotificationsController mNotificationsController; private final ScreenshotSmartActions mScreenshotSmartActions; private final UiEventLogger mUiEventLogger; Loading Loading @@ -288,6 +296,7 @@ public class ScreenshotController { @Inject ScreenshotController( Context context, FeatureFlags flags, ScreenshotSmartActions screenshotSmartActions, ScreenshotNotificationsController screenshotNotificationsController, ScrollCaptureClient scrollCaptureClient, Loading Loading @@ -331,6 +340,7 @@ public class ScreenshotController { final Context displayContext = context.createDisplayContext(getDefaultDisplay()); mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); mWindowManager = mContext.getSystemService(WindowManager.class); mFlags = flags; mAccessibilityManager = AccessibilityManager.getInstance(mContext); Loading Loading @@ -377,7 +387,6 @@ public class ScreenshotController { void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, Insets visibleInsets, int taskId, int userId, ComponentName topComponent, Consumer<Uri> finisher, RequestCallback requestCallback) { // TODO: use task Id, userId, topComponent for smart handler Assert.isMainThread(); if (screenshot == null) { Log.e(TAG, "Got null bitmap from screenshot message"); Loading @@ -395,7 +404,7 @@ public class ScreenshotController { } mCurrentRequestCallback = requestCallback; saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent, showFlash); showFlash, UserHandle.of(userId)); } /** Loading Loading @@ -543,14 +552,15 @@ public class ScreenshotController { return; } saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true); saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true, Process.myUserHandle()); mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), ClipboardOverlayController.SELF_PERMISSION); } private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, ComponentName topComponent, boolean showFlash) { Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) { withWindowAttached(() -> mScreenshotView.announceForAccessibility( mContext.getResources().getString(R.string.screenshot_saving_title))); Loading @@ -575,11 +585,11 @@ public class ScreenshotController { mScreenBitmap = screenshot; if (!isUserSetupComplete()) { if (!isUserSetupComplete(owner)) { 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); saveScreenshotAndToast(owner, finisher); return; } Loading @@ -587,7 +597,7 @@ public class ScreenshotController { mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady, saveScreenshotInWorkerThread(owner, finisher, this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); // The window is focusable by default Loading Loading @@ -853,11 +863,12 @@ public class ScreenshotController { * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on * failure). */ private void saveScreenshotAndToast(Consumer<Uri> finisher) { private void saveScreenshotAndToast(UserHandle owner, Consumer<Uri> finisher) { // Play the shutter sound to notify that we've taken a screenshot playCameraSound(); saveScreenshotInWorkerThread( owner, /* onComplete */ finisher, /* actionsReadyListener */ imageData -> { if (DEBUG_CALLBACK) { Loading Loading @@ -925,9 +936,11 @@ public class ScreenshotController { /** * Creates a new worker thread and saves the screenshot to the media store. */ private void saveScreenshotInWorkerThread(Consumer<Uri> finisher, @Nullable ScreenshotController.ActionsReadyListener actionsReadyListener, @Nullable ScreenshotController.QuickShareActionReadyListener private void saveScreenshotInWorkerThread( UserHandle owner, @NonNull Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener, @Nullable QuickShareActionReadyListener quickShareActionsReadyListener) { ScreenshotController.SaveImageInBackgroundData data = new ScreenshotController.SaveImageInBackgroundData(); Loading @@ -935,13 +948,14 @@ public class ScreenshotController { data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; data.mQuickShareActionsReadyListener = quickShareActionsReadyListener; data.owner = owner; if (mSaveInBgTask != null) { // just log success/failure for the pre-existing screenshot mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mImageExporter, mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter, mScreenshotSmartActions, data, getActionTransitionSupplier(), mScreenshotNotificationSmartActionsProvider); mSaveInBgTask.execute(); Loading @@ -960,6 +974,15 @@ public class ScreenshotController { mScreenshotHandler.resetTimeout(); if (imageData.uri != null) { if (!imageData.owner.equals(Process.myUserHandle())) { // TODO: Handle non-primary user ownership (e.g. Work Profile) // This image is owned by another user. Special treatment will be // required in the UI (badging) as well as sending intents which can // correctly forward those URIs on to be read (actions). Log.d(TAG, "*** Screenshot saved to a non-primary user (" + imageData.owner + ") as " + imageData.uri); } mScreenshotHandler.post(() -> { if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { Loading Loading @@ -1033,9 +1056,9 @@ public class ScreenshotController { } } private boolean isUserSetupComplete() { return Settings.Secure.getInt(mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; private boolean isUserSetupComplete(UserHandle owner) { return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0) .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; } /** Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt +3 −1 Original line number Diff line number Diff line Loading @@ -68,7 +68,9 @@ internal open class ScreenshotPolicyImpl @Inject constructor( } override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean { return withContext(bgDispatcher) { userMgr.isManagedProfile(userId) } val managed = withContext(bgDispatcher) { userMgr.isManagedProfile(userId) } Log.d(TAG, "isManagedProfile: $managed") return managed } private fun nonPipVisibleTask(info: RootTaskInfo): Boolean { Loading Loading
packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +18 −32 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.screenshot; import static android.os.FileUtils.closeQuietly; import android.annotation.IntRange; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.graphics.Bitmap; Loading @@ -29,6 +30,7 @@ import android.os.Environment; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.provider.MediaStore; import android.util.Log; Loading Loading @@ -142,8 +144,9 @@ class ImageExporter { * * @return a listenable future result */ ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap) { return export(executor, requestId, bitmap, ZonedDateTime.now()); ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, UserHandle owner) { return export(executor, requestId, bitmap, ZonedDateTime.now(), owner); } /** Loading @@ -155,10 +158,10 @@ class ImageExporter { * @return a listenable future result */ ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime) { ZonedDateTime captureTime, UserHandle owner) { final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, mQuality, /* publish */ true); mQuality, /* publish */ true, owner); return CallbackToFutureAdapter.getFuture( (completer) -> { Loading @@ -174,28 +177,6 @@ class ImageExporter { ); } /** * Delete the entry. * * @param executor the thread for execution * @param uri the uri of the image to publish * * @return a listenable future result */ ListenableFuture<Result> delete(Executor executor, Uri uri) { return CallbackToFutureAdapter.getFuture((completer) -> { executor.execute(() -> { mResolver.delete(uri, null); Result result = new Result(); result.uri = uri; result.deleted = true; completer.set(result); }); return "ContentResolver#delete"; }); } static class Result { Uri uri; UUID requestId; Loading @@ -203,7 +184,6 @@ class ImageExporter { long timestamp; CompressFormat format; boolean published; boolean deleted; @Override public String toString() { Loading @@ -214,7 +194,6 @@ class ImageExporter { sb.append(", timestamp=").append(timestamp); sb.append(", format=").append(format); sb.append(", published=").append(published); sb.append(", deleted=").append(deleted); sb.append('}'); return sb.toString(); } Loading @@ -227,17 +206,19 @@ class ImageExporter { private final ZonedDateTime mCaptureTime; private final CompressFormat mFormat; private final int mQuality; private final UserHandle mOwner; private final String mFileName; private final boolean mPublish; Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, CompressFormat format, int quality, boolean publish) { CompressFormat format, int quality, boolean publish, UserHandle owner) { mResolver = resolver; mRequestId = requestId; mBitmap = bitmap; mCaptureTime = captureTime; mFormat = format; mQuality = quality; mOwner = owner; mFileName = createFilename(mCaptureTime, mFormat); mPublish = publish; } Loading @@ -253,7 +234,7 @@ class ImageExporter { start = Instant.now(); } uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName); uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner); throwIfInterrupted(); writeImage(mResolver, mBitmap, mFormat, mQuality, uri); Loading Loading @@ -297,15 +278,20 @@ class ImageExporter { } private static Uri createEntry(ContentResolver resolver, CompressFormat format, ZonedDateTime time, String fileName) throws ImageExportException { ZonedDateTime time, String fileName, UserHandle owner) throws ImageExportException { Trace.beginSection("ImageExporter_createEntry"); try { final ContentValues values = createMetadata(time, format, fileName); Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; if (UserHandle.myUserId() != owner.getIdentifier()) { baseUri = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier()); } Uri uri = resolver.insert(baseUri, values); if (uri == null) { throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL); } Log.d(TAG, "Inserted new URI: " + uri); return uri; } finally { Trace.endSection(); Loading
packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +4 −1 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; Loading Loading @@ -387,7 +388,9 @@ public class LongScreenshotActivity extends Activity { mOutputBitmap = renderBitmap(drawable, bounds); ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export( mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now()); mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now(), // TODO: Owner must match the owner of the captured window. Process.myUserHandle()); exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor); } Loading
packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +13 −3 Original line number Diff line number Diff line Loading @@ -48,6 +48,8 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.google.common.util.concurrent.ListenableFuture; Loading @@ -71,6 +73,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"; private final Context mContext; private FeatureFlags mFlags; private final ScreenshotSmartActions mScreenshotSmartActions; private final ScreenshotController.SaveImageInBackgroundData mParams; private final ScreenshotController.SavedImageData mImageData; Loading @@ -84,7 +87,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final ImageExporter mImageExporter; private long mImageTime; SaveImageInBackgroundTask(Context context, ImageExporter exporter, SaveImageInBackgroundTask( Context context, FeatureFlags flags, ImageExporter exporter, ScreenshotSmartActions screenshotSmartActions, ScreenshotController.SaveImageInBackgroundData data, Supplier<ActionTransition> sharedElementTransition, Loading @@ -92,6 +98,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { screenshotNotificationSmartActionsProvider ) { mContext = context; mFlags = flags; mScreenshotSmartActions = screenshotSmartActions; mImageData = new ScreenshotController.SavedImageData(); mQuickShareData = new ScreenshotController.QuickShareData(); Loading @@ -117,7 +124,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { } // TODO: move to constructor / from ScreenshotRequest final UUID requestId = UUID.randomUUID(); final UserHandle user = getUserHandleOfForegroundApplication(mContext); final UserHandle user = mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY) ? mParams.owner : getUserHandleOfForegroundApplication(mContext); Thread.currentThread().setPriority(Thread.MAX_PRIORITY); Loading @@ -133,8 +141,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Call synchronously here since already on a background thread. ListenableFuture<ImageExporter.Result> future = mImageExporter.export(Runnable::run, requestId, image); mImageExporter.export(Runnable::run, requestId, image, mParams.owner); ImageExporter.Result result = future.get(); Log.d(TAG, "Saved screenshot: " + result); final Uri uri = result.uri; mImageTime = result.timestamp; Loading @@ -157,6 +166,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { } mImageData.uri = uri; mImageData.owner = user; mImageData.smartActions = smartActions; mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri); mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri); Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +38 −15 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import static java.util.Objects.requireNonNull; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; Loading @@ -57,7 +58,9 @@ import android.media.AudioSystem; import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; Loading Loading @@ -90,6 +93,7 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.clipboardoverlay.ClipboardOverlayController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; import com.android.systemui.util.Assert; Loading Loading @@ -151,6 +155,7 @@ public class ScreenshotController { public Consumer<Uri> finisher; public ScreenshotController.ActionsReadyListener mActionsReadyListener; public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener; public UserHandle owner; void clearImage() { image = null; Loading @@ -167,6 +172,8 @@ public class ScreenshotController { public Notification.Action deleteAction; public List<Notification.Action> smartActions; public Notification.Action quickShareAction; public UserHandle owner; /** * POD for shared element transition. Loading Loading @@ -242,6 +249,7 @@ public class ScreenshotController { private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000; private final WindowContext mContext; private final FeatureFlags mFlags; private final ScreenshotNotificationsController mNotificationsController; private final ScreenshotSmartActions mScreenshotSmartActions; private final UiEventLogger mUiEventLogger; Loading Loading @@ -288,6 +296,7 @@ public class ScreenshotController { @Inject ScreenshotController( Context context, FeatureFlags flags, ScreenshotSmartActions screenshotSmartActions, ScreenshotNotificationsController screenshotNotificationsController, ScrollCaptureClient scrollCaptureClient, Loading Loading @@ -331,6 +340,7 @@ public class ScreenshotController { final Context displayContext = context.createDisplayContext(getDefaultDisplay()); mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); mWindowManager = mContext.getSystemService(WindowManager.class); mFlags = flags; mAccessibilityManager = AccessibilityManager.getInstance(mContext); Loading Loading @@ -377,7 +387,6 @@ public class ScreenshotController { void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, Insets visibleInsets, int taskId, int userId, ComponentName topComponent, Consumer<Uri> finisher, RequestCallback requestCallback) { // TODO: use task Id, userId, topComponent for smart handler Assert.isMainThread(); if (screenshot == null) { Log.e(TAG, "Got null bitmap from screenshot message"); Loading @@ -395,7 +404,7 @@ public class ScreenshotController { } mCurrentRequestCallback = requestCallback; saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent, showFlash); showFlash, UserHandle.of(userId)); } /** Loading Loading @@ -543,14 +552,15 @@ public class ScreenshotController { return; } saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true); saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true, Process.myUserHandle()); mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), ClipboardOverlayController.SELF_PERMISSION); } private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, ComponentName topComponent, boolean showFlash) { Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) { withWindowAttached(() -> mScreenshotView.announceForAccessibility( mContext.getResources().getString(R.string.screenshot_saving_title))); Loading @@ -575,11 +585,11 @@ public class ScreenshotController { mScreenBitmap = screenshot; if (!isUserSetupComplete()) { if (!isUserSetupComplete(owner)) { 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); saveScreenshotAndToast(owner, finisher); return; } Loading @@ -587,7 +597,7 @@ public class ScreenshotController { mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady, saveScreenshotInWorkerThread(owner, finisher, this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); // The window is focusable by default Loading Loading @@ -853,11 +863,12 @@ public class ScreenshotController { * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on * failure). */ private void saveScreenshotAndToast(Consumer<Uri> finisher) { private void saveScreenshotAndToast(UserHandle owner, Consumer<Uri> finisher) { // Play the shutter sound to notify that we've taken a screenshot playCameraSound(); saveScreenshotInWorkerThread( owner, /* onComplete */ finisher, /* actionsReadyListener */ imageData -> { if (DEBUG_CALLBACK) { Loading Loading @@ -925,9 +936,11 @@ public class ScreenshotController { /** * Creates a new worker thread and saves the screenshot to the media store. */ private void saveScreenshotInWorkerThread(Consumer<Uri> finisher, @Nullable ScreenshotController.ActionsReadyListener actionsReadyListener, @Nullable ScreenshotController.QuickShareActionReadyListener private void saveScreenshotInWorkerThread( UserHandle owner, @NonNull Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener, @Nullable QuickShareActionReadyListener quickShareActionsReadyListener) { ScreenshotController.SaveImageInBackgroundData data = new ScreenshotController.SaveImageInBackgroundData(); Loading @@ -935,13 +948,14 @@ public class ScreenshotController { data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; data.mQuickShareActionsReadyListener = quickShareActionsReadyListener; data.owner = owner; if (mSaveInBgTask != null) { // just log success/failure for the pre-existing screenshot mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mImageExporter, mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter, mScreenshotSmartActions, data, getActionTransitionSupplier(), mScreenshotNotificationSmartActionsProvider); mSaveInBgTask.execute(); Loading @@ -960,6 +974,15 @@ public class ScreenshotController { mScreenshotHandler.resetTimeout(); if (imageData.uri != null) { if (!imageData.owner.equals(Process.myUserHandle())) { // TODO: Handle non-primary user ownership (e.g. Work Profile) // This image is owned by another user. Special treatment will be // required in the UI (badging) as well as sending intents which can // correctly forward those URIs on to be read (actions). Log.d(TAG, "*** Screenshot saved to a non-primary user (" + imageData.owner + ") as " + imageData.uri); } mScreenshotHandler.post(() -> { if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { Loading Loading @@ -1033,9 +1056,9 @@ public class ScreenshotController { } } private boolean isUserSetupComplete() { return Settings.Secure.getInt(mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; private boolean isUserSetupComplete(UserHandle owner) { return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0) .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; } /** Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt +3 −1 Original line number Diff line number Diff line Loading @@ -68,7 +68,9 @@ internal open class ScreenshotPolicyImpl @Inject constructor( } override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean { return withContext(bgDispatcher) { userMgr.isManagedProfile(userId) } val managed = withContext(bgDispatcher) { userMgr.isManagedProfile(userId) } Log.d(TAG, "isManagedProfile: $managed") return managed } private fun nonPipVisibleTask(info: RootTaskInfo): Boolean { Loading