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

Commit 2e3f8441 authored by Satakshi Rana's avatar Satakshi Rana Committed by Android (Google) Code Review
Browse files

Merge "Passing feedback for screenshots in SysUI"

parents 35e4ad97 aaf69536
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -369,6 +369,10 @@
        <receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver"
            android:exported="false" />

        <!-- Callback for invoking a smart action from the screenshot notification. -->
        <receiver android:name=".screenshot.GlobalScreenshot$SmartActionsReceiver"
                  android:exported="false"/>

        <!-- started from UsbDeviceSettingsManager -->
        <activity android:name=".usb.UsbConfirmActivity"
            android:exported="true"
+5 −1
Original line number Diff line number Diff line
@@ -45,6 +45,8 @@ import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.KeyguardStateController;

import java.util.concurrent.Executor;

import dagger.Module;
import dagger.Provides;

@@ -124,7 +126,9 @@ public class SystemUIFactory {
     * This method is overridden in vendor specific implementation of Sys UI.
     */
    public ScreenshotNotificationSmartActionsProvider
            createScreenshotNotificationSmartActionsProvider() {
            createScreenshotNotificationSmartActionsProvider(Context context,
            Executor executor,
            Handler uiHandler) {
        return new ScreenshotNotificationSmartActionsProvider();
    }

+92 −11
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.dagger.qualifiers.MainResources;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -126,8 +127,17 @@ public class GlobalScreenshot {
        }
    }

    static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
    // These strings are used for communicating the action invoked to
    // ScreenshotNotificationSmartActionsProvider.
    static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
    static final String EXTRA_ID = "android:screenshot_id";
    static final String ACTION_TYPE_DELETE = "Delete";
    static final String ACTION_TYPE_SHARE = "Share";
    static final String ACTION_TYPE_EDIT = "Edit";
    static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
    static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";

    static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
    static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
    static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";

@@ -689,9 +699,9 @@ public class GlobalScreenshot {
    }

    @VisibleForTesting
    static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(Context context,
    static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId,
            Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
            Handler handler, boolean smartActionsEnabled, boolean isManagedProfile) {
            boolean smartActionsEnabled, boolean isManagedProfile) {
        if (!smartActionsEnabled) {
            Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
            return CompletableFuture.completedFuture(Collections.emptyList());
@@ -705,6 +715,7 @@ public class GlobalScreenshot {

        Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
        CompletableFuture<List<Notification.Action>> smartActionsFuture;
        long startTimeMs = SystemClock.uptimeMillis();
        try {
            ActivityManager.RunningTaskInfo runningTask =
                    ActivityManagerWrapper.getInstance().getRunningTask();
@@ -712,34 +723,74 @@ public class GlobalScreenshot {
                    (runningTask != null && runningTask.topActivity != null)
                            ? runningTask.topActivity
                            : new ComponentName("", "");
            smartActionsFuture = smartActionsProvider.getActions(image, context,
                    THREAD_POOL_EXECUTOR,
                    handler,
            smartActionsFuture = smartActionsProvider.getActions(screenshotId, image,
                    componentName,
                    isManagedProfile);
        } catch (Throwable e) {
            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
            smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
            Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
            notifyScreenshotOp(screenshotId, smartActionsProvider,
                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
                    ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
                    waitTimeMs);
        }
        return smartActionsFuture;
    }

    @VisibleForTesting
    static List<Notification.Action> getSmartActions(
            CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs) {
        try {
    static List<Notification.Action> getSmartActions(String screenshotId,
            CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
            ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
        long startTimeMs = SystemClock.uptimeMillis();
        try {
            List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
                    TimeUnit.MILLISECONDS);
            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
            Slog.d(TAG, String.format("Wait time for smart actions: %d ms",
                    SystemClock.uptimeMillis() - startTimeMs));
                    waitTimeMs));
            notifyScreenshotOp(screenshotId, smartActionsProvider,
                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
                    ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
                    waitTimeMs);
            return actions;
        } catch (Throwable e) {
            Slog.e(TAG, "Failed to obtain screenshot notification smart actions.", e);
            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
            Slog.d(TAG, "Failed to obtain screenshot notification smart actions.", e);
            ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
                    (e instanceof TimeoutException)
                            ? ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
                            : ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR;
            notifyScreenshotOp(screenshotId, smartActionsProvider,
                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
                    status, waitTimeMs);
            return Collections.emptyList();
        }
    }

    static void notifyScreenshotOp(String screenshotId,
            ScreenshotNotificationSmartActionsProvider smartActionsProvider,
            ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
            ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
        try {
            smartActionsProvider.notifyOp(screenshotId, op, status, durationMs);
        } catch (Throwable e) {
            Slog.e(TAG, "Error in notifyScreenshotOp: ", e);
        }
    }

    static void notifyScreenshotAction(Context context, String screenshotId, String action,
            boolean isSmartAction) {
        try {
            ScreenshotNotificationSmartActionsProvider provider =
                    SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
                            context, THREAD_POOL_EXECUTOR, new Handler());
            provider.notifyAction(screenshotId, action, isSmartAction);
        } catch (Throwable e) {
            Slog.e(TAG, "Error in notifyScreenshotAction: ", e);
        }
    }

    /**
     * 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).
@@ -783,6 +834,13 @@ public class GlobalScreenshot {
            } else {
                startActivityRunnable.run();
            }

            if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
                String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) ? ACTION_TYPE_EDIT
                        : ACTION_TYPE_SHARE;
                notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
                        actionType, false);
            }
        }
    }

@@ -813,6 +871,29 @@ public class GlobalScreenshot {
            // And delete the image from the media store
            final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
            new DeleteImageInBackgroundTask(context).execute(uri);
            if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
                notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
                        ACTION_TYPE_DELETE,
                        false);
            }
        }
    }

    /**
     * Executes the smart action tapped by the user in the notification.
     */
    public static class SmartActionsReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
            ActivityOptions opts = ActivityOptions.makeBasic();
            context.startActivityAsUser(actionIntent.getIntent(), opts.toBundle(),
                    UserHandle.CURRENT);

            Slog.d(TAG, "Screenshot notification smart action is invoked.");
            notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
                    intent.getStringExtra(EXTRA_ACTION_TYPE),
                    true);
        }
    }

