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
This commit is contained in:
@@ -395,19 +395,15 @@
|
||||
|
||||
<!-- 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"
|
||||
<receiver android:name=".screenshot.ActionProxyReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Callback for deleting screenshot notification -->
|
||||
<receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver"
|
||||
<receiver android:name=".screenshot.DeleteScreenshotReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Callback for invoking a smart action from the screenshot notification. -->
|
||||
<receiver android:name=".screenshot.GlobalScreenshot$SmartActionsReceiver"
|
||||
<receiver android:name=".screenshot.SmartActionsReceiver"
|
||||
android:exported="false"/>
|
||||
|
||||
<!-- started from UsbDeviceSettingsManager -->
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Uri, Void, Void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Lazy<StatusBar>> statusBarLazy) {
|
||||
Lazy<StatusBar> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
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<Void, Void, Void> {
|
||||
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<Void, Void, Void> {
|
||||
final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
|
||||
|
||||
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
||||
ScreenshotSmartActions.getSmartActionsFuture(
|
||||
mScreenshotSmartActions.getSmartActionsFuture(
|
||||
mScreenshotId, uri, image, mSmartActionsProvider,
|
||||
mSmartActionsEnabled, getUserHandle(mContext));
|
||||
|
||||
@@ -199,7 +202,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
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<Void, Void, Void> {
|
||||
// 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<Void, Void, Void> {
|
||||
|
||||
// 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<Void, Void, Void> {
|
||||
|
||||
// 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<Void, Void, Void> {
|
||||
|
||||
// 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<Void, Void, Void> {
|
||||
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);
|
||||
|
||||
@@ -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<List<Notification.Action>> getSmartActionsFuture(
|
||||
CompletableFuture<List<Notification.Action>> getSmartActionsFuture(
|
||||
String screenshotId, Uri screenshotUri, Bitmap image,
|
||||
ScreenshotNotificationSmartActionsProvider smartActionsProvider,
|
||||
boolean smartActionsEnabled, UserHandle userHandle) {
|
||||
@@ -86,7 +93,7 @@ public class ScreenshotSmartActions {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static List<Notification.Action> getSmartActions(String screenshotId,
|
||||
List<Notification.Action> getSmartActions(String screenshotId,
|
||||
CompletableFuture<List<Notification.Action>> 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 =
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Object>) 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<List<Notification.Action>> 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<Notification.Action> actions = ScreenshotSmartActions.getSmartActions(
|
||||
List<Notification.Action> 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<List<Notification.Action>> 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<List<Notification.Action>> 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(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user