Delegate AppPredictor creation to a factory class

A scaffolding chnage that delegates AppPredictor instance creation to a
new factory class.
As we always create an AppPredictor instance for each available
profile, the creation logic is changed from lazy to eager.
As a collateral change, remvoe some obsolete flags.

Test: manual test
Test: atest IntentResolverUnitTests

Change-Id: I0cce89bce1fb39d39792263a3a490a074304afe3
diff --git a/Android.bp b/Android.bp
index c2866f8..c2620c4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,6 +36,7 @@
     min_sdk_version: "current",
     srcs: [
         "java/src/**/*.java",
+        "java/src/**/*.kt",
     ],
     resource_dirs: [
         "java/res",
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index 63ab20c..92dd5a0 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -32,8 +32,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.SharedElementCallback;
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionManager;
 import android.app.prediction.AppPredictor;
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
@@ -92,6 +90,7 @@
 import android.util.PluralsMessageFormatter;
 import android.util.Size;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.MeasureSpec;
@@ -120,6 +119,7 @@
 import com.android.intentresolver.chooser.MultiDisplayResolveInfo;
 import com.android.intentresolver.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
 import com.android.intentresolver.chooser.TargetInfo;
+import com.android.intentresolver.shortcuts.AppPredictorFactory;
 import com.android.intentresolver.widget.ResolverDrawerLayout;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -155,8 +155,6 @@
         SelectableTargetInfoCommunicator {
     private static final String TAG = "ChooserActivity";
 
-    private AppPredictor mPersonalAppPredictor;
-    private AppPredictor mWorkAppPredictor;
     private boolean mShouldDisplayLandscape;
 
     public ChooserActivity() {
@@ -184,22 +182,14 @@
 
     private static final boolean DEBUG = true;
 
-    private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
-    // TODO(b/123088566) Share these in a better way.
-    private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
     public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share";
-    public static final String CHOOSER_TARGET = "chooser_target";
     private static final String SHORTCUT_TARGET = "shortcut_target";
-    private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
-    public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
-    private static final String SHARED_TEXT_KEY = "shared_text";
 
     private static final String PLURALS_COUNT = "count";
     private static final String PLURALS_FILE_NAME = "file_name";
 
     private static final String IMAGE_EDITOR_SHARED_ELEMENT = "screenshot_preview_image";
 
-    private boolean mIsAppPredictorComponentAvailable;
     private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
     private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache;
 
@@ -315,8 +305,9 @@
 
     private View mContentView = null;
 
-    private ShortcutToChooserTargetConverter mShortcutToChooserTargetConverter =
+    private final ShortcutToChooserTargetConverter mShortcutToChooserTargetConverter =
             new ShortcutToChooserTargetConverter();
+    private final SparseArray<AppPredictor> mProfileAppPredictors = new SparseArray<>();
 
     private class ContentPreviewCoordinator {
         private static final int IMAGE_FADE_IN_MILLIS = 150;
@@ -529,8 +520,6 @@
         mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET);
 
         getChooserActivityLogger().logSharesheetTriggered();
-        // This is the only place this value is being set. Effectively final.
-        mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
 
         mIsSuccessfullySelected = false;
         Intent intent = getIntent();
@@ -663,6 +652,11 @@
         mShouldDisplayLandscape =
                 shouldDisplayLandscape(getResources().getConfiguration().orientation);
         setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
+        createAppPredictors(
+                new AppPredictorFactory(
+                        this,
+                        target.getStringExtra(Intent.EXTRA_TEXT),
+                        getTargetIntentFilter(target)));
         super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
                 null, false);
 
@@ -736,9 +730,25 @@
         return R.style.Theme_DeviceDefault_Chooser;
     }
 
+    private void createAppPredictors(AppPredictorFactory factory) {
+        UserHandle mainUserHandle = getPersonalProfileUserHandle();
+        createAppPredictorForProfile(mainUserHandle, factory);
+        UserHandle workUserHandle = getWorkProfileUserHandle();
+        if (workUserHandle != null) {
+            createAppPredictorForProfile(workUserHandle, factory);
+        }
+    }
+
+    private void createAppPredictorForProfile(UserHandle userHandle, AppPredictorFactory factory) {
+        AppPredictor appPredictor = factory.create(userHandle);
+        if (appPredictor != null) {
+            mProfileAppPredictors.put(userHandle.getIdentifier(), appPredictor);
+        }
+    }
+
     private AppPredictor setupAppPredictorForUser(UserHandle userHandle,
             AppPredictor.Callback appPredictorCallback) {
-        AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
+        AppPredictor appPredictor = getAppPredictor(userHandle);
         if (appPredictor == null) {
             return null;
         }
@@ -883,13 +893,6 @@
     }
 
     /**
-     * Returns true if app prediction service is defined and the component exists on device.
-     */
-    private boolean isAppPredictionServiceAvailable() {
-        return getPackageManager().getAppPredictionServicePackageName() != null;
-    }
-
-    /**
      * Check if the profile currently used is a work profile.
      * @return true if it is work profile, false if it is parent profile (or no work profile is
      * set up)
@@ -1626,8 +1629,7 @@
         if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
             mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor();
         }
-        mPersonalAppPredictor = null;
-        mWorkAppPredictor = null;
+        mProfileAppPredictors.clear();
     }
 
     @Override // ResolverListCommunicator
@@ -1935,8 +1937,11 @@
     }
 
     private IntentFilter getTargetIntentFilter() {
+        return getTargetIntentFilter(getTargetIntent());
+    }
+
+    private IntentFilter getTargetIntentFilter(final Intent intent) {
         try {
-            final Intent intent = getTargetIntent();
             String dataString = intent.getDataString();
             if (intent.getType() == null) {
                 if (!TextUtils.isEmpty(dataString)) {
@@ -1976,7 +1981,7 @@
         mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
         UserHandle userHandle = adapter.getUserHandle();
         if (!skipAppPredictionService) {
-            AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
+            AppPredictor appPredictor = getAppPredictor(userHandle);
             if (appPredictor != null) {
                 appPredictor.requestPredictionUpdate();
                 return;
@@ -2146,15 +2151,16 @@
     }
 
     private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
-        AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
-                mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
-        if (directShareAppPredictor == null) {
-            return;
-        }
         // Send DS target impression info to AppPredictor, only when user chooses app share.
         if (targetInfo.isChooserTargetInfo()) {
             return;
         }
+
+        AppPredictor directShareAppPredictor = getAppPredictor(
+                mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
+        if (directShareAppPredictor == null) {
+            return;
+        }
         List<TargetInfo> surfacedTargetInfo = adapter.getSurfacedTargetInfo();
         List<AppTargetId> targetIds = new ArrayList<>();
         for (TargetInfo chooserTargetInfo : surfacedTargetInfo) {
@@ -2171,12 +2177,13 @@
     }
 
     private void sendClickToAppPredictor(TargetInfo targetInfo) {
-        AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
-                mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
-        if (directShareAppPredictor == null) {
+        if (!targetInfo.isChooserTargetInfo()) {
             return;
         }
-        if (!targetInfo.isChooserTargetInfo()) {
+
+        AppPredictor directShareAppPredictor = getAppPredictor(
+                mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
+        if (directShareAppPredictor == null) {
             return;
         }
         ChooserTarget chooserTarget = targetInfo.getChooserTarget();
@@ -2194,70 +2201,8 @@
     }
 
     @Nullable
-    private AppPredictor createAppPredictor(UserHandle userHandle) {
-        if (!mIsAppPredictorComponentAvailable) {
-            return null;
-        }
-
-        if (getPersonalProfileUserHandle().equals(userHandle)) {
-            if (mPersonalAppPredictor != null) {
-                return mPersonalAppPredictor;
-            }
-        } else {
-            if (mWorkAppPredictor != null) {
-                return mWorkAppPredictor;
-            }
-        }
-
-        // TODO(b/148230574): Currently AppPredictor fetches only the same-profile app targets.
-        // Make AppPredictor work cross-profile.
-        Context contextAsUser = createContextAsUser(userHandle, 0 /* flags */);
-        final IntentFilter filter = getTargetIntentFilter();
-        Bundle extras = new Bundle();
-        extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
-        populateTextContent(extras);
-        AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(contextAsUser)
-            .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
-            .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
-            .setExtras(extras)
-            .build();
-        AppPredictionManager appPredictionManager =
-                contextAsUser
-                        .getSystemService(AppPredictionManager.class);
-        AppPredictor appPredictionSession = appPredictionManager.createAppPredictionSession(
-                appPredictionContext);
-        if (getPersonalProfileUserHandle().equals(userHandle)) {
-            mPersonalAppPredictor = appPredictionSession;
-        } else {
-            mWorkAppPredictor = appPredictionSession;
-        }
-        return appPredictionSession;
-    }
-
-    private void populateTextContent(Bundle extras) {
-        final Intent intent = getTargetIntent();
-        String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
-        extras.putString(SHARED_TEXT_KEY, sharedText);
-    }
-
-    /**
-     * This will return an app predictor if it is enabled for direct share sorting
-     * and if one exists. Otherwise, it returns null.
-     * @param userHandle
-     */
-    @Nullable
-    private AppPredictor getAppPredictorForDirectShareIfEnabled(UserHandle userHandle) {
-        return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS
-                && !ActivityManager.isLowRamDeviceStatic() ? createAppPredictor(userHandle) : null;
-    }
-
-    /**
-     * This will return an app predictor if it is enabled for share activity sorting
-     * and if one exists. Otherwise, it returns null.
-     */
-    @Nullable
-    private AppPredictor getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) {
-        return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? createAppPredictor(userHandle) : null;
+    private AppPredictor getAppPredictor(UserHandle userHandle) {
+        return mProfileAppPredictors.get(userHandle.getIdentifier(), null);
     }
 
     void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
@@ -2374,10 +2319,13 @@
         ChooserListAdapter chooserListAdapter = createChooserListAdapter(context, payloadIntents,
                 initialIntents, rList, filterLastUsed,
                 createListController(userHandle));
-        AppPredictor.Callback appPredictorCallback = createAppPredictorCallback(chooserListAdapter);
-        AppPredictor appPredictor = setupAppPredictorForUser(userHandle, appPredictorCallback);
-        chooserListAdapter.setAppPredictor(appPredictor);
-        chooserListAdapter.setAppPredictorCallback(appPredictorCallback);
+        if (!ActivityManager.isLowRamDeviceStatic()) {
+            AppPredictor.Callback appPredictorCallback =
+                    createAppPredictorCallback(chooserListAdapter);
+            AppPredictor appPredictor = setupAppPredictorForUser(userHandle, appPredictorCallback);
+            chooserListAdapter.setAppPredictor(appPredictor);
+            chooserListAdapter.setAppPredictorCallback(appPredictorCallback);
+        }
         return new ChooserGridAdapter(chooserListAdapter);
     }
 
@@ -2393,7 +2341,7 @@
 
     @VisibleForTesting
     protected ResolverListController createListController(UserHandle userHandle) {
-        AppPredictor appPredictor = getAppPredictorForShareActivitiesIfEnabled(userHandle);
+        AppPredictor appPredictor = getAppPredictor(userHandle);
         AbstractResolverComparator resolverComparator;
         if (appPredictor != null) {
             resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
@@ -2681,13 +2629,11 @@
             return;
         }
 
-        if (ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
-            if (DEBUG) {
-                Log.d(TAG, "querying direct share targets from ShortcutManager");
-            }
-
-            queryDirectShareTargets(chooserListAdapter, false);
+        if (DEBUG) {
+            Log.d(TAG, "querying direct share targets from ShortcutManager");
         }
+
+        queryDirectShareTargets(chooserListAdapter, false);
     }
 
     @VisibleForTesting
diff --git a/java/src/com/android/intentresolver/ChooserFlags.java b/java/src/com/android/intentresolver/ChooserFlags.java
deleted file mode 100644
index 67f9046..0000000
--- a/java/src/com/android/intentresolver/ChooserFlags.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver;
-
-import android.app.prediction.AppPredictionManager;
-
-/**
- * Common flags for {@link ChooserListAdapter} and {@link ChooserActivity}.
- */
-public class ChooserFlags {
-
-    /**
-     * Whether to use {@link AppPredictionManager} to query for direct share targets (as opposed to
-     * talking directly to {@link android.content.pm.ShortcutManager}.
-     */
-    // TODO(b/123089490): Replace with system flag
-    static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = true;
-}
-
diff --git a/java/src/com/android/intentresolver/shortcuts/AppPredictorFactory.kt b/java/src/com/android/intentresolver/shortcuts/AppPredictorFactory.kt
new file mode 100644
index 0000000..82f40b9
--- /dev/null
+++ b/java/src/com/android/intentresolver/shortcuts/AppPredictorFactory.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.intentresolver.shortcuts
+
+import android.app.prediction.AppPredictionContext
+import android.app.prediction.AppPredictionManager
+import android.app.prediction.AppPredictor
+import android.content.Context
+import android.content.IntentFilter
+import android.os.Bundle
+import android.os.UserHandle
+
+// TODO(b/123088566) Share these in a better way.
+private const val APP_PREDICTION_SHARE_UI_SURFACE = "share"
+private const val APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20
+private const val APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter"
+private const val SHARED_TEXT_KEY = "shared_text"
+
+/**
+ * A factory to create an AppPredictor instance for a profile, if available.
+ * @param context, application context
+ * @param sharedText, a shared text associated with the Chooser's target intent
+ * (see [android.content.Intent.EXTRA_TEXT]).
+ * Will be mapped to app predictor's "shared_text" parameter.
+ * @param targetIntentFilter, an IntentFilter to match direct share targets against.
+ * Will be mapped app predictor's "intent_filter" parameter.
+ */
+class AppPredictorFactory(
+    private val context: Context,
+    private val sharedText: String?,
+    private val targetIntentFilter: IntentFilter?
+) {
+    private val mIsComponentAvailable =
+        context.packageManager.appPredictionServicePackageName != null
+
+    /**
+     * Creates an AppPredictor instance for a profile or `null` if app predictor is not available.
+     */
+    fun create(userHandle: UserHandle): AppPredictor? {
+        if (!mIsComponentAvailable) return null
+        val contextAsUser = context.createContextAsUser(userHandle, 0 /* flags */)
+        val extras = Bundle().apply {
+            putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, targetIntentFilter)
+            putString(SHARED_TEXT_KEY, sharedText)
+        }
+        val appPredictionContext = AppPredictionContext.Builder(contextAsUser)
+            .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
+            .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
+            .setExtras(extras)
+            .build()
+        return contextAsUser.getSystemService(AppPredictionManager::class.java)
+            ?.createAppPredictionSession(appPredictionContext)
+    }
+}