Merge "Work profile screenshots: save to owner" into tm-qpr-dev
This commit is contained in:
@@ -19,6 +19,7 @@ package com.android.systemui.screenshot;
|
|||||||
import static android.os.FileUtils.closeQuietly;
|
import static android.os.FileUtils.closeQuietly;
|
||||||
|
|
||||||
import android.annotation.IntRange;
|
import android.annotation.IntRange;
|
||||||
|
import android.content.ContentProvider;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@@ -29,6 +30,7 @@ import android.os.Environment;
|
|||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.os.Trace;
|
import android.os.Trace;
|
||||||
|
import android.os.UserHandle;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
@@ -142,8 +144,9 @@ class ImageExporter {
|
|||||||
*
|
*
|
||||||
* @return a listenable future result
|
* @return a listenable future result
|
||||||
*/
|
*/
|
||||||
ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap) {
|
ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
|
||||||
return export(executor, requestId, bitmap, ZonedDateTime.now());
|
UserHandle owner) {
|
||||||
|
return export(executor, requestId, bitmap, ZonedDateTime.now(), owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,10 +158,10 @@ class ImageExporter {
|
|||||||
* @return a listenable future result
|
* @return a listenable future result
|
||||||
*/
|
*/
|
||||||
ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
|
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,
|
final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
|
||||||
mQuality, /* publish */ true);
|
mQuality, /* publish */ true, owner);
|
||||||
|
|
||||||
return CallbackToFutureAdapter.getFuture(
|
return CallbackToFutureAdapter.getFuture(
|
||||||
(completer) -> {
|
(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 {
|
static class Result {
|
||||||
Uri uri;
|
Uri uri;
|
||||||
UUID requestId;
|
UUID requestId;
|
||||||
@@ -203,7 +184,6 @@ class ImageExporter {
|
|||||||
long timestamp;
|
long timestamp;
|
||||||
CompressFormat format;
|
CompressFormat format;
|
||||||
boolean published;
|
boolean published;
|
||||||
boolean deleted;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
@@ -214,7 +194,6 @@ class ImageExporter {
|
|||||||
sb.append(", timestamp=").append(timestamp);
|
sb.append(", timestamp=").append(timestamp);
|
||||||
sb.append(", format=").append(format);
|
sb.append(", format=").append(format);
|
||||||
sb.append(", published=").append(published);
|
sb.append(", published=").append(published);
|
||||||
sb.append(", deleted=").append(deleted);
|
|
||||||
sb.append('}');
|
sb.append('}');
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@@ -227,17 +206,19 @@ class ImageExporter {
|
|||||||
private final ZonedDateTime mCaptureTime;
|
private final ZonedDateTime mCaptureTime;
|
||||||
private final CompressFormat mFormat;
|
private final CompressFormat mFormat;
|
||||||
private final int mQuality;
|
private final int mQuality;
|
||||||
|
private final UserHandle mOwner;
|
||||||
private final String mFileName;
|
private final String mFileName;
|
||||||
private final boolean mPublish;
|
private final boolean mPublish;
|
||||||
|
|
||||||
Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime,
|
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;
|
mResolver = resolver;
|
||||||
mRequestId = requestId;
|
mRequestId = requestId;
|
||||||
mBitmap = bitmap;
|
mBitmap = bitmap;
|
||||||
mCaptureTime = captureTime;
|
mCaptureTime = captureTime;
|
||||||
mFormat = format;
|
mFormat = format;
|
||||||
mQuality = quality;
|
mQuality = quality;
|
||||||
|
mOwner = owner;
|
||||||
mFileName = createFilename(mCaptureTime, mFormat);
|
mFileName = createFilename(mCaptureTime, mFormat);
|
||||||
mPublish = publish;
|
mPublish = publish;
|
||||||
}
|
}
|
||||||
@@ -253,7 +234,7 @@ class ImageExporter {
|
|||||||
start = Instant.now();
|
start = Instant.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName);
|
uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner);
|
||||||
throwIfInterrupted();
|
throwIfInterrupted();
|
||||||
|
|
||||||
writeImage(mResolver, mBitmap, mFormat, mQuality, uri);
|
writeImage(mResolver, mBitmap, mFormat, mQuality, uri);
|
||||||
@@ -297,15 +278,20 @@ class ImageExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Uri createEntry(ContentResolver resolver, CompressFormat format,
|
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");
|
Trace.beginSection("ImageExporter_createEntry");
|
||||||
try {
|
try {
|
||||||
final ContentValues values = createMetadata(time, format, fileName);
|
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) {
|
if (uri == null) {
|
||||||
throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
|
throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
|
||||||
}
|
}
|
||||||
|
Log.d(TAG, "Inserted new URI: " + uri);
|
||||||
return uri;
|
return uri;
|
||||||
} finally {
|
} finally {
|
||||||
Trace.endSection();
|
Trace.endSection();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import android.graphics.drawable.BitmapDrawable;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Process;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -387,7 +388,9 @@ public class LongScreenshotActivity extends Activity {
|
|||||||
|
|
||||||
mOutputBitmap = renderBitmap(drawable, bounds);
|
mOutputBitmap = renderBitmap(drawable, bounds);
|
||||||
ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
|
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);
|
exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ import android.util.Log;
|
|||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||||
import com.android.systemui.R;
|
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.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
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 static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
private FeatureFlags mFlags;
|
||||||
private final ScreenshotSmartActions mScreenshotSmartActions;
|
private final ScreenshotSmartActions mScreenshotSmartActions;
|
||||||
private final ScreenshotController.SaveImageInBackgroundData mParams;
|
private final ScreenshotController.SaveImageInBackgroundData mParams;
|
||||||
private final ScreenshotController.SavedImageData mImageData;
|
private final ScreenshotController.SavedImageData mImageData;
|
||||||
@@ -84,7 +87,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
private final ImageExporter mImageExporter;
|
private final ImageExporter mImageExporter;
|
||||||
private long mImageTime;
|
private long mImageTime;
|
||||||
|
|
||||||
SaveImageInBackgroundTask(Context context, ImageExporter exporter,
|
SaveImageInBackgroundTask(
|
||||||
|
Context context,
|
||||||
|
FeatureFlags flags,
|
||||||
|
ImageExporter exporter,
|
||||||
ScreenshotSmartActions screenshotSmartActions,
|
ScreenshotSmartActions screenshotSmartActions,
|
||||||
ScreenshotController.SaveImageInBackgroundData data,
|
ScreenshotController.SaveImageInBackgroundData data,
|
||||||
Supplier<ActionTransition> sharedElementTransition,
|
Supplier<ActionTransition> sharedElementTransition,
|
||||||
@@ -92,6 +98,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
screenshotNotificationSmartActionsProvider
|
screenshotNotificationSmartActionsProvider
|
||||||
) {
|
) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
mFlags = flags;
|
||||||
mScreenshotSmartActions = screenshotSmartActions;
|
mScreenshotSmartActions = screenshotSmartActions;
|
||||||
mImageData = new ScreenshotController.SavedImageData();
|
mImageData = new ScreenshotController.SavedImageData();
|
||||||
mQuickShareData = new ScreenshotController.QuickShareData();
|
mQuickShareData = new ScreenshotController.QuickShareData();
|
||||||
@@ -117,7 +124,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
}
|
}
|
||||||
// TODO: move to constructor / from ScreenshotRequest
|
// TODO: move to constructor / from ScreenshotRequest
|
||||||
final UUID requestId = UUID.randomUUID();
|
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);
|
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.
|
// Call synchronously here since already on a background thread.
|
||||||
ListenableFuture<ImageExporter.Result> future =
|
ListenableFuture<ImageExporter.Result> future =
|
||||||
mImageExporter.export(Runnable::run, requestId, image);
|
mImageExporter.export(Runnable::run, requestId, image, mParams.owner);
|
||||||
ImageExporter.Result result = future.get();
|
ImageExporter.Result result = future.get();
|
||||||
|
Log.d(TAG, "Saved screenshot: " + result);
|
||||||
final Uri uri = result.uri;
|
final Uri uri = result.uri;
|
||||||
mImageTime = result.timestamp;
|
mImageTime = result.timestamp;
|
||||||
|
|
||||||
@@ -157,6 +166,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mImageData.uri = uri;
|
mImageData.uri = uri;
|
||||||
|
mImageData.owner = user;
|
||||||
mImageData.smartActions = smartActions;
|
mImageData.smartActions = smartActions;
|
||||||
mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
|
mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
|
||||||
mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
|
mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import static java.util.Objects.requireNonNull;
|
|||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.annotation.MainThread;
|
import android.annotation.MainThread;
|
||||||
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.ActivityOptions;
|
import android.app.ActivityOptions;
|
||||||
@@ -57,7 +58,9 @@ import android.media.AudioSystem;
|
|||||||
import android.media.MediaPlayer;
|
import android.media.MediaPlayer;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Process;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
import android.os.UserHandle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -90,6 +93,7 @@ import com.android.systemui.R;
|
|||||||
import com.android.systemui.broadcast.BroadcastSender;
|
import com.android.systemui.broadcast.BroadcastSender;
|
||||||
import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
|
import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
|
||||||
import com.android.systemui.dagger.qualifiers.Main;
|
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.ScreenshotController.SavedImageData.ActionTransition;
|
||||||
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
|
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
|
||||||
import com.android.systemui.util.Assert;
|
import com.android.systemui.util.Assert;
|
||||||
@@ -151,6 +155,7 @@ public class ScreenshotController {
|
|||||||
public Consumer<Uri> finisher;
|
public Consumer<Uri> finisher;
|
||||||
public ScreenshotController.ActionsReadyListener mActionsReadyListener;
|
public ScreenshotController.ActionsReadyListener mActionsReadyListener;
|
||||||
public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener;
|
public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener;
|
||||||
|
public UserHandle owner;
|
||||||
|
|
||||||
void clearImage() {
|
void clearImage() {
|
||||||
image = null;
|
image = null;
|
||||||
@@ -167,6 +172,8 @@ public class ScreenshotController {
|
|||||||
public Notification.Action deleteAction;
|
public Notification.Action deleteAction;
|
||||||
public List<Notification.Action> smartActions;
|
public List<Notification.Action> smartActions;
|
||||||
public Notification.Action quickShareAction;
|
public Notification.Action quickShareAction;
|
||||||
|
public UserHandle owner;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POD for shared element transition.
|
* POD for shared element transition.
|
||||||
@@ -242,6 +249,7 @@ public class ScreenshotController {
|
|||||||
private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
|
private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
|
||||||
|
|
||||||
private final WindowContext mContext;
|
private final WindowContext mContext;
|
||||||
|
private final FeatureFlags mFlags;
|
||||||
private final ScreenshotNotificationsController mNotificationsController;
|
private final ScreenshotNotificationsController mNotificationsController;
|
||||||
private final ScreenshotSmartActions mScreenshotSmartActions;
|
private final ScreenshotSmartActions mScreenshotSmartActions;
|
||||||
private final UiEventLogger mUiEventLogger;
|
private final UiEventLogger mUiEventLogger;
|
||||||
@@ -288,6 +296,7 @@ public class ScreenshotController {
|
|||||||
@Inject
|
@Inject
|
||||||
ScreenshotController(
|
ScreenshotController(
|
||||||
Context context,
|
Context context,
|
||||||
|
FeatureFlags flags,
|
||||||
ScreenshotSmartActions screenshotSmartActions,
|
ScreenshotSmartActions screenshotSmartActions,
|
||||||
ScreenshotNotificationsController screenshotNotificationsController,
|
ScreenshotNotificationsController screenshotNotificationsController,
|
||||||
ScrollCaptureClient scrollCaptureClient,
|
ScrollCaptureClient scrollCaptureClient,
|
||||||
@@ -331,6 +340,7 @@ public class ScreenshotController {
|
|||||||
final Context displayContext = context.createDisplayContext(getDefaultDisplay());
|
final Context displayContext = context.createDisplayContext(getDefaultDisplay());
|
||||||
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
|
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
|
||||||
mWindowManager = mContext.getSystemService(WindowManager.class);
|
mWindowManager = mContext.getSystemService(WindowManager.class);
|
||||||
|
mFlags = flags;
|
||||||
|
|
||||||
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
|
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
|
||||||
|
|
||||||
@@ -377,7 +387,6 @@ public class ScreenshotController {
|
|||||||
void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
|
void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
|
||||||
Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
|
Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
|
||||||
Consumer<Uri> finisher, RequestCallback requestCallback) {
|
Consumer<Uri> finisher, RequestCallback requestCallback) {
|
||||||
// TODO: use task Id, userId, topComponent for smart handler
|
|
||||||
Assert.isMainThread();
|
Assert.isMainThread();
|
||||||
if (screenshot == null) {
|
if (screenshot == null) {
|
||||||
Log.e(TAG, "Got null bitmap from screenshot message");
|
Log.e(TAG, "Got null bitmap from screenshot message");
|
||||||
@@ -395,7 +404,7 @@ public class ScreenshotController {
|
|||||||
}
|
}
|
||||||
mCurrentRequestCallback = requestCallback;
|
mCurrentRequestCallback = requestCallback;
|
||||||
saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
|
saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
|
||||||
showFlash);
|
showFlash, UserHandle.of(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -543,14 +552,15 @@ public class ScreenshotController {
|
|||||||
return;
|
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),
|
mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
|
||||||
ClipboardOverlayController.SELF_PERMISSION);
|
ClipboardOverlayController.SELF_PERMISSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
|
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(() ->
|
withWindowAttached(() ->
|
||||||
mScreenshotView.announceForAccessibility(
|
mScreenshotView.announceForAccessibility(
|
||||||
mContext.getResources().getString(R.string.screenshot_saving_title)));
|
mContext.getResources().getString(R.string.screenshot_saving_title)));
|
||||||
@@ -575,11 +585,11 @@ public class ScreenshotController {
|
|||||||
|
|
||||||
mScreenBitmap = screenshot;
|
mScreenBitmap = screenshot;
|
||||||
|
|
||||||
if (!isUserSetupComplete()) {
|
if (!isUserSetupComplete(owner)) {
|
||||||
Log.w(TAG, "User setup not complete, displaying toast only");
|
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
|
// 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.
|
// and sharing shouldn't be exposed to the user.
|
||||||
saveScreenshotAndToast(finisher);
|
saveScreenshotAndToast(owner, finisher);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,7 +597,7 @@ public class ScreenshotController {
|
|||||||
mScreenBitmap.setHasAlpha(false);
|
mScreenBitmap.setHasAlpha(false);
|
||||||
mScreenBitmap.prepareToDraw();
|
mScreenBitmap.prepareToDraw();
|
||||||
|
|
||||||
saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady,
|
saveScreenshotInWorkerThread(owner, finisher, this::showUiOnActionsReady,
|
||||||
this::showUiOnQuickShareActionReady);
|
this::showUiOnQuickShareActionReady);
|
||||||
|
|
||||||
// The window is focusable by default
|
// 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
|
* Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
|
||||||
* failure).
|
* 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
|
// Play the shutter sound to notify that we've taken a screenshot
|
||||||
playCameraSound();
|
playCameraSound();
|
||||||
|
|
||||||
saveScreenshotInWorkerThread(
|
saveScreenshotInWorkerThread(
|
||||||
|
owner,
|
||||||
/* onComplete */ finisher,
|
/* onComplete */ finisher,
|
||||||
/* actionsReadyListener */ imageData -> {
|
/* actionsReadyListener */ imageData -> {
|
||||||
if (DEBUG_CALLBACK) {
|
if (DEBUG_CALLBACK) {
|
||||||
@@ -925,9 +936,11 @@ public class ScreenshotController {
|
|||||||
/**
|
/**
|
||||||
* Creates a new worker thread and saves the screenshot to the media store.
|
* Creates a new worker thread and saves the screenshot to the media store.
|
||||||
*/
|
*/
|
||||||
private void saveScreenshotInWorkerThread(Consumer<Uri> finisher,
|
private void saveScreenshotInWorkerThread(
|
||||||
@Nullable ScreenshotController.ActionsReadyListener actionsReadyListener,
|
UserHandle owner,
|
||||||
@Nullable ScreenshotController.QuickShareActionReadyListener
|
@NonNull Consumer<Uri> finisher,
|
||||||
|
@Nullable ActionsReadyListener actionsReadyListener,
|
||||||
|
@Nullable QuickShareActionReadyListener
|
||||||
quickShareActionsReadyListener) {
|
quickShareActionsReadyListener) {
|
||||||
ScreenshotController.SaveImageInBackgroundData
|
ScreenshotController.SaveImageInBackgroundData
|
||||||
data = new ScreenshotController.SaveImageInBackgroundData();
|
data = new ScreenshotController.SaveImageInBackgroundData();
|
||||||
@@ -935,13 +948,14 @@ public class ScreenshotController {
|
|||||||
data.finisher = finisher;
|
data.finisher = finisher;
|
||||||
data.mActionsReadyListener = actionsReadyListener;
|
data.mActionsReadyListener = actionsReadyListener;
|
||||||
data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;
|
data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;
|
||||||
|
data.owner = owner;
|
||||||
|
|
||||||
if (mSaveInBgTask != null) {
|
if (mSaveInBgTask != null) {
|
||||||
// just log success/failure for the pre-existing screenshot
|
// just log success/failure for the pre-existing screenshot
|
||||||
mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
|
mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
|
||||||
}
|
}
|
||||||
|
|
||||||
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mImageExporter,
|
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter,
|
||||||
mScreenshotSmartActions, data, getActionTransitionSupplier(),
|
mScreenshotSmartActions, data, getActionTransitionSupplier(),
|
||||||
mScreenshotNotificationSmartActionsProvider);
|
mScreenshotNotificationSmartActionsProvider);
|
||||||
mSaveInBgTask.execute();
|
mSaveInBgTask.execute();
|
||||||
@@ -960,6 +974,15 @@ public class ScreenshotController {
|
|||||||
mScreenshotHandler.resetTimeout();
|
mScreenshotHandler.resetTimeout();
|
||||||
|
|
||||||
if (imageData.uri != null) {
|
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(() -> {
|
mScreenshotHandler.post(() -> {
|
||||||
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
|
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
|
||||||
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
|
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
|
||||||
@@ -1033,9 +1056,9 @@ public class ScreenshotController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUserSetupComplete() {
|
private boolean isUserSetupComplete(UserHandle owner) {
|
||||||
return Settings.Secure.getInt(mContext.getContentResolver(),
|
return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
|
||||||
SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
|
.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ internal open class ScreenshotPolicyImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
|
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 {
|
private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ public class ScrollCaptureController {
|
|||||||
static class LongScreenshot {
|
static class LongScreenshot {
|
||||||
private final ImageTileSet mImageTileSet;
|
private final ImageTileSet mImageTileSet;
|
||||||
private final Session mSession;
|
private final Session mSession;
|
||||||
|
// TODO: Add UserHandle so LongScreenshots can adhere to work profile screenshot policy
|
||||||
|
|
||||||
LongScreenshot(Session session, ImageTileSet imageTileSet) {
|
LongScreenshot(Session session, ImageTileSet imageTileSet) {
|
||||||
mSession = session;
|
mSession = session;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import android.graphics.Color;
|
|||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.os.Process;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.testing.AndroidTestingRunner;
|
import android.testing.AndroidTestingRunner;
|
||||||
|
|
||||||
@@ -97,7 +98,8 @@ public class ImageExporterTest extends SysuiTestCase {
|
|||||||
Bitmap original = createCheckerBitmap(10, 10, 10);
|
Bitmap original = createCheckerBitmap(10, 10, 10);
|
||||||
|
|
||||||
ListenableFuture<ImageExporter.Result> direct =
|
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());
|
assertTrue("future should be done", direct.isDone());
|
||||||
assertFalse("future should not be canceled", direct.isCancelled());
|
assertFalse("future should not be canceled", direct.isCancelled());
|
||||||
ImageExporter.Result result = direct.get();
|
ImageExporter.Result result = direct.get();
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
data.finisher = null;
|
data.finisher = null;
|
||||||
data.mActionsReadyListener = null;
|
data.mActionsReadyListener = null;
|
||||||
SaveImageInBackgroundTask task =
|
SaveImageInBackgroundTask task =
|
||||||
new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
|
new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
|
||||||
ActionTransition::new, mSmartActionsProvider);
|
ActionTransition::new, mSmartActionsProvider);
|
||||||
|
|
||||||
Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
|
Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
|
||||||
@@ -208,7 +208,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
data.finisher = null;
|
data.finisher = null;
|
||||||
data.mActionsReadyListener = null;
|
data.mActionsReadyListener = null;
|
||||||
SaveImageInBackgroundTask task =
|
SaveImageInBackgroundTask task =
|
||||||
new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
|
new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
|
||||||
ActionTransition::new, mSmartActionsProvider);
|
ActionTransition::new, mSmartActionsProvider);
|
||||||
|
|
||||||
Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
|
Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
|
||||||
@@ -236,7 +236,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
|||||||
data.finisher = null;
|
data.finisher = null;
|
||||||
data.mActionsReadyListener = null;
|
data.mActionsReadyListener = null;
|
||||||
SaveImageInBackgroundTask task =
|
SaveImageInBackgroundTask task =
|
||||||
new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
|
new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
|
||||||
ActionTransition::new, mSmartActionsProvider);
|
ActionTransition::new, mSmartActionsProvider);
|
||||||
|
|
||||||
Notification.Action deleteAction = task.createDeleteAction(mContext,
|
Notification.Action deleteAction = task.createDeleteAction(mContext,
|
||||||
|
|||||||
Reference in New Issue
Block a user