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)
+ }
+}