diff options
12 files changed, 464 insertions, 15 deletions
diff --git a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java index 1bb81b1487af..1e6ab4136187 100644 --- a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java +++ b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java @@ -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( diff --git a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java index efc8e877f3c3..4bcd39fbd41f 100644 --- a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java +++ b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java @@ -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( diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 40ee511b0727..d9a78dadba41 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -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. */ diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 403e894a68e4..43fec6b6805c 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -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" /> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 0899d955a1ac..4fc6a36550dc 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -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, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 11ca94f6f4e6..f3361b884391 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -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). diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java new file mode 100644 index 000000000000..fa23bf7d5bde --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java @@ -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()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java new file mode 100644 index 000000000000..02e5515d3ecc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -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); + } +} diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java index ecea251cc1ac..9cdb58d8c019 100644 --- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java @@ -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; diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java index 06d9395cd7d6..7828050223f4 100644 --- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java @@ -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(); + } } } diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 869913dec646..3edbd7ee6eed 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -22,6 +22,7 @@ android_test { "services.appwidget", "services.autofill", "services.backup", + "services.contentsuggestions", "services.core", "services.devicepolicy", "services.net", diff --git a/services/tests/servicestests/src/com/android/server/contentsuggestions/ContentSuggestionsPerUserServiceTest.java b/services/tests/servicestests/src/com/android/server/contentsuggestions/ContentSuggestionsPerUserServiceTest.java new file mode 100644 index 000000000000..80cf6ad6d88e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/contentsuggestions/ContentSuggestionsPerUserServiceTest.java @@ -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()); + } +} + + |