diff options
8 files changed, 83 insertions, 56 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java index 55602a98b8c5..e3658defc52a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java @@ -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; @@ -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; @@ -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); } /** @@ -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) -> { @@ -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; @@ -203,7 +184,6 @@ class ImageExporter { long timestamp; CompressFormat format; boolean published; - boolean deleted; @Override public String toString() { @@ -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(); } @@ -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; } @@ -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); @@ -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(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index ba6e98e79ac0..8bf956b86683 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -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; @@ -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); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index f248d6913878..077ad35fd63f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -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; @@ -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; @@ -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, @@ -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(); @@ -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); @@ -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; @@ -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); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 3fee232b3465..df32d2081fde 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -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; @@ -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; @@ -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; @@ -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; @@ -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. @@ -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; @@ -288,6 +296,7 @@ public class ScreenshotController { @Inject ScreenshotController( Context context, + FeatureFlags flags, ScreenshotSmartActions screenshotSmartActions, ScreenshotNotificationsController screenshotNotificationsController, ScrollCaptureClient scrollCaptureClient, @@ -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); @@ -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"); @@ -395,7 +404,7 @@ public class ScreenshotController { } mCurrentRequestCallback = requestCallback; saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent, - showFlash); + showFlash, UserHandle.of(userId)); } /** @@ -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))); @@ -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; } @@ -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 @@ -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) { @@ -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(); @@ -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(); @@ -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() { @@ -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; } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt index c2a50609b6a5..3a3528606302 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt @@ -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 { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index 83b60fb23b90..30a0b8f2d76f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -78,6 +78,7 @@ public class ScrollCaptureController { static class LongScreenshot { private final ImageTileSet mImageTileSet; private final Session mSession; + // TODO: Add UserHandle so LongScreenshots can adhere to work profile screenshot policy LongScreenshot(Session session, ImageTileSet imageTileSet) { mSession = session; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java index 7d563399ee1c..4c44dacab1a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java @@ -33,6 +33,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.os.Build; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.provider.MediaStore; import android.testing.AndroidTestingRunner; @@ -97,7 +98,8 @@ public class ImageExporterTest extends SysuiTestCase { Bitmap original = createCheckerBitmap(10, 10, 10); ListenableFuture<ImageExporter.Result> direct = - exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME); + exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME, + Process.myUserHandle()); assertTrue("future should be done", direct.isDone()); assertFalse("future should not be canceled", direct.isCancelled()); ImageExporter.Result result = direct.get(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java index 69b7b88b0524..8c9404e336ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -180,7 +180,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = - new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data, + new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data, ActionTransition::new, mSmartActionsProvider); Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), @@ -208,7 +208,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = - new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data, + new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data, ActionTransition::new, mSmartActionsProvider); Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), @@ -236,7 +236,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = - new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data, + new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data, ActionTransition::new, mSmartActionsProvider); Notification.Action deleteAction = task.createDeleteAction(mContext, |