From c07f0a910faaa59031ed212dc7cbabf3a12ff466 Mon Sep 17 00:00:00 2001 From: Miranda Kephart Date: Thu, 16 Jul 2020 13:19:15 -0400 Subject: [PATCH] Move screenshot receivers and add tests Moves the various broadcast receivers out of GlobalScreenshot and adds unit tests for them, along with some other changes to make them more easily testable: - ScreenshotSmartActions is now a stateless injectable class, instead of a collection of static methods - DeleteImageInBackgroundTask removed, in favor of just calling a background executor directly - remove the TargetChosenReceiver (used to remove notifications after a share target is chosen) since we're not using notifications anymore Bug: 160325487 Test: atest SystemUITests, plus manually checked that screenshots continue to function as expected Change-Id: I1c054dddd76404f385e59f7ab7317beaafde1106 --- packages/SystemUI/AndroidManifest.xml | 10 +- .../DefaultBroadcastReceiverBinder.java | 27 +++- .../screenshot/ActionProxyReceiver.java | 105 ++++++++++++ .../DeleteImageInBackgroundTask.java | 43 ----- .../screenshot/DeleteScreenshotReceiver.java | 68 ++++++++ .../systemui/screenshot/GlobalScreenshot.java | 134 +-------------- .../screenshot/SaveImageInBackgroundTask.java | 24 ++- .../screenshot/ScreenshotSmartActions.java | 15 +- .../screenshot/SmartActionsReceiver.java | 63 ++++++++ .../screenshot/ActionProxyReceiverTest.java | 153 ++++++++++++++++++ .../DeleteScreenshotReceiverTest.java | 145 +++++++++++++++++ ...creenshotNotificationSmartActionsTest.java | 23 +-- .../screenshot/SmartActionsReceiverTest.java | 76 +++++++++ 13 files changed, 678 insertions(+), 208 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java delete mode 100644 packages/SystemUI/src/com/android/systemui/screenshot/DeleteImageInBackgroundTask.java create mode 100644 packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java create mode 100644 packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index a7ef5e6f58f0f..0a02848184cd5 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -395,19 +395,15 @@ - - - - - - diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java index 56d0fa237b82b..6e8d63b2c5168 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java @@ -18,7 +18,9 @@ package com.android.systemui.dagger; import android.content.BroadcastReceiver; -import com.android.systemui.screenshot.GlobalScreenshot.ActionProxyReceiver; +import com.android.systemui.screenshot.ActionProxyReceiver; +import com.android.systemui.screenshot.DeleteScreenshotReceiver; +import com.android.systemui.screenshot.SmartActionsReceiver; import dagger.Binds; import dagger.Module; @@ -30,10 +32,31 @@ import dagger.multibindings.IntoMap; */ @Module public abstract class DefaultBroadcastReceiverBinder { - /** */ + /** + * + */ @Binds @IntoMap @ClassKey(ActionProxyReceiver.class) public abstract BroadcastReceiver bindActionProxyReceiver( ActionProxyReceiver broadcastReceiver); + + /** + * + */ + @Binds + @IntoMap + @ClassKey(DeleteScreenshotReceiver.class) + public abstract BroadcastReceiver bindDeleteScreenshotReceiver( + DeleteScreenshotReceiver broadcastReceiver); + + /** + * + */ + @Binds + @IntoMap + @ClassKey(SmartActionsReceiver.class) + public abstract BroadcastReceiver bindSmartActionsReceiver( + SmartActionsReceiver broadcastReceiver); + } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java new file mode 100644 index 0000000000000..3fd7f94514f3d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_EDIT; +import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_SHARE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED; +import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT; + +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.statusbar.phone.StatusBar; + +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.inject.Inject; + +/** + * 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 class ActionProxyReceiver extends BroadcastReceiver { + private static final String TAG = "ActionProxyReceiver"; + + private static final int CLOSE_WINDOWS_TIMEOUT_MILLIS = 3000; + private final StatusBar mStatusBar; + private final ActivityManagerWrapper mActivityManagerWrapper; + private final ScreenshotSmartActions mScreenshotSmartActions; + + @Inject + public ActionProxyReceiver(Optional statusBar, + ActivityManagerWrapper activityManagerWrapper, + ScreenshotSmartActions screenshotSmartActions) { + mStatusBar = statusBar.orElse(null); + mActivityManagerWrapper = activityManagerWrapper; + mScreenshotSmartActions = screenshotSmartActions; + } + + @Override + public void onReceive(Context context, final Intent intent) { + Runnable startActivityRunnable = () -> { + try { + mActivityManagerWrapper.closeSystemWindows( + SYSTEM_DIALOG_REASON_SCREENSHOT).get( + CLOSE_WINDOWS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } catch (TimeoutException | InterruptedException | ExecutionException e) { + Log.e(TAG, "Unable to share screenshot", e); + return; + } + + PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); + ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setDisallowEnterPictureInPictureWhileLaunching( + intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false)); + try { + actionIntent.send(context, 0, null, null, null, null, opts.toBundle()); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Pending intent canceled", e); + } + + }; + + if (mStatusBar != null) { + mStatusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null, + true /* dismissShade */, true /* afterKeyguardGone */, + true /* deferred */); + } 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; + mScreenshotSmartActions.notifyScreenshotAction( + context, intent.getStringExtra(EXTRA_ID), actionType, false); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteImageInBackgroundTask.java deleted file mode 100644 index 8c4865510ed1c..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteImageInBackgroundTask.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot; - -import android.content.ContentResolver; -import android.content.Context; -import android.net.Uri; -import android.os.AsyncTask; - -/** - * An AsyncTask that deletes an image from the media store in the background. - */ -class DeleteImageInBackgroundTask extends AsyncTask { - private Context mContext; - - DeleteImageInBackgroundTask(Context context) { - mContext = context; - } - - @Override - protected Void doInBackground(Uri... params) { - if (params.length != 1) return null; - - Uri screenshotUri = params[0]; - ContentResolver resolver = mContext.getContentResolver(); - resolver.delete(screenshotUri, null, null); - return null; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java new file mode 100644 index 0000000000000..9028bb57c8e5a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_DELETE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED; +import static com.android.systemui.screenshot.GlobalScreenshot.SCREENSHOT_URI_ID; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +import com.android.systemui.dagger.qualifiers.Background; + +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * Removes the file at a provided URI. + */ +public class DeleteScreenshotReceiver extends BroadcastReceiver { + + private final ScreenshotSmartActions mScreenshotSmartActions; + private final Executor mBackgroundExecutor; + + @Inject + public DeleteScreenshotReceiver(ScreenshotSmartActions screenshotSmartActions, + @Background Executor backgroundExecutor) { + mScreenshotSmartActions = screenshotSmartActions; + mBackgroundExecutor = backgroundExecutor; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!intent.hasExtra(SCREENSHOT_URI_ID)) { + return; + } + + // And delete the image from the media store + final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID)); + mBackgroundExecutor.execute(() -> { + ContentResolver resolver = context.getContentResolver(); + resolver.delete(uri, null, null); + }); + if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) { + mScreenshotSmartActions.notifyScreenshotAction( + context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 85444303490dc..aca82fd75904a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -21,8 +21,6 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -30,13 +28,10 @@ import android.animation.ValueAnimator; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; -import android.app.ActivityOptions; import android.app.Notification; import android.app.PendingIntent; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; @@ -63,7 +58,6 @@ import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import android.util.MathUtils; -import android.util.Slog; import android.view.Display; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -88,23 +82,15 @@ import android.widget.Toast; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.statusbar.phone.StatusBar; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Singleton; -import dagger.Lazy; - /** * Class for handling device screen shots */ @@ -193,6 +179,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private final UiEventLogger mUiEventLogger; private final Context mContext; + private final ScreenshotSmartActions mScreenshotSmartActions; private final WindowManager mWindowManager; private final WindowManager.LayoutParams mWindowLayoutParams; private final Display mDisplay; @@ -248,9 +235,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset @Inject public GlobalScreenshot( Context context, @Main Resources resources, + ScreenshotSmartActions screenshotSmartActions, ScreenshotNotificationsController screenshotNotificationsController, UiEventLogger uiEventLogger) { mContext = context; + mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; mUiEventLogger = uiEventLogger; @@ -713,7 +702,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); } - mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); + mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); mSaveInBgTask.execute(); } @@ -1125,119 +1114,4 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return insetDrawable; } } - - /** - * 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 ActionProxyReceiver extends BroadcastReceiver { - static final int CLOSE_WINDOWS_TIMEOUT_MILLIS = 3000; - private final StatusBar mStatusBar; - - @Inject - public ActionProxyReceiver(Optional> statusBarLazy) { - Lazy statusBar = statusBarLazy.orElse(null); - mStatusBar = statusBar != null ? statusBar.get() : null; - } - - @Override - public void onReceive(Context context, final Intent intent) { - Runnable startActivityRunnable = () -> { - try { - ActivityManagerWrapper.getInstance().closeSystemWindows( - SYSTEM_DIALOG_REASON_SCREENSHOT).get( - CLOSE_WINDOWS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (TimeoutException | InterruptedException | ExecutionException e) { - Slog.e(TAG, "Unable to share screenshot", e); - return; - } - - PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); - if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) { - ScreenshotNotificationsController.cancelScreenshotNotification(context); - } - ActivityOptions opts = ActivityOptions.makeBasic(); - opts.setDisallowEnterPictureInPictureWhileLaunching( - intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false)); - try { - actionIntent.send(context, 0, null, null, null, null, opts.toBundle()); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "Pending intent canceled", e); - } - - }; - - if (mStatusBar != null) { - mStatusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null, - true /* dismissShade */, true /* afterKeyguardGone */, - true /* deferred */); - } 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; - ScreenshotSmartActions.notifyScreenshotAction( - context, intent.getStringExtra(EXTRA_ID), actionType, false); - } - } - } - - /** - * 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 only after the user has chosen a share action - ScreenshotNotificationsController.cancelScreenshotNotification(context); - } - } - - /** - * Removes the last screenshot. - */ - public static class DeleteScreenshotReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (!intent.hasExtra(SCREENSHOT_URI_ID)) { - return; - } - - // Clear the notification when the image is deleted - ScreenshotNotificationsController.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); - if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) { - ScreenshotSmartActions.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 pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); - String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE); - Slog.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent()); - ActivityOptions opts = ActivityOptions.makeBasic(); - - try { - pendingIntent.send(context, 0, null, null, null, null, opts.toBundle()); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "Pending intent canceled", e); - } - - ScreenshotSmartActions.notifyScreenshotAction( - context, intent.getStringExtra(EXTRA_ID), actionType, true); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 468b9b16addb0..df1d78953f46c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -81,6 +81,7 @@ class SaveImageInBackgroundTask extends AsyncTask { private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"; private final Context mContext; + private final ScreenshotSmartActions mScreenshotSmartActions; private final GlobalScreenshot.SaveImageInBackgroundData mParams; private final GlobalScreenshot.SavedImageData mImageData; private final String mImageFileName; @@ -90,8 +91,10 @@ class SaveImageInBackgroundTask extends AsyncTask { private final boolean mSmartActionsEnabled; private final Random mRandom = new Random(); - SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) { + SaveImageInBackgroundTask(Context context, ScreenshotSmartActions screenshotSmartActions, + GlobalScreenshot.SaveImageInBackgroundData data) { mContext = context; + mScreenshotSmartActions = screenshotSmartActions; mImageData = new GlobalScreenshot.SavedImageData(); // Prepare all the output metadata @@ -141,7 +144,7 @@ class SaveImageInBackgroundTask extends AsyncTask { final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); CompletableFuture> smartActionsFuture = - ScreenshotSmartActions.getSmartActionsFuture( + mScreenshotSmartActions.getSmartActionsFuture( mScreenshotId, uri, image, mSmartActionsProvider, mSmartActionsEnabled, getUserHandle(mContext)); @@ -199,7 +202,7 @@ class SaveImageInBackgroundTask extends AsyncTask { SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS, 1000); smartActions.addAll(buildSmartActions( - ScreenshotSmartActions.getSmartActions( + mScreenshotSmartActions.getSmartActions( mScreenshotId, smartActionsFuture, timeoutMs, mSmartActionsProvider), mContext)); @@ -274,11 +277,8 @@ class SaveImageInBackgroundTask extends AsyncTask { // by setting the (otherwise unused) request code to the current user id. int requestCode = context.getUserId(); - PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode, - new Intent(context, GlobalScreenshot.TargetChosenReceiver.class), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); Intent sharingChooserIntent = - Intent.createChooser(sharingIntent, null, chooserAction.getIntentSender()) + Intent.createChooser(sharingIntent, null) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -288,7 +288,7 @@ class SaveImageInBackgroundTask extends AsyncTask { // Create a share action for the notification PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode, - new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) + new Intent(context, ActionProxyReceiver.class) .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent) .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) @@ -333,10 +333,8 @@ class SaveImageInBackgroundTask extends AsyncTask { // Create a edit action PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, - new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) + new Intent(context, ActionProxyReceiver.class) .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent) - .putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION, - editIntent.getComponent() != null) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, mSmartActionsEnabled) @@ -358,7 +356,7 @@ class SaveImageInBackgroundTask extends AsyncTask { // Create a delete action for the notification PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, - new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class) + new Intent(context, DeleteScreenshotReceiver.class) .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, @@ -398,7 +396,7 @@ class SaveImageInBackgroundTask extends AsyncTask { String actionType = extras.getString( ScreenshotNotificationSmartActionsProvider.ACTION_TYPE, ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE); - Intent intent = new Intent(context, GlobalScreenshot.SmartActionsReceiver.class) + Intent intent = new Intent(context, SmartActionsReceiver.class) .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java index 442b373b31bee..633cdd6ca5ca5 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java @@ -39,14 +39,21 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Collects the static functions for retrieving and acting on smart actions. */ +@Singleton public class ScreenshotSmartActions { private static final String TAG = "ScreenshotSmartActions"; + @Inject + public ScreenshotSmartActions() {} + @VisibleForTesting - static CompletableFuture> getSmartActionsFuture( + CompletableFuture> getSmartActionsFuture( String screenshotId, Uri screenshotUri, Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider, boolean smartActionsEnabled, UserHandle userHandle) { @@ -86,7 +93,7 @@ public class ScreenshotSmartActions { } @VisibleForTesting - static List getSmartActions(String screenshotId, + List getSmartActions(String screenshotId, CompletableFuture> smartActionsFuture, int timeoutMs, ScreenshotNotificationSmartActionsProvider smartActionsProvider) { long startTimeMs = SystemClock.uptimeMillis(); @@ -116,7 +123,7 @@ public class ScreenshotSmartActions { } } - static void notifyScreenshotOp(String screenshotId, + void notifyScreenshotOp(String screenshotId, ScreenshotNotificationSmartActionsProvider smartActionsProvider, ScreenshotNotificationSmartActionsProvider.ScreenshotOp op, ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) { @@ -127,7 +134,7 @@ public class ScreenshotSmartActions { } } - static void notifyScreenshotAction(Context context, String screenshotId, String action, + void notifyScreenshotAction(Context context, String screenshotId, String action, boolean isSmartAction) { try { ScreenshotNotificationSmartActionsProvider provider = diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java new file mode 100644 index 0000000000000..217235b16ecf5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_TYPE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; + +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.util.Slog; + +import javax.inject.Inject; + + +/** + * Executes the smart action tapped by the user in the notification. + */ +public class SmartActionsReceiver extends BroadcastReceiver { + private static final String TAG = "SmartActionsReceiver"; + + private final ScreenshotSmartActions mScreenshotSmartActions; + + @Inject + SmartActionsReceiver(ScreenshotSmartActions screenshotSmartActions) { + mScreenshotSmartActions = screenshotSmartActions; + } + + @Override + public void onReceive(Context context, Intent intent) { + PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); + String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE); + Slog.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent()); + ActivityOptions opts = ActivityOptions.makeBasic(); + + try { + pendingIntent.send(context, 0, null, null, null, null, opts.toBundle()); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Pending intent canceled", e); + } + + mScreenshotSmartActions.notifyScreenshotAction( + context, intent.getStringExtra(EXTRA_ID), actionType, true); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java new file mode 100644 index 0000000000000..4aaafbdaec1d8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_SHARE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED; +import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.statusbar.phone.StatusBar; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; + +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class ActionProxyReceiverTest extends SysuiTestCase { + + @Mock + private StatusBar mMockStatusBar; + @Mock + private ActivityManagerWrapper mMockActivityManagerWrapper; + @Mock + private Future mMockFuture; + @Mock + private ScreenshotSmartActions mMockScreenshotSmartActions; + @Mock + private PendingIntent mMockPendingIntent; + + private Intent mIntent; + + @Before + public void setup() throws InterruptedException, ExecutionException, TimeoutException { + MockitoAnnotations.initMocks(this); + mIntent = new Intent(mContext, ActionProxyReceiver.class) + .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, mMockPendingIntent); + + when(mMockActivityManagerWrapper.closeSystemWindows(anyString())).thenReturn(mMockFuture); + when(mMockFuture.get(anyLong(), any(TimeUnit.class))).thenReturn(null); + } + + @Test + public void testPendingIntentSentWithoutStatusBar() throws PendingIntent.CanceledException { + ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(false); + + actionProxyReceiver.onReceive(mContext, mIntent); + + verify(mMockActivityManagerWrapper).closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT); + verify(mMockStatusBar, never()).executeRunnableDismissingKeyguard( + any(Runnable.class), any(Runnable.class), anyBoolean(), anyBoolean(), anyBoolean()); + verify(mMockPendingIntent).send( + eq(mContext), anyInt(), isNull(), isNull(), isNull(), isNull(), any(Bundle.class)); + } + + @Test + public void testPendingIntentSentWithStatusBar() throws PendingIntent.CanceledException { + ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true); + // ensure that the pending intent call is passed through + doAnswer((Answer) invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mMockStatusBar).executeRunnableDismissingKeyguard( + any(Runnable.class), isNull(), anyBoolean(), anyBoolean(), anyBoolean()); + + actionProxyReceiver.onReceive(mContext, mIntent); + + verify(mMockActivityManagerWrapper).closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT); + verify(mMockStatusBar).executeRunnableDismissingKeyguard( + any(Runnable.class), isNull(), eq(true), eq(true), eq(true)); + verify(mMockPendingIntent).send( + eq(mContext), anyInt(), isNull(), isNull(), isNull(), isNull(), any(Bundle.class)); + } + + @Test + public void testSmartActionsNotNotifiedByDefault() { + ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true); + + actionProxyReceiver.onReceive(mContext, mIntent); + + verify(mMockScreenshotSmartActions, never()) + .notifyScreenshotAction(any(Context.class), anyString(), anyString(), anyBoolean()); + } + + @Test + public void testSmartActionsNotifiedIfEnabled() { + ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true); + mIntent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true); + String testId = "testID"; + mIntent.putExtra(EXTRA_ID, testId); + + actionProxyReceiver.onReceive(mContext, mIntent); + + verify(mMockScreenshotSmartActions).notifyScreenshotAction( + mContext, testId, ACTION_TYPE_SHARE, false); + } + + private ActionProxyReceiver constructActionProxyReceiver(boolean withStatusBar) { + if (withStatusBar) { + return new ActionProxyReceiver( + Optional.of(mMockStatusBar), mMockActivityManagerWrapper, + mMockScreenshotSmartActions); + } else { + return new ActionProxyReceiver( + Optional.empty(), mMockActivityManagerWrapper, mMockScreenshotSmartActions); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java new file mode 100644 index 0000000000000..b9249131c191b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_DELETE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED; +import static com.android.systemui.screenshot.GlobalScreenshot.SCREENSHOT_URI_ID; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.util.concurrent.Executor; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class DeleteScreenshotReceiverTest extends SysuiTestCase { + + @Mock + private ScreenshotSmartActions mMockScreenshotSmartActions; + @Mock + private Executor mMockExecutor; + + private DeleteScreenshotReceiver mDeleteScreenshotReceiver; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mDeleteScreenshotReceiver = + new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mMockExecutor); + } + + @Test + public void testNoUriProvided() { + Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class); + + mDeleteScreenshotReceiver.onReceive(mContext, intent); + + verify(mMockExecutor, never()).execute(any(Runnable.class)); + verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction( + any(Context.class), any(String.class), any(String.class), anyBoolean()); + } + + @Test + public void testFileDeleted() { + DeleteScreenshotReceiver deleteScreenshotReceiver = + new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mFakeExecutor); + ContentResolver contentResolver = mContext.getContentResolver(); + final Uri testUri = contentResolver.insert( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, getFakeContentValues()); + assertNotNull(testUri); + + try { + Cursor cursor = + contentResolver.query(testUri, null, null, null, null); + assertEquals(1, cursor.getCount()); + Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class) + .putExtra(SCREENSHOT_URI_ID, testUri.toString()); + + deleteScreenshotReceiver.onReceive(mContext, intent); + int runCount = mFakeExecutor.runAllReady(); + + assertEquals(1, runCount); + cursor = + contentResolver.query(testUri, null, null, null, null); + assertEquals(0, cursor.getCount()); + } finally { + contentResolver.delete(testUri, null, null); + } + + // ensure smart actions not called by default + verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction( + any(Context.class), any(String.class), any(String.class), anyBoolean()); + } + + @Test + public void testNotifyScreenshotAction() { + Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class); + String uriString = "testUri"; + String testId = "testID"; + intent.putExtra(SCREENSHOT_URI_ID, uriString); + intent.putExtra(EXTRA_ID, testId); + intent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true); + + mDeleteScreenshotReceiver.onReceive(mContext, intent); + + verify(mMockExecutor).execute(any(Runnable.class)); + verify(mMockScreenshotSmartActions).notifyScreenshotAction( + mContext, testId, ACTION_TYPE_DELETE, false); + } + + private static ContentValues getFakeContentValues() { + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + + File.separator + Environment.DIRECTORY_SCREENSHOTS); + values.put(MediaStore.MediaColumns.DISPLAY_NAME, "test_screenshot"); + values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png"); + values.put(MediaStore.MediaColumns.DATE_ADDED, 0); + values.put(MediaStore.MediaColumns.DATE_MODIFIED, 0); + return values; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java index d3b33992d0176..184329ec6e5fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -61,12 +61,14 @@ import java.util.concurrent.TimeUnit; */ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { private ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; + private ScreenshotSmartActions mScreenshotSmartActions; private Handler mHandler; @Before public void setup() { mSmartActionsProvider = mock( ScreenshotNotificationSmartActionsProvider.class); + mScreenshotSmartActions = new ScreenshotSmartActions(); mHandler = mock(Handler.class); } @@ -82,7 +84,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { when(smartActionsProvider.getActions(any(), any(), any(), any(), any())) .thenThrow(RuntimeException.class); CompletableFuture> smartActionsFuture = - ScreenshotSmartActions.getSmartActionsFuture( + mScreenshotSmartActions.getSmartActionsFuture( "", Uri.parse("content://authority/data"), bitmap, smartActionsProvider, true, UserHandle.getUserHandleForUid(UserHandle.myUserId())); assertNotNull(smartActionsFuture); @@ -100,7 +102,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { int timeoutMs = 1000; when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow( RuntimeException.class); - List actions = ScreenshotSmartActions.getSmartActions( + List actions = mScreenshotSmartActions.getSmartActions( "", smartActionsFuture, timeoutMs, mSmartActionsProvider); assertEquals(Collections.emptyList(), actions); } @@ -111,7 +113,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { throws Exception { doThrow(RuntimeException.class).when(mSmartActionsProvider).notifyOp(any(), any(), any(), anyLong()); - ScreenshotSmartActions.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1); + mScreenshotSmartActions.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1); } // Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked @@ -122,7 +124,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { Bitmap bitmap = mock(Bitmap.class); when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565); CompletableFuture> smartActionsFuture = - ScreenshotSmartActions.getSmartActionsFuture( + mScreenshotSmartActions.getSmartActionsFuture( "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider, true, UserHandle.getUserHandleForUid(UserHandle.myUserId())); verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), any()); @@ -136,7 +138,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { public void testScreenshotNotificationSmartActionsProviderInvokedOnce() { Bitmap bitmap = mock(Bitmap.class); when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE); - ScreenshotSmartActions.getSmartActionsFuture( + mScreenshotSmartActions.getSmartActionsFuture( "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider, true, UserHandle.getUserHandleForUid(UserHandle.myUserId())); verify(mSmartActionsProvider, times(1)).getActions(any(), any(), any(), any(), any()); @@ -152,7 +154,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider( mContext, null, mHandler); CompletableFuture> smartActionsFuture = - ScreenshotSmartActions.getSmartActionsFuture("", null, bitmap, + mScreenshotSmartActions.getSmartActionsFuture("", null, bitmap, actionsProvider, true, UserHandle.getUserHandleForUid(UserHandle.myUserId())); assertNotNull(smartActionsFuture); @@ -172,7 +174,8 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; - SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + SaveImageInBackgroundTask task = + new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")); @@ -198,7 +201,8 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; - SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + SaveImageInBackgroundTask task = + new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")); @@ -224,7 +228,8 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; - SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + SaveImageInBackgroundTask task = + new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); Notification.Action deleteAction = task.createDeleteAction(mContext, mContext.getResources(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java new file mode 100644 index 0000000000000..ce6f0736ec335 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_TYPE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; + +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class SmartActionsReceiverTest extends SysuiTestCase { + + @Mock + private ScreenshotSmartActions mMockScreenshotSmartActions; + @Mock + private PendingIntent mMockPendingIntent; + + private SmartActionsReceiver mSmartActionsReceiver; + private Intent mIntent; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mSmartActionsReceiver = new SmartActionsReceiver(mMockScreenshotSmartActions); + mIntent = new Intent(mContext, SmartActionsReceiver.class) + .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, mMockPendingIntent); + } + + @Test + public void testSmartActionIntent() throws PendingIntent.CanceledException { + String testId = "testID"; + String testActionType = "testActionType"; + mIntent.putExtra(EXTRA_ID, testId); + mIntent.putExtra(EXTRA_ACTION_TYPE, testActionType); + + mSmartActionsReceiver.onReceive(mContext, mIntent); + + verify(mMockPendingIntent).send( + eq(mContext), eq(0), isNull(), isNull(), isNull(), isNull(), any(Bundle.class)); + verify(mMockScreenshotSmartActions).notifyScreenshotAction( + mContext, testId, testActionType, true); + } +}