Add callback to ImageActionUtils for unresolved intents

Adding an optional callback to ImageActionUtils.persistBitmapAndStartActivity allows for external handling of the case in which the given intent cannot be resolved. The main use case is for NIU Actions when the user has chosen an assistant that does not support the feature. TaskOverlayFactoryGo provides a callback that shows a dialog in this case.

Various parts of TaskOverlayFactoryGo were also made public for better testability.

Bug: 196125662
Bug: 192406446
Bug: 195681795
Test: m -j RunLauncherGoGoogleRoboTests ROBOTEST_FILTER=TaskOverlayFactoryGoTest
Change-Id: I64f3a1274bc942a64e964dca20bd4245e336ad9d
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
index 6502526..bc38739 100644
--- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -50,7 +50,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.views.ArrowTipView;
@@ -76,7 +75,7 @@
     public static final String ACTIONS_ERROR_CODE = "niu_actions_app_error_code";
     public static final int ERROR_PERMISSIONS_STRUCTURE = 1;
     public static final int ERROR_PERMISSIONS_SCREENSHOT = 2;
-    private static final String NIU_ACTIONS_CONFIRMED = "launcher_go.niu_actions_confirmed";
+    public static final String NIU_ACTIONS_CONFIRMED = "launcher_go.niu_actions_confirmed";
     private static final String ASSIST_SETTINGS_ARGS_BUNDLE = ":settings:show_fragment_args";
     private static final String ASSIST_SETTINGS_ARGS_KEY = ":settings:fragment_args_key";
     private static final String ASSIST_SETTINGS_PREFERENCE_KEY = "default_assist";
@@ -87,10 +86,11 @@
 
     @Retention(SOURCE)
     @IntDef({PRIVACY_CONFIRMATION, ASSISTANT_NOT_SELECTED, ASSISTANT_NOT_SUPPORTED})
-    private @interface DialogType{}
-    private static final int PRIVACY_CONFIRMATION = 0;
-    private static final int ASSISTANT_NOT_SELECTED = 1;
-    private static final int ASSISTANT_NOT_SUPPORTED = 2;
+    @VisibleForTesting
+    public @interface DialogType{}
+    public static final int PRIVACY_CONFIRMATION = 0;
+    public static final int ASSISTANT_NOT_SELECTED = 1;
+    public static final int ASSISTANT_NOT_SUPPORTED = 2;
 
     private AssistContentRequester mContentRequester;
 
@@ -211,7 +211,8 @@
             Intent intent = createNIUIntent(actionType);
             // Only add and send the image if the appropriate permissions are held
             if (mAssistStructurePermitted && mAssistScreenshotPermitted) {
-                mImageApi.shareAsDataWithExplicitIntent(/* crop */ null, intent);
+                mImageApi.shareAsDataWithExplicitIntent(/* crop */ null, intent,
+                        () -> showDialog(actionType, ASSISTANT_NOT_SUPPORTED));
             } else {
                 // If both permissions are disabled, the structure error code takes priority
                 // The user must enable that one before they can enable screenshots
@@ -301,7 +302,6 @@
             mImageApi = imageActionsApi;
         }
 
-        // TODO (b/192406446): Test that these dialogs are shown at the appropriate times
         private void showDialog(String action, @DialogType int type) {
             switch (type) {
                 case PRIVACY_CONFIRMATION:
@@ -334,7 +334,7 @@
                                 int bodyTextID, int button1TextID,
                                 View.OnClickListener button1Callback, int button2TextID,
                                 View.OnClickListener button2Callback) {
-            BaseDraggingActivity activity = BaseActivity.fromContext(getActionsView().getContext());
+            BaseActivity activity = BaseActivity.fromContext(getActionsView().getContext());
             LayoutInflater inflater = LayoutInflater.from(activity);
             View view = inflater.inflate(R.layout.niu_actions_dialog, /* root */ null);
 
@@ -368,6 +368,11 @@
             mDialog.cancel();
         }
 
+        @VisibleForTesting
+        public OverlayDialogGo getDialog() {
+            return mDialog;
+        }
+
         private void onDialogClickSettings(View v) {
             mDialog.dismiss();
 
@@ -401,7 +406,11 @@
         }
     }
 
-    private static final class OverlayDialogGo extends AlertDialog {
+    /**
+     * Basic modal dialog for various user prompts
+     */
+    @VisibleForTesting
+    public static final class OverlayDialogGo extends AlertDialog {
         private final String mAction;
         private final @DialogType int mType;
 
diff --git a/quickstep/src/com/android/quickstep/ImageActionsApi.java b/quickstep/src/com/android/quickstep/ImageActionsApi.java
index 8cb64c2..154848d 100644
--- a/quickstep/src/com/android/quickstep/ImageActionsApi.java
+++ b/quickstep/src/com/android/quickstep/ImageActionsApi.java
@@ -64,20 +64,23 @@
      */
     @UiThread
     public void shareWithExplicitIntent(@Nullable Rect crop, Intent intent) {
-        addImageAndSendIntent(crop, intent, false);
+        addImageAndSendIntent(crop, intent, false, null /* exceptionCallback */);
     }
 
     /**
      * Share the image this api was constructed with using the provided intent. The implementation
      * should set the intent's data field to the URI pointing to the image.
+     * @param exceptionCallback An optional callback to be called when the intent can't be resolved
      */
     @UiThread
-    public void shareAsDataWithExplicitIntent(@Nullable Rect crop, Intent intent) {
-        addImageAndSendIntent(crop, intent, true);
+    public void shareAsDataWithExplicitIntent(@Nullable Rect crop, Intent intent,
+            @Nullable Runnable exceptionCallback) {
+        addImageAndSendIntent(crop, intent, true, exceptionCallback);
     }
 
     @UiThread
-    private void addImageAndSendIntent(@Nullable Rect crop, Intent intent, boolean setData) {
+    private void addImageAndSendIntent(@Nullable Rect crop, Intent intent, boolean setData,
+            @Nullable Runnable exceptionCallback) {
         if (mBitmapSupplier.get() == null) {
             Log.e(TAG, "No snapshot available, not starting share.");
             return;
@@ -92,7 +95,7 @@
                         intentForUri.putExtra(EXTRA_STREAM, uri);
                     }
                     return new Intent[]{intentForUri};
-                }, TAG));
+                }, TAG, exceptionCallback));
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index de7dbd6..51a9915 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -154,6 +154,18 @@
     @WorkerThread
     public static void persistBitmapAndStartActivity(Context context, Bitmap bitmap, Rect crop,
             Intent intent, BiFunction<Uri, Intent, Intent[]> uriToIntentMap, String tag) {
+        persistBitmapAndStartActivity(context, bitmap, crop, intent, uriToIntentMap, tag,
+                (Runnable) null);
+    }
+
+    /**
+     * Starts activity based on given intent created from image uri.
+     * @param exceptionCallback An optional callback to be called when the intent can't be resolved
+     */
+    @WorkerThread
+    public static void persistBitmapAndStartActivity(Context context, Bitmap bitmap, Rect crop,
+            Intent intent, BiFunction<Uri, Intent, Intent[]> uriToIntentMap, String tag,
+            Runnable exceptionCallback) {
         Intent[] intents = uriToIntentMap.apply(getImageUri(bitmap, crop, context, tag), intent);
 
         try {
@@ -165,6 +177,9 @@
             }
         } catch (ActivityNotFoundException e) {
             Log.e(TAG, "No activity found to receive image intent");
+            if (exceptionCallback != null) {
+                exceptionCallback.run();
+            }
         }
     }