Merge "Screenshot Notification Smart Action: AiAi and - Sys UI integration" into qt-qpr1-dev
diff --git a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
index 1bb81b1..1e6ab41 100644
--- a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
+++ b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
@@ -45,6 +45,17 @@
  */
 @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 @@
      * 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 efc8e87..4bcd39f 100644
--- a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
+++ b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
@@ -66,12 +66,17 @@
                 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 40ee511b..d9a78da 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 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 403e894..43fec6b 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 0899d95..4fc6a36 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.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 @@
         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 7bb4103..f7a26a8 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.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.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.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.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.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 @@
     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 @@
         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 @@
         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 @@
 
         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 @@
             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 @@
         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 0000000..fa23bf7
--- /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 0000000..02e5515
--- /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 ecea251c..9cdb58d 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 @@
 
     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 06d9395..7828050 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.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 @@
     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 869913d..3edbd7e 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -22,6 +22,7 @@
         "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 0000000..80cf6ad
--- /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());
+    }
+}
+
+