+167 −105
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import android.media.ExifInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
@@ -50,6 +51,7 @@ import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.R;
@@ -69,9 +71,12 @@ import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

/**
@@ -81,6 +86,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
    private static final String TAG = "SaveImageInBackgroundTask";

    private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
    private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
    private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";

    private final GlobalScreenshot.SaveImageInBackgroundData mParams;
@@ -91,8 +97,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
    private final Notification.BigPictureStyle mNotificationStyle;
    private final int mImageWidth;
    private final int mImageHeight;
    private final Handler mHandler;
    private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
    private final String mScreenshotId;
    private final boolean mSmartActionsEnabled;
    private final Random mRandom = new Random();

    SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data,
            NotificationManager nManager) {
@@ -103,11 +111,20 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
        mImageTime = System.currentTimeMillis();
        String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
        mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
        mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID());

        // Initialize screenshot notification smart actions provider.
        mHandler = new Handler();
        mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false);
        if (mSmartActionsEnabled) {
            mSmartActionsProvider =
                SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider();
                    SystemUIFactory.getInstance()
                            .createScreenshotNotificationSmartActionsProvider(
                                    context, THREAD_POOL_EXECUTOR, new Handler());
        } else {
            // If smart actions is not enabled use empty implementation.
            mSmartActionsProvider = new ScreenshotNotificationSmartActionsProvider();
        }

        // Create the large notification icon
        mImageWidth = data.image.getWidth();
@@ -201,6 +218,38 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
        return info.isManagedProfile();
    }

    private List<Notification.Action> buildSmartActions(
            List<Notification.Action> actions, Context context) {
        List<Notification.Action> broadcastActions = new ArrayList<>();
        for (Notification.Action action : actions) {
            // Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver}
            // for logging smart actions.
            Bundle extras = action.getExtras();
            String actionType = extras.getString(
                    ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
                    ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
            Intent intent = new Intent(context,
                    GlobalScreenshot.SmartActionsReceiver.class).putExtra(
                    GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent);
            addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
            PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
                    mRandom.nextInt(),
                    intent,
                    PendingIntent.FLAG_CANCEL_CURRENT);
            broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
                    broadcastIntent).setContextual(true).addExtras(extras).build());
        }
        return broadcastActions;
    }

    private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
            boolean smartActionsEnabled) {
        intent
                .putExtra(GlobalScreenshot.EXTRA_ACTION_TYPE, actionType)
                .putExtra(GlobalScreenshot.EXTRA_ID, screenshotId)
                .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
    }

    /**
     * Generates a new hardware bitmap with specified values, copying the content from the
     * passed in bitmap.
@@ -227,16 +276,13 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {

        Context context = mParams.context;
        Bitmap image = mParams.image;
        boolean smartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true);
        CompletableFuture<List<Notification.Action>>
                smartActionsFuture = GlobalScreenshot.getSmartActionsFuture(
                context, image, mSmartActionsProvider, mHandler, smartActionsEnabled,
                isManagedProfile(context));

        Resources r = context.getResources();

        try {
            CompletableFuture<List<Notification.Action>> smartActionsFuture =
                    GlobalScreenshot.getSmartActionsFuture(mScreenshotId, image,
                            mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));

            // Save the screenshot to the MediaStore
            final MediaStore.PendingParams params = new MediaStore.PendingParams(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
@@ -289,6 +335,31 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
                IoUtils.closeQuietly(session);
            }

            populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);

            mParams.imageUri = uri;
            mParams.image = null;
            mParams.errorMsgResId = 0;
        } catch (Exception e) {
            // IOException/UnsupportedOperationException may be thrown if external storage is
            // not mounted
            Slog.e(TAG, "unable to save screenshot", e);
            mParams.clearImage();
            mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
        }

        // Recycle the bitmap data
        if (image != null) {
            image.recycle();
        }

        return null;
    }

    @VisibleForTesting
    void populateNotificationActions(Context context, Resources r, Uri uri,
            CompletableFuture<List<Notification.Action>> smartActionsFuture,
            Notification.Builder notificationBuilder) {
        // 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
@@ -326,12 +397,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
                new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
                        .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, sharingChooserIntent)
                        .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true)
                        .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
                        .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
                                mSmartActionsEnabled)
                        .setAction(Intent.ACTION_SEND),
                PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
        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());
        notificationBuilder.addAction(shareActionBuilder.build());

        // Create an edit intent, if a specific package is provided as the editor, then
        // launch that directly
@@ -351,12 +425,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
                        .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, editIntent)
                        .putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION,
                                editIntent.getComponent() != null)
                        .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
                        .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
                                mSmartActionsEnabled)
                        .setAction(Intent.ACTION_EDIT),
                PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
        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());
        notificationBuilder.addAction(editActionBuilder.build());
        if (editAction != null && mParams.onEditReady != null) {
            mParams.onEditReady.apply(editAction);
        }
@@ -364,43 +441,28 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
        // Create a delete action for the notification
        PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
                new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
                            .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
                        .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
                        .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
                        .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
                                mSmartActionsEnabled),
                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
        Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
                R.drawable.ic_screenshot_delete,
                r.getString(com.android.internal.R.string.delete), deleteAction);
            mNotificationBuilder.addAction(deleteActionBuilder.build());

            mParams.imageUri = uri;
            mParams.image = null;
            mParams.errorMsgResId = 0;
        notificationBuilder.addAction(deleteActionBuilder.build());

            if (smartActionsEnabled) {
        if (mSmartActionsEnabled) {
            int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
                    SystemUiDeviceConfigFlags
                            .SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
                    1000);
                List<Notification.Action> smartActions = GlobalScreenshot.getSmartActions(
                        smartActionsFuture,
                        timeoutMs);
            List<Notification.Action> smartActions = buildSmartActions(
                    GlobalScreenshot.getSmartActions(mScreenshotId, smartActionsFuture,
                            timeoutMs, mSmartActionsProvider), context);
            for (Notification.Action action : smartActions) {
                    mNotificationBuilder.addAction(action);
                notificationBuilder.addAction(action);
            }
        }
        } catch (Exception e) {
            // IOException/UnsupportedOperationException may be thrown if external storage is
            // not mounted
            Slog.e(TAG, "unable to save screenshot", e);
            mParams.clearImage();
            mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
        }

        // Recycle the bitmap data
        if (image != null) {
            image.recycle();
        }

        return null;
    }

    @Override
+51 −8

File changed.

Preview size limit exceeded, changes collapsed.

Loading