Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 67e693ad authored by Matt Casey's avatar Matt Casey Committed by Automerger Merge Worker
Browse files

Merge "Work profile screenshots: save to owner" into tm-qpr-dev am: 802120c8

parents 38578969 802120c8
Loading
Loading
Loading
Loading
+18 −32
Original line number Diff line number Diff line
@@ -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();
+4 −1
Original line number Diff line number Diff line
@@ -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);
    }

+13 −3
Original line number Diff line number Diff line
@@ -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);
+38 −15
Original line number Diff line number Diff line
@@ -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;
    }

    /**
+3 −1
Original line number Diff line number Diff line
@@ -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