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

Commit 06ca2744 authored by Winson Chung's avatar Winson Chung
Browse files

Allow share/edit screenshot actions from lockscreen

- Dismiss keyguard when routing the actions through the proxy receiver
- Fix issue with edit activity also disallowing auto-enter PiP
- Minor cleanup of some unused/shared code

Bug: 72459081
Test: Launch share/edit screenshot from lockscreen (and normally)
Test: Launch screenshot share action with auto-enter PiP activity and
      ensure it does not trigger PiP

Change-Id: Ibae2de51bc3fe10f439d7506ab7d0d9243142c94
parent 3743d28e
Loading
Loading
Loading
Loading
+3 −5
Original line number Diff line number Diff line
@@ -339,19 +339,17 @@
            android:exported="false">
        </activity>

        <!-- Springboard for launching the share activity -->
        <receiver android:name=".screenshot.GlobalScreenshot$ScreenshotActionReceiver"
            android:process=":screenshot"
        <!-- Springboard for launching the share and edit activity. This needs to be in the main
             system ui process since we need to notify the status bar to dismiss the keyguard -->
        <receiver android:name=".screenshot.GlobalScreenshot$ActionProxyReceiver"
            android:exported="false" />

        <!-- Callback for dismissing screenshot notification after a share target is picked -->
        <receiver android:name=".screenshot.GlobalScreenshot$TargetChosenReceiver"
            android:process=":screenshot"
            android:exported="false" />

        <!-- Callback for deleting screenshot notification -->
        <receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver"
            android:process=":screenshot"
            android:exported="false" />

        <!-- started from UsbDeviceSettingsManager -->
