Loading packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +11 −5 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.screenshot import android.app.ActivityOptions import android.app.ExitTransitionCoordinator import android.content.Context import android.content.Intent import android.os.Bundle Loading @@ -23,6 +25,7 @@ import android.os.Process.myUserHandle import android.os.RemoteException import android.os.UserHandle import android.util.Log import android.util.Pair import android.view.IRemoteAnimationFinishedCallback import android.view.IRemoteAnimationRunner import android.view.RemoteAnimationAdapter Loading Loading @@ -64,18 +67,18 @@ constructor( */ fun launchIntentAsync( intent: Intent, options: Bundle?, transition: Pair<ActivityOptions, ExitTransitionCoordinator>?, user: UserHandle, overrideTransition: Boolean, ) { applicationScope.launch("$TAG#launchIntentAsync") { launchIntent(intent, options, user, overrideTransition) launchIntent(intent, transition, user, overrideTransition) } } suspend fun launchIntent( intent: Intent, options: Bundle?, transition: Pair<ActivityOptions, ExitTransitionCoordinator>?, user: UserHandle, overrideTransition: Boolean, ) { Loading @@ -87,11 +90,14 @@ constructor( } else { dismissKeyguard() } transition?.second?.startExit() if (user == myUserHandle()) { withContext(mainDispatcher) { context.startActivity(intent, options) } withContext(mainDispatcher) { context.startActivity(intent, transition?.first?.toBundle()) } } else { launchCrossProfileIntent(user, intent, options) launchCrossProfileIntent(user, intent, transition?.first?.toBundle()) } if (overrideTransition) { Loading packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +0 −179 Original line number Diff line number Diff line Loading @@ -16,40 +16,29 @@ package com.android.systemui.screenshot; import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE; import static com.android.systemui.screenshot.LogConfig.logTag; import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType; import android.app.ActivityTaskManager; import android.app.Notification; import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipDescription; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.res.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.google.common.util.concurrent.ListenableFuture; Loading @@ -60,7 +49,6 @@ import java.util.List; import java.util.Random; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; /** * An AsyncTask that saves an image to the media store in the background. Loading @@ -81,7 +69,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; private String mScreenshotId; private final Random mRandom = new Random(); private final Supplier<ActionTransition> mSharedElementTransition; private final ImageExporter mImageExporter; private long mImageTime; Loading @@ -91,7 +78,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { ImageExporter exporter, ScreenshotSmartActions screenshotSmartActions, ScreenshotController.SaveImageInBackgroundData data, Supplier<ActionTransition> sharedElementTransition, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider ) { Loading @@ -100,7 +86,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mScreenshotSmartActions = screenshotSmartActions; mImageData = new ScreenshotController.SavedImageData(); mQuickShareData = new ScreenshotController.QuickShareData(); mSharedElementTransition = sharedElementTransition; mImageExporter = exporter; // Prepare all the output metadata Loading Loading @@ -176,12 +161,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageData.uri = uri; mImageData.owner = mParams.owner; mImageData.smartActions = smartActions; mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri, smartActionsEnabled); mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri, smartActionsEnabled); mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri, smartActionsEnabled); mImageData.quickShareAction = createQuickShareAction( mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image, mParams.owner); Loading Loading @@ -234,164 +213,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mParams.clearImage(); } /** * Assumes that the action intent is sent immediately after being supplied. */ @VisibleForTesting Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri, boolean smartActionsEnabled) { return () -> { ActionTransition transition = mSharedElementTransition.get(); // Note: Both the share and edit actions are proxied through ActionProxyReceiver in // order to do some common work like dismissing the keyguard and sending // closeSystemWindows // Create a share intent, this will always go through the chooser activity first // which should not trigger auto-enter PiP Intent sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setDataAndType(uri, "image/png"); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); // Include URI in ClipData also, so that grantPermission picks it up. // We don't use setData here because some apps interpret this as "to:". ClipData clipdata = new ClipData(new ClipDescription("content", new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}), new ClipData.Item(uri)); sharingIntent.setClipData(clipdata); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(mImageTime)); sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // Make sure pending intents for the system user are still unique across users // by setting the (otherwise unused) request code to the current user id. int requestCode = context.getUserId(); Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // cancel current pending intent (if any) since clipData isn't used for matching PendingIntent pendingIntent = PendingIntent.getActivityAsUser( context, 0, sharingChooserIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, transition.bundle, UserHandle.CURRENT); // Create a share action for the notification PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode, new Intent(context, ActionProxyReceiver.class) .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent) .putExtra(ScreenshotController.EXTRA_DISALLOW_ENTER_PIP, true) .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId) .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled) .setAction(Intent.ACTION_SEND) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, UserHandle.SYSTEM); Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_share), r.getString(com.android.internal.R.string.share), shareAction); transition.action = shareActionBuilder.build(); return transition; }; } @VisibleForTesting Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri, boolean smartActionsEnabled) { return () -> { ActionTransition transition = mSharedElementTransition.get(); // Note: Both the share and edit actions are proxied through ActionProxyReceiver in // order to do some common work like dismissing the keyguard and sending // closeSystemWindows // Create an edit intent, if a specific package is provided as the editor, then // launch that directly String editorPackage = context.getString(R.string.config_screenshotEditor); Intent editIntent = new Intent(Intent.ACTION_EDIT); if (!TextUtils.isEmpty(editorPackage)) { editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); } editIntent.setDataAndType(uri, "image/png"); editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivityAsUser( context, 0, editIntent, PendingIntent.FLAG_IMMUTABLE, transition.bundle, UserHandle.CURRENT); // Make sure pending intents for the system user are still unique across users // by setting the (otherwise unused) request code to the current user id. int requestCode = mContext.getUserId(); // Create an edit action PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, new Intent(context, ActionProxyReceiver.class) .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent) .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId) .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled) .putExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, true) .setAction(Intent.ACTION_EDIT) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, UserHandle.SYSTEM); Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_edit), r.getString(com.android.internal.R.string.screenshot_edit), editAction); transition.action = editActionBuilder.build(); return transition; }; } @VisibleForTesting Notification.Action createDeleteAction(Context context, Resources r, Uri uri, boolean smartActionsEnabled) { // Make sure pending intents for the system user are still unique across users // by setting the (otherwise unused) request code to the current user id. int requestCode = mContext.getUserId(); // Create a delete action for the notification PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, new Intent(context, DeleteScreenshotReceiver.class) .putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString()) .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId) .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_delete), r.getString(com.android.internal.R.string.delete), deleteAction); return deleteActionBuilder.build(); } private UserHandle getUserHandleOfForegroundApplication(Context context) { UserManager manager = UserManager.get(context); int result; // This logic matches // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile try { result = ActivityTaskManager.getService().getLastResumedActivityUserId(); } catch (RemoteException e) { if (DEBUG_ACTIONS) { Log.d(TAG, "Failed to get UserHandle of foreground app: ", e); } result = context.getUserId(); } UserInfo userInfo = manager.getUserInfo(result); return userInfo.getUserHandle(); } private List<Notification.Action> buildSmartActions( List<Notification.Action> actions, Context context) { List<Notification.Action> broadcastActions = new ArrayList<>(); Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +42 −75 Original line number Diff line number Diff line Loading @@ -39,7 +39,6 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ExitTransitionCoordinator; import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; import android.app.ICompatCameraControlCallback; import android.app.Notification; import android.app.assist.AssistContent; Loading @@ -54,7 +53,6 @@ import android.graphics.Insets; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; Loading Loading @@ -87,13 +85,12 @@ import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.res.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.flags.Flags; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.android.systemui.res.R; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; import com.android.systemui.util.Assert; Loading @@ -111,7 +108,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Consumer; import java.util.function.Supplier; import javax.inject.Provider; Loading Loading @@ -171,31 +167,16 @@ public class ScreenshotController { */ static class SavedImageData { public Uri uri; public Supplier<ActionTransition> shareTransition; public Supplier<ActionTransition> editTransition; public Notification.Action deleteAction; public List<Notification.Action> smartActions; public Notification.Action quickShareAction; public UserHandle owner; public String subject; // Title for sharing /** * POD for shared element transition. */ static class ActionTransition { public Bundle bundle; public Notification.Action action; public Runnable onCancelRunnable; } /** * Used to reset the return data on error */ public void reset() { uri = null; shareTransition = null; editTransition = null; deleteAction = null; smartActions = null; quickShareAction = null; subject = null; Loading Loading @@ -470,7 +451,8 @@ public class ScreenshotController { if (!shouldShowUi()) { saveScreenshotInWorkerThread( screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady, (ignored) -> {}); (ignored) -> { }); return; } Loading Loading @@ -642,6 +624,12 @@ public class ScreenshotController { mScreenshotHandler.resetTimeout(); } @Override public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) { mActionExecutor.launchIntentAsync( intent, createWindowTransition(), owner, overrideTransition); } @Override public void onDismiss() { finishDismiss(); Loading @@ -652,7 +640,7 @@ public class ScreenshotController { // TODO(159460485): Remove this when focus is handled properly in the system setWindowFocusable(false); } }, mActionExecutor, mFlags); }, mFlags); mScreenshotView.setDefaultDisplay(mDisplayId); mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); Loading Loading @@ -964,6 +952,35 @@ public class ScreenshotController { mScreenshotAnimation.start(); } /** * Supplies the necessary bits for the shared element transition to share sheet. * Note that once called, the action intent to share must be sent immediately after. */ private Pair<ActivityOptions, ExitTransitionCoordinator> createWindowTransition() { ExitTransitionCoordinator.ExitTransitionCallbacks callbacks = new ExitTransitionCoordinator.ExitTransitionCallbacks() { @Override public boolean isReturnTransitionAllowed() { return false; } @Override public void hideSharedElements() { finishDismiss(); } @Override public void onFinish() { } }; Pair<ActivityOptions, ExitTransitionCoordinator> transition = ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null, Pair.create(mScreenshotView.getScreenshotPreview(), ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); return transition; } /** Reset screenshot view and then call onCompleteRunnable */ private void finishDismiss() { Log.d(TAG, "finishDismiss"); Loading Loading @@ -1011,7 +1028,7 @@ public class ScreenshotController { } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter, mScreenshotSmartActions, data, getActionTransitionSupplier(), mScreenshotSmartActions, data, mScreenshotNotificationSmartActionsProvider); mSaveInBgTask.execute(); } Loading Loading @@ -1077,26 +1094,6 @@ public class ScreenshotController { } } /** * Supplies the necessary bits for the shared element transition to share sheet. * Note that once supplied, the action intent to share must be sent immediately after. */ private Supplier<ActionTransition> getActionTransitionSupplier() { return () -> { Pair<ActivityOptions, ExitTransitionCoordinator> transition = ActivityOptions.startSharedElementAnimation( mWindow, new ScreenshotExitTransitionCallbacksSupplier(true).get(), null, Pair.create(mScreenshotView.getScreenshotPreview(), ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); transition.second.startExit(); ActionTransition supply = new ActionTransition(); supply.bundle = transition.first.toBundle(); supply.onCancelRunnable = () -> ActivityOptions.stopSharedElementAnimation(mWindow); return supply; }; } /** * Logs success/failure of the screenshot saving task, and shows an error if it failed. */ Loading Loading @@ -1186,36 +1183,6 @@ public class ScreenshotController { return matchWithinTolerance; } private class ScreenshotExitTransitionCallbacksSupplier implements Supplier<ExitTransitionCallbacks> { final boolean mDismissOnHideSharedElements; ScreenshotExitTransitionCallbacksSupplier(boolean dismissOnHideSharedElements) { mDismissOnHideSharedElements = dismissOnHideSharedElements; } @Override public ExitTransitionCallbacks get() { return new ExitTransitionCallbacks() { @Override public boolean isReturnTransitionAllowed() { return false; } @Override public void hideSharedElements() { if (mDismissOnHideSharedElements) { finishDismiss(); } } @Override public void onFinish() { } }; } } /** Injectable factory to create screenshot controller instances for a specific display. */ @AssistedFactory public interface Factory { Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +9 −12 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; Loading Loading @@ -86,9 +87,9 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.systemui.res.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; Loading @@ -104,6 +105,8 @@ public class ScreenshotView extends FrameLayout implements interface ScreenshotViewCallback { void onUserInteraction(); void onAction(Intent intent, UserHandle owner, boolean overrideTransition); void onDismiss(); /** DOWN motion event was observed outside of the touchable areas of this view. */ Loading Loading @@ -166,7 +169,6 @@ public class ScreenshotView extends FrameLayout implements private final InteractionJankMonitor mInteractionJankMonitor; private long mDefaultTimeoutOfTimeoutHandler; private ActionIntentExecutor mActionExecutor; private FeatureFlags mFlags; private final Bundle mInteractiveBroadcastOption; Loading Loading @@ -430,11 +432,9 @@ public class ScreenshotView extends FrameLayout implements * Note: must be called before any other (non-constructor) method or null pointer exceptions * may occur. */ void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, ActionIntentExecutor actionExecutor, FeatureFlags flags) { void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, FeatureFlags flags) { mUiEventLogger = uiEventLogger; mCallbacks = callbacks; mActionExecutor = actionExecutor; mFlags = flags; } Loading Loading @@ -800,24 +800,21 @@ public class ScreenshotView extends FrameLayout implements shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject( imageData.uri, imageData.subject); } mActionExecutor.launchIntentAsync(shareIntent, imageData.shareTransition.get().bundle, imageData.owner, false); mCallbacks.onAction(shareIntent, imageData.owner, false); }); mEditChip.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName); prepareSharedTransition(); mActionExecutor.launchIntentAsync( mCallbacks.onAction( ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext), imageData.editTransition.get().bundle, imageData.owner, true); }); mScreenshotPreview.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName); prepareSharedTransition(); mActionExecutor.launchIntentAsync( mCallbacks.onAction( ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext), imageData.editTransition.get().bundle, imageData.owner, true); }); if (mQuickShareChip != null) { Loading packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt +0 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +11 −5 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.screenshot import android.app.ActivityOptions import android.app.ExitTransitionCoordinator import android.content.Context import android.content.Intent import android.os.Bundle Loading @@ -23,6 +25,7 @@ import android.os.Process.myUserHandle import android.os.RemoteException import android.os.UserHandle import android.util.Log import android.util.Pair import android.view.IRemoteAnimationFinishedCallback import android.view.IRemoteAnimationRunner import android.view.RemoteAnimationAdapter Loading Loading @@ -64,18 +67,18 @@ constructor( */ fun launchIntentAsync( intent: Intent, options: Bundle?, transition: Pair<ActivityOptions, ExitTransitionCoordinator>?, user: UserHandle, overrideTransition: Boolean, ) { applicationScope.launch("$TAG#launchIntentAsync") { launchIntent(intent, options, user, overrideTransition) launchIntent(intent, transition, user, overrideTransition) } } suspend fun launchIntent( intent: Intent, options: Bundle?, transition: Pair<ActivityOptions, ExitTransitionCoordinator>?, user: UserHandle, overrideTransition: Boolean, ) { Loading @@ -87,11 +90,14 @@ constructor( } else { dismissKeyguard() } transition?.second?.startExit() if (user == myUserHandle()) { withContext(mainDispatcher) { context.startActivity(intent, options) } withContext(mainDispatcher) { context.startActivity(intent, transition?.first?.toBundle()) } } else { launchCrossProfileIntent(user, intent, options) launchCrossProfileIntent(user, intent, transition?.first?.toBundle()) } if (overrideTransition) { Loading
packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +0 −179 Original line number Diff line number Diff line Loading @@ -16,40 +16,29 @@ package com.android.systemui.screenshot; import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE; import static com.android.systemui.screenshot.LogConfig.logTag; import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType; import android.app.ActivityTaskManager; import android.app.Notification; import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipDescription; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.res.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.google.common.util.concurrent.ListenableFuture; Loading @@ -60,7 +49,6 @@ import java.util.List; import java.util.Random; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; /** * An AsyncTask that saves an image to the media store in the background. Loading @@ -81,7 +69,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; private String mScreenshotId; private final Random mRandom = new Random(); private final Supplier<ActionTransition> mSharedElementTransition; private final ImageExporter mImageExporter; private long mImageTime; Loading @@ -91,7 +78,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { ImageExporter exporter, ScreenshotSmartActions screenshotSmartActions, ScreenshotController.SaveImageInBackgroundData data, Supplier<ActionTransition> sharedElementTransition, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider ) { Loading @@ -100,7 +86,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mScreenshotSmartActions = screenshotSmartActions; mImageData = new ScreenshotController.SavedImageData(); mQuickShareData = new ScreenshotController.QuickShareData(); mSharedElementTransition = sharedElementTransition; mImageExporter = exporter; // Prepare all the output metadata Loading Loading @@ -176,12 +161,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageData.uri = uri; mImageData.owner = mParams.owner; mImageData.smartActions = smartActions; mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri, smartActionsEnabled); mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri, smartActionsEnabled); mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri, smartActionsEnabled); mImageData.quickShareAction = createQuickShareAction( mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image, mParams.owner); Loading Loading @@ -234,164 +213,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mParams.clearImage(); } /** * Assumes that the action intent is sent immediately after being supplied. */ @VisibleForTesting Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri, boolean smartActionsEnabled) { return () -> { ActionTransition transition = mSharedElementTransition.get(); // Note: Both the share and edit actions are proxied through ActionProxyReceiver in // order to do some common work like dismissing the keyguard and sending // closeSystemWindows // Create a share intent, this will always go through the chooser activity first // which should not trigger auto-enter PiP Intent sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setDataAndType(uri, "image/png"); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); // Include URI in ClipData also, so that grantPermission picks it up. // We don't use setData here because some apps interpret this as "to:". ClipData clipdata = new ClipData(new ClipDescription("content", new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}), new ClipData.Item(uri)); sharingIntent.setClipData(clipdata); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(mImageTime)); sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // Make sure pending intents for the system user are still unique across users // by setting the (otherwise unused) request code to the current user id. int requestCode = context.getUserId(); Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // cancel current pending intent (if any) since clipData isn't used for matching PendingIntent pendingIntent = PendingIntent.getActivityAsUser( context, 0, sharingChooserIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, transition.bundle, UserHandle.CURRENT); // Create a share action for the notification PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode, new Intent(context, ActionProxyReceiver.class) .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent) .putExtra(ScreenshotController.EXTRA_DISALLOW_ENTER_PIP, true) .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId) .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled) .setAction(Intent.ACTION_SEND) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, UserHandle.SYSTEM); Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_share), r.getString(com.android.internal.R.string.share), shareAction); transition.action = shareActionBuilder.build(); return transition; }; } @VisibleForTesting Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri, boolean smartActionsEnabled) { return () -> { ActionTransition transition = mSharedElementTransition.get(); // Note: Both the share and edit actions are proxied through ActionProxyReceiver in // order to do some common work like dismissing the keyguard and sending // closeSystemWindows // Create an edit intent, if a specific package is provided as the editor, then // launch that directly String editorPackage = context.getString(R.string.config_screenshotEditor); Intent editIntent = new Intent(Intent.ACTION_EDIT); if (!TextUtils.isEmpty(editorPackage)) { editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); } editIntent.setDataAndType(uri, "image/png"); editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivityAsUser( context, 0, editIntent, PendingIntent.FLAG_IMMUTABLE, transition.bundle, UserHandle.CURRENT); // Make sure pending intents for the system user are still unique across users // by setting the (otherwise unused) request code to the current user id. int requestCode = mContext.getUserId(); // Create an edit action PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, new Intent(context, ActionProxyReceiver.class) .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent) .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId) .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled) .putExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, true) .setAction(Intent.ACTION_EDIT) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, UserHandle.SYSTEM); Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_edit), r.getString(com.android.internal.R.string.screenshot_edit), editAction); transition.action = editActionBuilder.build(); return transition; }; } @VisibleForTesting Notification.Action createDeleteAction(Context context, Resources r, Uri uri, boolean smartActionsEnabled) { // Make sure pending intents for the system user are still unique across users // by setting the (otherwise unused) request code to the current user id. int requestCode = mContext.getUserId(); // Create a delete action for the notification PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, new Intent(context, DeleteScreenshotReceiver.class) .putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString()) .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId) .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_delete), r.getString(com.android.internal.R.string.delete), deleteAction); return deleteActionBuilder.build(); } private UserHandle getUserHandleOfForegroundApplication(Context context) { UserManager manager = UserManager.get(context); int result; // This logic matches // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile try { result = ActivityTaskManager.getService().getLastResumedActivityUserId(); } catch (RemoteException e) { if (DEBUG_ACTIONS) { Log.d(TAG, "Failed to get UserHandle of foreground app: ", e); } result = context.getUserId(); } UserInfo userInfo = manager.getUserInfo(result); return userInfo.getUserHandle(); } private List<Notification.Action> buildSmartActions( List<Notification.Action> actions, Context context) { List<Notification.Action> broadcastActions = new ArrayList<>(); Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +42 −75 Original line number Diff line number Diff line Loading @@ -39,7 +39,6 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ExitTransitionCoordinator; import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; import android.app.ICompatCameraControlCallback; import android.app.Notification; import android.app.assist.AssistContent; Loading @@ -54,7 +53,6 @@ import android.graphics.Insets; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; Loading Loading @@ -87,13 +85,12 @@ import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.res.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.flags.Flags; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.android.systemui.res.R; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; import com.android.systemui.util.Assert; Loading @@ -111,7 +108,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Consumer; import java.util.function.Supplier; import javax.inject.Provider; Loading Loading @@ -171,31 +167,16 @@ public class ScreenshotController { */ static class SavedImageData { public Uri uri; public Supplier<ActionTransition> shareTransition; public Supplier<ActionTransition> editTransition; public Notification.Action deleteAction; public List<Notification.Action> smartActions; public Notification.Action quickShareAction; public UserHandle owner; public String subject; // Title for sharing /** * POD for shared element transition. */ static class ActionTransition { public Bundle bundle; public Notification.Action action; public Runnable onCancelRunnable; } /** * Used to reset the return data on error */ public void reset() { uri = null; shareTransition = null; editTransition = null; deleteAction = null; smartActions = null; quickShareAction = null; subject = null; Loading Loading @@ -470,7 +451,8 @@ public class ScreenshotController { if (!shouldShowUi()) { saveScreenshotInWorkerThread( screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady, (ignored) -> {}); (ignored) -> { }); return; } Loading Loading @@ -642,6 +624,12 @@ public class ScreenshotController { mScreenshotHandler.resetTimeout(); } @Override public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) { mActionExecutor.launchIntentAsync( intent, createWindowTransition(), owner, overrideTransition); } @Override public void onDismiss() { finishDismiss(); Loading @@ -652,7 +640,7 @@ public class ScreenshotController { // TODO(159460485): Remove this when focus is handled properly in the system setWindowFocusable(false); } }, mActionExecutor, mFlags); }, mFlags); mScreenshotView.setDefaultDisplay(mDisplayId); mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); Loading Loading @@ -964,6 +952,35 @@ public class ScreenshotController { mScreenshotAnimation.start(); } /** * Supplies the necessary bits for the shared element transition to share sheet. * Note that once called, the action intent to share must be sent immediately after. */ private Pair<ActivityOptions, ExitTransitionCoordinator> createWindowTransition() { ExitTransitionCoordinator.ExitTransitionCallbacks callbacks = new ExitTransitionCoordinator.ExitTransitionCallbacks() { @Override public boolean isReturnTransitionAllowed() { return false; } @Override public void hideSharedElements() { finishDismiss(); } @Override public void onFinish() { } }; Pair<ActivityOptions, ExitTransitionCoordinator> transition = ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null, Pair.create(mScreenshotView.getScreenshotPreview(), ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); return transition; } /** Reset screenshot view and then call onCompleteRunnable */ private void finishDismiss() { Log.d(TAG, "finishDismiss"); Loading Loading @@ -1011,7 +1028,7 @@ public class ScreenshotController { } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter, mScreenshotSmartActions, data, getActionTransitionSupplier(), mScreenshotSmartActions, data, mScreenshotNotificationSmartActionsProvider); mSaveInBgTask.execute(); } Loading Loading @@ -1077,26 +1094,6 @@ public class ScreenshotController { } } /** * Supplies the necessary bits for the shared element transition to share sheet. * Note that once supplied, the action intent to share must be sent immediately after. */ private Supplier<ActionTransition> getActionTransitionSupplier() { return () -> { Pair<ActivityOptions, ExitTransitionCoordinator> transition = ActivityOptions.startSharedElementAnimation( mWindow, new ScreenshotExitTransitionCallbacksSupplier(true).get(), null, Pair.create(mScreenshotView.getScreenshotPreview(), ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); transition.second.startExit(); ActionTransition supply = new ActionTransition(); supply.bundle = transition.first.toBundle(); supply.onCancelRunnable = () -> ActivityOptions.stopSharedElementAnimation(mWindow); return supply; }; } /** * Logs success/failure of the screenshot saving task, and shows an error if it failed. */ Loading Loading @@ -1186,36 +1183,6 @@ public class ScreenshotController { return matchWithinTolerance; } private class ScreenshotExitTransitionCallbacksSupplier implements Supplier<ExitTransitionCallbacks> { final boolean mDismissOnHideSharedElements; ScreenshotExitTransitionCallbacksSupplier(boolean dismissOnHideSharedElements) { mDismissOnHideSharedElements = dismissOnHideSharedElements; } @Override public ExitTransitionCallbacks get() { return new ExitTransitionCallbacks() { @Override public boolean isReturnTransitionAllowed() { return false; } @Override public void hideSharedElements() { if (mDismissOnHideSharedElements) { finishDismiss(); } } @Override public void onFinish() { } }; } } /** Injectable factory to create screenshot controller instances for a specific display. */ @AssistedFactory public interface Factory { Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +9 −12 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; Loading Loading @@ -86,9 +87,9 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.systemui.res.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; Loading @@ -104,6 +105,8 @@ public class ScreenshotView extends FrameLayout implements interface ScreenshotViewCallback { void onUserInteraction(); void onAction(Intent intent, UserHandle owner, boolean overrideTransition); void onDismiss(); /** DOWN motion event was observed outside of the touchable areas of this view. */ Loading Loading @@ -166,7 +169,6 @@ public class ScreenshotView extends FrameLayout implements private final InteractionJankMonitor mInteractionJankMonitor; private long mDefaultTimeoutOfTimeoutHandler; private ActionIntentExecutor mActionExecutor; private FeatureFlags mFlags; private final Bundle mInteractiveBroadcastOption; Loading Loading @@ -430,11 +432,9 @@ public class ScreenshotView extends FrameLayout implements * Note: must be called before any other (non-constructor) method or null pointer exceptions * may occur. */ void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, ActionIntentExecutor actionExecutor, FeatureFlags flags) { void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, FeatureFlags flags) { mUiEventLogger = uiEventLogger; mCallbacks = callbacks; mActionExecutor = actionExecutor; mFlags = flags; } Loading Loading @@ -800,24 +800,21 @@ public class ScreenshotView extends FrameLayout implements shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject( imageData.uri, imageData.subject); } mActionExecutor.launchIntentAsync(shareIntent, imageData.shareTransition.get().bundle, imageData.owner, false); mCallbacks.onAction(shareIntent, imageData.owner, false); }); mEditChip.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName); prepareSharedTransition(); mActionExecutor.launchIntentAsync( mCallbacks.onAction( ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext), imageData.editTransition.get().bundle, imageData.owner, true); }); mScreenshotPreview.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName); prepareSharedTransition(); mActionExecutor.launchIntentAsync( mCallbacks.onAction( ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext), imageData.editTransition.get().bundle, imageData.owner, true); }); if (mQuickShareChip != null) { Loading
packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt +0 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes