Merge "Screenshot Notification Smart Action: AiAi and - Sys UI integration" into qt-qpr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
73b4e1923a
@@ -45,6 +45,17 @@ import java.util.concurrent.Executor;
|
||||
*/
|
||||
@SystemApi
|
||||
public final class ContentSuggestionsManager {
|
||||
/**
|
||||
* Key into the extras Bundle passed to {@link #provideContextImage(int, Bundle)}.
|
||||
* This can be used to provide the bitmap to
|
||||
* {@link android.service.contentsuggestions.ContentSuggestionsService}.
|
||||
* The value must be a {@link android.graphics.Bitmap} with the
|
||||
* config {@link android.graphics.Bitmap.Config.HARDWARE}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_BITMAP = "android.contentsuggestions.extra.BITMAP";
|
||||
|
||||
private static final String TAG = ContentSuggestionsManager.class.getSimpleName();
|
||||
|
||||
/**
|
||||
@@ -70,7 +81,7 @@ public final class ContentSuggestionsManager {
|
||||
* system content suggestions service.
|
||||
*
|
||||
* @param taskId of the task to snapshot.
|
||||
* @param imageContextRequestExtras sent with with request to provide implementation specific
|
||||
* @param imageContextRequestExtras sent with request to provide implementation specific
|
||||
* extra information.
|
||||
*/
|
||||
public void provideContextImage(
|
||||
|
||||
@@ -66,12 +66,17 @@ public abstract class ContentSuggestionsService extends Service {
|
||||
int colorSpaceId, Bundle imageContextRequestExtras) {
|
||||
|
||||
Bitmap wrappedBuffer = null;
|
||||
if (contextImage != null) {
|
||||
ColorSpace colorSpace = null;
|
||||
if (colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length) {
|
||||
colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]);
|
||||
if (imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) {
|
||||
wrappedBuffer = imageContextRequestExtras.getParcelable(
|
||||
ContentSuggestionsManager.EXTRA_BITMAP);
|
||||
} else {
|
||||
if (contextImage != null) {
|
||||
ColorSpace colorSpace = null;
|
||||
if (colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length) {
|
||||
colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]);
|
||||
}
|
||||
wrappedBuffer = Bitmap.wrapHardwareBuffer(contextImage, colorSpace);
|
||||
}
|
||||
wrappedBuffer = Bitmap.wrapHardwareBuffer(contextImage, colorSpace);
|
||||
}
|
||||
|
||||
mHandler.sendMessage(
|
||||
|
||||
@@ -47,6 +47,20 @@ public final class SystemUiDeviceConfigFlags {
|
||||
*/
|
||||
public static final String NAS_MAX_SUGGESTIONS = "nas_max_suggestions";
|
||||
|
||||
// Flags related to screenshot intelligence
|
||||
|
||||
/**
|
||||
* (bool) Whether to enable smart actions in screenshot notifications.
|
||||
*/
|
||||
public static final String ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS =
|
||||
"enable_screenshot_notification_smart_actions";
|
||||
|
||||
/**
|
||||
* (int) Timeout value in ms to get smart actions for screenshot notification.
|
||||
*/
|
||||
public static final String SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS =
|
||||
"screenshot_notification_smart_actions_timeout_ms";
|
||||
|
||||
// Flags related to Smart Suggestions - these are read in SmartReplyConstants.
|
||||
|
||||
/** (boolean) Whether to enable smart suggestions in notifications. */
|
||||
|
||||
@@ -60,6 +60,9 @@
|
||||
<uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
|
||||
<uses-permission android:name="android.permission.USE_RESERVED_DISK" />
|
||||
|
||||
<!-- to invoke ContentSuggestionsService -->
|
||||
<uses-permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"/>
|
||||
|
||||
<!-- Networking and telephony -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.android.keyguard.ViewMediatorCallback;
|
||||
import com.android.systemui.keyguard.DismissCallbackRegistry;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
|
||||
import com.android.systemui.statusbar.KeyguardIndicationController;
|
||||
import com.android.systemui.statusbar.NotificationMediaManager;
|
||||
import com.android.systemui.statusbar.ScrimView;
|
||||
@@ -110,6 +111,15 @@ public class SystemUIFactory {
|
||||
return new StatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of ScreenshotNotificationSmartActionsProvider.
|
||||
* This method is overridden in vendor specific implementation of Sys UI.
|
||||
*/
|
||||
public ScreenshotNotificationSmartActionsProvider
|
||||
createScreenshotNotificationSmartActionsProvider() {
|
||||
return new ScreenshotNotificationSmartActionsProvider();
|
||||
}
|
||||
|
||||
public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback,
|
||||
LockPatternUtils lockPatternUtils, ViewGroup container,
|
||||
DismissCallbackRegistry dismissCallbackRegistry,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.systemui.screenshot;
|
||||
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
|
||||
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
|
||||
import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT;
|
||||
@@ -29,7 +30,9 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.app.Notification;
|
||||
import android.app.Notification.BigPictureStyle;
|
||||
import android.app.NotificationManager;
|
||||
@@ -42,6 +45,7 @@ import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -59,9 +63,14 @@ import android.media.MediaActionSound;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
@@ -77,10 +86,13 @@ import android.view.animation.Interpolator;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysUiServiceProvider;
|
||||
import com.android.systemui.SystemUI;
|
||||
import com.android.systemui.SystemUIFactory;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.statusbar.phone.StatusBar;
|
||||
import com.android.systemui.util.NotificationChannels;
|
||||
@@ -91,7 +103,10 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@@ -138,6 +153,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
private final BigPictureStyle mNotificationStyle;
|
||||
private final int mImageWidth;
|
||||
private final int mImageHeight;
|
||||
private final Handler mHandler;
|
||||
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
|
||||
|
||||
SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
|
||||
NotificationManager nManager) {
|
||||
@@ -149,6 +166,11 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
|
||||
mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
|
||||
|
||||
// Initialize screenshot notification smart actions provider.
|
||||
mHandler = new Handler();
|
||||
mSmartActionsProvider =
|
||||
SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider();
|
||||
|
||||
// Create the large notification icon
|
||||
mImageWidth = data.image.getWidth();
|
||||
mImageHeight = data.image.getHeight();
|
||||
@@ -222,6 +244,23 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
mNotificationStyle.bigLargeIcon((Bitmap) null);
|
||||
}
|
||||
|
||||
private int getUserHandleOfForegroundApplication(Context context) {
|
||||
// This logic matches
|
||||
// com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
|
||||
try {
|
||||
return ActivityTaskManager.getService().getLastResumedActivityUserId();
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "getUserHandleOfForegroundApplication: ", e);
|
||||
return context.getUserId();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isManagedProfile(Context context) {
|
||||
UserManager manager = UserManager.get(context);
|
||||
UserInfo info = manager.getUserInfo(getUserHandleOfForegroundApplication(context));
|
||||
return info.isManagedProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new hardware bitmap with specified values, copying the content from the passed
|
||||
* in bitmap.
|
||||
@@ -248,6 +287,12 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
Context context = mParams.context;
|
||||
Bitmap image = mParams.image;
|
||||
boolean smartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false);
|
||||
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
||||
GlobalScreenshot.getSmartActionsFuture(
|
||||
context, image, mSmartActionsProvider, mHandler, smartActionsEnabled,
|
||||
isManagedProfile(context));
|
||||
Resources r = context.getResources();
|
||||
|
||||
try {
|
||||
@@ -348,6 +393,19 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
mParams.imageUri = uri;
|
||||
mParams.image = null;
|
||||
mParams.errorMsgResId = 0;
|
||||
|
||||
if (smartActionsEnabled) {
|
||||
int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags
|
||||
.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
|
||||
1000);
|
||||
List<Notification.Action> smartActions = GlobalScreenshot.getSmartActions(
|
||||
smartActionsFuture,
|
||||
timeoutMs);
|
||||
for (Notification.Action action : smartActions) {
|
||||
mNotificationBuilder.addAction(action);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// IOException/UnsupportedOperationException may be thrown if external storage is not
|
||||
// mounted
|
||||
@@ -904,6 +962,58 @@ class GlobalScreenshot {
|
||||
nManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(Context context,
|
||||
Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
|
||||
Handler handler, boolean smartActionsEnabled, boolean isManagedProfile) {
|
||||
if (!smartActionsEnabled) {
|
||||
Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
if (image.getConfig() != Bitmap.Config.HARDWARE) {
|
||||
Slog.w(TAG, String.format(
|
||||
"Bitmap expected: Hardware, Bitmap found: %s. Returning empty list.",
|
||||
image.getConfig()));
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
|
||||
CompletableFuture<List<Notification.Action>> smartActionsFuture;
|
||||
try {
|
||||
ActivityManager.RunningTaskInfo runningTask =
|
||||
ActivityManagerWrapper.getInstance().getRunningTask();
|
||||
ComponentName componentName =
|
||||
(runningTask != null && runningTask.topActivity != null)
|
||||
? runningTask.topActivity
|
||||
: new ComponentName("", "");
|
||||
smartActionsFuture = smartActionsProvider.getActions(image, context,
|
||||
THREAD_POOL_EXECUTOR,
|
||||
handler,
|
||||
componentName,
|
||||
isManagedProfile);
|
||||
} catch (Throwable e) {
|
||||
smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
|
||||
Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
|
||||
}
|
||||
return smartActionsFuture;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static List<Notification.Action> getSmartActions(
|
||||
CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs) {
|
||||
try {
|
||||
long startTimeMs = SystemClock.uptimeMillis();
|
||||
List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
|
||||
TimeUnit.MILLISECONDS);
|
||||
Slog.d(TAG, String.format("Wait time for smart actions: %d ms",
|
||||
SystemClock.uptimeMillis() - startTimeMs));
|
||||
return actions;
|
||||
} catch (Throwable e) {
|
||||
Slog.e(TAG, "Failed to obtain screenshot notification smart actions.", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.app.Notification;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* This class can be overridden by a vendor-specific sys UI implementation,
|
||||
* in order to provide smart actions in the screenshot notification.
|
||||
*/
|
||||
public class ScreenshotNotificationSmartActionsProvider {
|
||||
private static final String TAG = "ScreenshotActions";
|
||||
|
||||
/**
|
||||
* Default implementation that returns an empty list.
|
||||
* This method is overridden in vendor-specific Sys UI implementation.
|
||||
*
|
||||
* @param bitmap The bitmap of the screenshot. The bitmap config must be {@link
|
||||
* HARDWARE}.
|
||||
* @param context The current app {@link Context}.
|
||||
* @param executor A {@link Executor} that can be used to execute tasks in parallel.
|
||||
* @param handler A {@link Handler} to possibly run UI-thread code.
|
||||
* @param componentName Contains package and activity class names where the screenshot was
|
||||
* taken. This is used as an additional signal to generate and rank more
|
||||
* relevant actions.
|
||||
* @param isManagedProfile The screenshot was taken for a work profile app.
|
||||
*/
|
||||
public CompletableFuture<List<Notification.Action>> getActions(Bitmap bitmap, Context context,
|
||||
Executor executor, Handler handler, ComponentName componentName,
|
||||
boolean isManagedProfile) {
|
||||
Log.d(TAG, "Returning empty smart action list.");
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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 static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Handler;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SystemUIFactory;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Tests for exception handling and bitmap configuration in adding smart actions to Screenshot
|
||||
* Notification.
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
|
||||
private ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
|
||||
private Handler mHandler;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mSmartActionsProvider = mock(
|
||||
ScreenshotNotificationSmartActionsProvider.class);
|
||||
mHandler = mock(Handler.class);
|
||||
}
|
||||
|
||||
// Tests any exception thrown in getting smart actions future does not affect regular
|
||||
// screenshot flow.
|
||||
@Test
|
||||
public void testExceptionHandlingInGetSmartActionsFuture()
|
||||
throws Exception {
|
||||
Bitmap bitmap = mock(Bitmap.class);
|
||||
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
|
||||
ScreenshotNotificationSmartActionsProvider smartActionsProvider = mock(
|
||||
ScreenshotNotificationSmartActionsProvider.class);
|
||||
when(smartActionsProvider.getActions(any(), any(), any(), any(), any(),
|
||||
eq(false))).thenThrow(
|
||||
RuntimeException.class);
|
||||
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
||||
GlobalScreenshot.getSmartActionsFuture(mContext, bitmap,
|
||||
smartActionsProvider, mHandler, true, false);
|
||||
Assert.assertNotNull(smartActionsFuture);
|
||||
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
||||
Assert.assertEquals(Collections.emptyList(), smartActions);
|
||||
}
|
||||
|
||||
// Tests any exception thrown in waiting for smart actions future to complete does
|
||||
// not affect regular screenshot flow.
|
||||
@Test
|
||||
public void testExceptionHandlingInGetSmartActions()
|
||||
throws Exception {
|
||||
CompletableFuture<List<Notification.Action>> smartActionsFuture = mock(
|
||||
CompletableFuture.class);
|
||||
int timeoutMs = 1000;
|
||||
when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow(
|
||||
RuntimeException.class);
|
||||
List<Notification.Action> actions = GlobalScreenshot.getSmartActions(
|
||||
smartActionsFuture, timeoutMs);
|
||||
Assert.assertEquals(Collections.emptyList(), actions);
|
||||
}
|
||||
|
||||
// Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked
|
||||
// and a completed future is returned.
|
||||
@Test
|
||||
public void testUnsupportedBitmapConfiguration()
|
||||
throws Exception {
|
||||
Bitmap bitmap = mock(Bitmap.class);
|
||||
when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565);
|
||||
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
||||
GlobalScreenshot.getSmartActionsFuture(mContext, bitmap,
|
||||
mSmartActionsProvider, mHandler, true, true);
|
||||
verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), any(),
|
||||
eq(false));
|
||||
Assert.assertNotNull(smartActionsFuture);
|
||||
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
||||
Assert.assertEquals(Collections.emptyList(), smartActions);
|
||||
}
|
||||
|
||||
// Tests for a hardware bitmap, ScreenshotNotificationSmartActionsProvider is invoked once.
|
||||
@Test
|
||||
public void testScreenshotNotificationSmartActionsProviderInvokedOnce() {
|
||||
Bitmap bitmap = mock(Bitmap.class);
|
||||
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
|
||||
GlobalScreenshot.getSmartActionsFuture(mContext, bitmap, mSmartActionsProvider,
|
||||
mHandler, true, true);
|
||||
verify(mSmartActionsProvider, times(1))
|
||||
.getActions(any(), any(), any(), any(), any(), eq(true));
|
||||
}
|
||||
|
||||
// Tests for a hardware bitmap, a completed future is returned.
|
||||
@Test
|
||||
public void testSupportedBitmapConfiguration()
|
||||
throws Exception {
|
||||
Bitmap bitmap = mock(Bitmap.class);
|
||||
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
|
||||
ScreenshotNotificationSmartActionsProvider actionsProvider =
|
||||
SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider();
|
||||
CompletableFuture<List<Notification.Action>> smartActionsFuture =
|
||||
GlobalScreenshot.getSmartActionsFuture(mContext, bitmap,
|
||||
actionsProvider,
|
||||
mHandler, true, true);
|
||||
Assert.assertNotNull(smartActionsFuture);
|
||||
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
|
||||
Assert.assertEquals(smartActions.size(), 0);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.server.contentsuggestions;
|
||||
|
||||
import static android.Manifest.permission.BIND_CONTENT_SUGGESTIONS_SERVICE;
|
||||
import static android.Manifest.permission.MANAGE_CONTENT_SUGGESTIONS;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
@@ -96,7 +95,7 @@ public class ContentSuggestionsManagerService extends
|
||||
|
||||
private void enforceCaller(int userId, String func) {
|
||||
Context ctx = getContext();
|
||||
if (ctx.checkCallingPermission(BIND_CONTENT_SUGGESTIONS_SERVICE) == PERMISSION_GRANTED
|
||||
if (ctx.checkCallingPermission(MANAGE_CONTENT_SUGGESTIONS) == PERMISSION_GRANTED
|
||||
|| mServiceNameResolver.isTemporary(userId)
|
||||
|| mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
|
||||
return;
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.contentsuggestions.ClassificationsRequest;
|
||||
import android.app.contentsuggestions.ContentSuggestionsManager;
|
||||
import android.app.contentsuggestions.IClassificationsCallback;
|
||||
import android.app.contentsuggestions.ISelectionsCallback;
|
||||
import android.app.contentsuggestions.SelectionsRequest;
|
||||
@@ -97,15 +98,19 @@ public final class ContentSuggestionsPerUserService extends
|
||||
void provideContextImageLocked(int taskId, @NonNull Bundle imageContextRequestExtras) {
|
||||
RemoteContentSuggestionsService service = ensureRemoteServiceLocked();
|
||||
if (service != null) {
|
||||
ActivityManager.TaskSnapshot snapshot =
|
||||
mActivityTaskManagerInternal.getTaskSnapshotNoRestore(taskId, false);
|
||||
GraphicBuffer snapshotBuffer = null;
|
||||
int colorSpaceId = 0;
|
||||
if (snapshot != null) {
|
||||
snapshotBuffer = snapshot.getSnapshot();
|
||||
ColorSpace colorSpace = snapshot.getColorSpace();
|
||||
if (colorSpace != null) {
|
||||
colorSpaceId = colorSpace.getId();
|
||||
|
||||
// Skip taking TaskSnapshot when bitmap is provided.
|
||||
if (!imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) {
|
||||
ActivityManager.TaskSnapshot snapshot =
|
||||
mActivityTaskManagerInternal.getTaskSnapshotNoRestore(taskId, false);
|
||||
if (snapshot != null) {
|
||||
snapshotBuffer = snapshot.getSnapshot();
|
||||
ColorSpace colorSpace = snapshot.getColorSpace();
|
||||
if (colorSpace != null) {
|
||||
colorSpaceId = colorSpace.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ android_test {
|
||||
"services.appwidget",
|
||||
"services.autofill",
|
||||
"services.backup",
|
||||
"services.contentsuggestions",
|
||||
"services.core",
|
||||
"services.devicepolicy",
|
||||
"services.net",
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.server.contentsuggestions;
|
||||
|
||||
import static androidx.test.InstrumentationRegistry.getContext;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.contentsuggestions.ContentSuggestionsManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserManagerInternal;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.wm.ActivityTaskManagerInternal;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class ContentSuggestionsPerUserServiceTest {
|
||||
private int mUserId;
|
||||
private ContentSuggestionsPerUserService mPerUserService;
|
||||
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
UserManagerInternal umi = mock(UserManagerInternal.class);
|
||||
LocalServices.removeServiceForTest(UserManagerInternal.class);
|
||||
LocalServices.addService(UserManagerInternal.class, umi);
|
||||
|
||||
mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
|
||||
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
|
||||
LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
|
||||
|
||||
ContentSuggestionsManagerService contentSuggestionsManagerService =
|
||||
new ContentSuggestionsManagerService(getContext());
|
||||
mUserId = 1;
|
||||
mPerUserService = new ContentSuggestionsPerUserService(contentSuggestionsManagerService,
|
||||
new Object(),
|
||||
mUserId);
|
||||
}
|
||||
|
||||
// Tests TaskSnapshot is taken when the key ContentSuggestionsManager.EXTRA_BITMAP is missing
|
||||
// from imageContextRequestExtras provided.
|
||||
@Test
|
||||
public void testProvideContextImageLocked_noBitmapInBundle() {
|
||||
Bundle imageContextRequestExtras = Bundle.EMPTY;
|
||||
mPerUserService.provideContextImageLocked(mUserId, imageContextRequestExtras);
|
||||
verify(mActivityTaskManagerInternal, times(1)).getTaskSnapshotNoRestore(anyInt(),
|
||||
anyBoolean());
|
||||
}
|
||||
|
||||
// Tests TaskSnapshot is not taken when the key ContentSuggestionsManager.EXTRA_BITMAP is
|
||||
// provided in imageContextRequestExtras.
|
||||
@Test
|
||||
public void testProvideContextImageLocked_bitmapInBundle() {
|
||||
Bundle imageContextRequestExtras = new Bundle();
|
||||
imageContextRequestExtras.putParcelable(ContentSuggestionsManager.EXTRA_BITMAP,
|
||||
Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
|
||||
mPerUserService.provideContextImageLocked(mUserId, imageContextRequestExtras);
|
||||
verify(mActivityTaskManagerInternal, times(0))
|
||||
.getTaskSnapshotNoRestore(anyInt(), anyBoolean());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user