+2 −2
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
            long token = Binder.clearCallingIdentity();
            try {
                mHandler.post(() -> {
                    StatusBar statusBar = ((SystemUIApplication) mContext).getComponent(
                    StatusBar statusBar = SysUiServiceProvider.getComponent(mContext,
                            StatusBar.class);
                    if (statusBar != null) {
                        statusBar.showScreenPinningRequest(taskId, false /* allowCancel */);
@@ -152,7 +152,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
        public Rect getNonMinimizedSplitScreenSecondaryBounds() {
            long token = Binder.clearCallingIdentity();
            try {
                Divider divider = ((SystemUIApplication) mContext).getComponent(Divider.class);
                Divider divider = SysUiServiceProvider.getComponent(mContext, Divider.class);
                if (divider != null) {
                    return divider.getView().getNonMinimizedSplitScreenSecondaryBounds();
                }
+2 −1
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import android.widget.Toast;

import com.android.systemui.Dependency;
import com.android.systemui.OverviewProxyService;
import com.android.systemui.SysUiServiceProvider;
import com.google.android.collect.Lists;

import com.android.internal.logging.MetricsLogger;
@@ -1095,7 +1096,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
    }

    private StatusBar getStatusBar() {
        return ((SystemUIApplication) mContext).getComponent(StatusBar.class);
        return SysUiServiceProvider.getComponent(mContext, StatusBar.class);
    }

    /**
+2 −2
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.SystemUIApplication;

/**
@@ -40,8 +41,7 @@ public class RecentsSystemUserService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        SystemUIApplication app = (SystemUIApplication) getApplication();
        Recents recents = app.getComponent(Recents.class);
        Recents recents = SysUiServiceProvider.getComponent(this, Recents.class);
        if (DEBUG) {
            Log.d(TAG, "onBind: " + recents);
        }
+71 −78
Original line number Diff line number Diff line
@@ -16,9 +16,11 @@

package com.android.systemui.screenshot;

import static android.content.Context.NOTIFICATION_SERVICE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;

import static com.android.systemui.screenshot.GlobalScreenshot.SHARING_INTENT;
import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT;
import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION;
import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP;
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;

import android.animation.Animator;
@@ -26,7 +28,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.Notification;
import android.app.Notification.BigPictureStyle;
@@ -56,9 +57,9 @@ import android.os.AsyncTask;
import android.os.Environment;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
@@ -73,12 +74,13 @@ import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.Toast;

import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.SystemUI;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.NotificationChannels;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
@@ -277,7 +279,12 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
            values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
            Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

            // Create a share intent
            // 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
            String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
            String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
@@ -286,37 +293,47 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
            sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

            // Create a share action for the notification. Note, we proxy the call to
            // ScreenshotActionReceiver because RemoteViews currently forces an activity options
            // on the PendingIntent being launched, and since we don't want to trigger the share
            // sheet in this case, we start the chooser activity directly in
            // ScreenshotActionReceiver.
            PendingIntent chooserAction = PendingIntent.getBroadcast(context, 0,
                    new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
            Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null,
                    chooserAction.getIntentSender())
                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);

            // Create a share action for the notification
            PendingIntent shareAction = PendingIntent.getBroadcast(context, 0,
                    new Intent(context, GlobalScreenshot.ScreenshotActionReceiver.class)
                            .putExtra(SHARING_INTENT, sharingIntent),
                    new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
                            .putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
                            .putExtra(EXTRA_DISALLOW_ENTER_PIP, true),
                    PendingIntent.FLAG_CANCEL_CURRENT);
            Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
                    R.drawable.ic_screenshot_share,
                    r.getString(com.android.internal.R.string.share), shareAction);
            mNotificationBuilder.addAction(shareActionBuilder.build());

            // 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.setType("image/png");
            editIntent.setData(uri);
            editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

            // Create a edit action for the notification the same way.
            // Create a edit action
            PendingIntent editAction = PendingIntent.getBroadcast(context, 1,
                    new Intent(context, GlobalScreenshot.ScreenshotActionReceiver.class)
                            .putExtra(SHARING_INTENT, editIntent),
                    new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
                            .putExtra(EXTRA_ACTION_INTENT, editIntent)
                            .putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null),
                    PendingIntent.FLAG_CANCEL_CURRENT);
            Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
                    R.drawable.ic_screenshot_edit,
                    r.getString(com.android.internal.R.string.screenshot_edit), editAction);
            mNotificationBuilder.addAction(editActionBuilder.build());


            // Create a delete action for the notification
            PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
                    new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
@@ -429,7 +446,9 @@ class DeleteImageInBackgroundTask extends AsyncTask<Uri, Void, Void> {

class GlobalScreenshot {
    static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
    static final String SHARING_INTENT = "android:screenshot_sharing_intent";
    static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
    static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
    static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";

    private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
    private static final int SCREENSHOT_DROP_IN_DURATION = 430;
@@ -452,7 +471,6 @@ class GlobalScreenshot {
    private NotificationManager mNotificationManager;
    private Display mDisplay;
    private DisplayMetrics mDisplayMetrics;
    private Matrix mDisplayMatrix;

    private Bitmap mScreenBitmap;
    private View mScreenshotLayout;
@@ -482,7 +500,6 @@ class GlobalScreenshot {
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        // Inflate the screenshot layout
        mDisplayMatrix = new Matrix();
        mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
        mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background);
        mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
@@ -512,7 +529,7 @@ class GlobalScreenshot {
        mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mNotificationManager =
            (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
        mDisplay = mWindowManager.getDefaultDisplay();
        mDisplayMetrics = new DisplayMetrics();
        mDisplay.getRealMetrics(mDisplayMetrics);
@@ -561,21 +578,6 @@ class GlobalScreenshot {
                .execute();
    }

    /**
     * @return the current display rotation in degrees
     */
    private float getDegreesForRotation(int value) {
        switch (value) {
        case Surface.ROTATION_90:
            return 360f - 90f;
        case Surface.ROTATION_180:
            return 360f - 180f;
        case Surface.ROTATION_270:
            return 360f - 270f;
        }
        return 0f;
    }

    /**
     * Takes a screenshot of the current display and shows an animation.
     */
@@ -891,52 +893,39 @@ class GlobalScreenshot {
    }

    /**
     * Receiver to proxy the share or edit intent.
     * Receiver to proxy the share or edit intent, used to clean up the notification and send
     * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
     */
    public static class ScreenshotActionReceiver extends BroadcastReceiver {
    public static class ActionProxyReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            try {
                ActivityManager.getService().closeSystemDialogs(SYSTEM_DIALOG_REASON_SCREENSHOT);
            } catch (RemoteException e) {
            }

            Intent actionIntent = intent.getParcelableExtra(SHARING_INTENT);
        public void onReceive(Context context, final Intent intent) {
            Runnable startActivityRunnable = () -> {
                ActivityManagerWrapper.getInstance().closeSystemWindows(
                        SYSTEM_DIALOG_REASON_SCREENSHOT);

            // If this is an edit & default editor exists, route straight there.
            String editorPackage = context.getResources().getString(R.string.config_screenshotEditor);
            if (actionIntent.getAction() == Intent.ACTION_EDIT &&
                    editorPackage != null && editorPackage.length() > 0) {
                actionIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
                final NotificationManager nm =
                        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
                nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
            } else {
                PendingIntent chooseAction = PendingIntent.getBroadcast(context, 0,
                        new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
                        PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
                actionIntent = Intent.createChooser(actionIntent, null,
                        chooseAction.getIntentSender())
                        .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
                Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
                if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
                    cancelScreenshotNotification(context);
                }

                ActivityOptions opts = ActivityOptions.makeBasic();
            opts.setDisallowEnterPictureInPictureWhileLaunching(true);

                opts.setDisallowEnterPictureInPictureWhileLaunching(
                        intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
                context.startActivityAsUser(actionIntent, opts.toBundle(),UserHandle.CURRENT);
            };
            StatusBar statusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
            statusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
                    true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
        }
    }

    /**
     * Removes the notification for a screenshot after a share or edit target is chosen.
     * Removes the notification for a screenshot after a share target is chosen.
     */
    public static class TargetChosenReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Clear the notification
            final NotificationManager nm =
                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
            // Clear the notification only after the user has chosen a share action
            cancelScreenshotNotification(context);
        }
    }

@@ -950,14 +939,18 @@ class GlobalScreenshot {
                return;
            }

            // Clear the notification
            final NotificationManager nm =
                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
            nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
            // Clear the notification when the image is deleted
            cancelScreenshotNotification(context);

            // And delete the image from the media store
            final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
            new DeleteImageInBackgroundTask(context).execute(uri);
        }
    }

    private static void cancelScreenshotNotification(Context context) {
        final NotificationManager nm =
                (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
        nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
    }
}