summaryrefslogtreecommitdiff
path: root/java/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com')
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java33
-rw-r--r--java/src/com/android/intentresolver/ChooserListAdapter.java5
-rw-r--r--java/src/com/android/intentresolver/ShortcutSelectionLogic.java5
-rw-r--r--java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java141
-rw-r--r--java/src/com/android/intentresolver/chooser/TargetInfo.java19
5 files changed, 145 insertions, 58 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index a73424bf..193956f9 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -190,6 +190,11 @@ public class ChooserActivity extends ResolverActivity implements
private static final String IMAGE_EDITOR_SHARED_ELEMENT = "screenshot_preview_image";
+ // TODO: these data structures are for one-time use in shuttling data from where they're
+ // populated in `ShortcutToChooserTargetConverter` to where they're consumed in
+ // `ShortcutSelectionLogic` which packs the appropriate elements into the final `TargetInfo`.
+ // That flow should be refactored so that `ChooserActivity` isn't responsible for holding their
+ // intermediate data, and then these members can be removed.
private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache;
@@ -494,7 +499,8 @@ public class ChooserActivity extends ResolverActivity implements
adapterForUserHandle.addServiceResults(
resultInfo.originalTarget,
resultInfo.resultTargets, msg.arg1,
- mDirectShareShortcutInfoCache);
+ mDirectShareShortcutInfoCache,
+ mDirectShareAppTargetCache);
}
}
}
@@ -1683,7 +1689,8 @@ public class ChooserActivity extends ResolverActivity implements
/* origTarget */ null,
Lists.newArrayList(mCallerChooserTargets),
TARGET_TYPE_DEFAULT,
- /* directShareShortcutInfoCache */ null);
+ /* directShareShortcutInfoCache */ null,
+ /* directShareAppTargetCache */ null);
}
}
@@ -2164,12 +2171,15 @@ public class ChooserActivity extends ResolverActivity implements
List<TargetInfo> surfacedTargetInfo = adapter.getSurfacedTargetInfo();
List<AppTargetId> targetIds = new ArrayList<>();
for (TargetInfo chooserTargetInfo : surfacedTargetInfo) {
- ChooserTarget chooserTarget = chooserTargetInfo.getChooserTarget();
- ComponentName componentName = chooserTarget.getComponentName();
- if (mDirectShareShortcutInfoCache.containsKey(chooserTarget)) {
- String shortcutId = mDirectShareShortcutInfoCache.get(chooserTarget).getId();
+ ShortcutInfo shortcutInfo = chooserTargetInfo.getDirectShareShortcutInfo();
+ if (shortcutInfo != null) {
+ ComponentName componentName =
+ chooserTargetInfo.getChooserTarget().getComponentName();
targetIds.add(new AppTargetId(
- String.format("%s/%s/%s", shortcutId, componentName.flattenToString(),
+ String.format(
+ "%s/%s/%s",
+ shortcutInfo.getId(),
+ componentName.flattenToString(),
SHORTCUT_TARGET)));
}
}
@@ -2186,13 +2196,12 @@ public class ChooserActivity extends ResolverActivity implements
if (directShareAppPredictor == null) {
return;
}
- ChooserTarget chooserTarget = targetInfo.getChooserTarget();
- AppTarget appTarget = null;
- if (mDirectShareAppTargetCache != null) {
- appTarget = mDirectShareAppTargetCache.get(chooserTarget);
+ if (!targetInfo.isChooserTargetInfo()) {
+ return;
}
- // This is a direct share click that was provided by the APS
+ AppTarget appTarget = targetInfo.getDirectShareAppTarget();
if (appTarget != null) {
+ // This is a direct share click that was provided by the APS
directShareAppPredictor.notifyAppTargetEvent(
new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
.setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE)
diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java
index 85b50ab6..25b50625 100644
--- a/java/src/com/android/intentresolver/ChooserListAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserListAdapter.java
@@ -22,6 +22,7 @@ import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_F
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -544,7 +545,8 @@ public class ChooserListAdapter extends ResolverListAdapter {
@Nullable DisplayResolveInfo origTarget,
List<ChooserTarget> targets,
@ChooserActivity.ShareTargetType int targetType,
- Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos) {
+ Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos,
+ Map<ChooserTarget, AppTarget> directShareToAppTargets) {
// Avoid inserting any potentially late results.
if ((mServiceTargets.size() == 1) && mServiceTargets.get(0).isEmptyTargetInfo()) {
return;
@@ -557,6 +559,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
targets,
isShortcutResult,
directShareToShortcutInfos,
+ directShareToAppTargets,
mContext.createContextAsUser(getUserHandle(), 0),
mSelectableTargetInfoCommunicator,
mChooserListCommunicator.getMaxRankedTargets(),
diff --git a/java/src/com/android/intentresolver/ShortcutSelectionLogic.java b/java/src/com/android/intentresolver/ShortcutSelectionLogic.java
index a278baae..39187bdb 100644
--- a/java/src/com/android/intentresolver/ShortcutSelectionLogic.java
+++ b/java/src/com/android/intentresolver/ShortcutSelectionLogic.java
@@ -17,6 +17,7 @@
package com.android.intentresolver;
import android.annotation.Nullable;
+import android.app.prediction.AppTarget;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.service.chooser.ChooserTarget;
@@ -62,6 +63,7 @@ class ShortcutSelectionLogic {
List<ChooserTarget> targets,
boolean isShortcutResult,
Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos,
+ Map<ChooserTarget, AppTarget> directShareToAppTargets,
Context userContext,
SelectableTargetInfoCommunicator mSelectableTargetInfoCommunicator,
int maxRankedTargets,
@@ -105,7 +107,8 @@ class ShortcutSelectionLogic {
target,
targetScore,
mSelectableTargetInfoCommunicator,
- shortcutInfo),
+ shortcutInfo,
+ directShareToAppTargets.get(target)),
maxRankedTargets,
serviceTargets);
diff --git a/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java b/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java
index 506faa9d..3a3c3e64 100644
--- a/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java
+++ b/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java
@@ -18,6 +18,7 @@ package com.android.intentresolver.chooser;
import android.annotation.Nullable;
import android.app.Activity;
+import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -61,17 +62,23 @@ public final class SelectableTargetInfo extends ChooserTargetInfo {
private final String mDisplayLabel;
private final PackageManager mPm;
private final SelectableTargetInfoCommunicator mSelectableTargetInfoCommunicator;
- @GuardedBy("this")
- private ShortcutInfo mShortcutInfo;
- private Drawable mBadgeIcon = null;
- private CharSequence mBadgeContentDescription;
- @GuardedBy("this")
- private Drawable mDisplayIcon;
+ @Nullable
+ private final AppTarget mAppTarget;
+ @Nullable
+ private final ShortcutInfo mShortcutInfo;
private final Intent mFillInIntent;
private final int mFillInFlags;
private final boolean mIsPinned;
private final float mModifiedScore;
- private boolean mIsSuspended = false;
+ private final boolean mIsSuspended;
+ private final Drawable mBadgeIcon;
+ private final CharSequence mBadgeContentDescription;
+
+ @GuardedBy("this")
+ private Drawable mDisplayIcon;
+
+ @GuardedBy("this")
+ private boolean mHasAttemptedIconLoad;
/** Create a new {@link TargetInfo} instance representing a selectable target. */
public static TargetInfo newSelectableTargetInfo(
@@ -80,14 +87,16 @@ public final class SelectableTargetInfo extends ChooserTargetInfo {
ChooserTarget chooserTarget,
float modifiedScore,
SelectableTargetInfoCommunicator selectableTargetInfoCommunicator,
- @Nullable ShortcutInfo shortcutInfo) {
+ @Nullable ShortcutInfo shortcutInfo,
+ @Nullable AppTarget appTarget) {
return new SelectableTargetInfo(
context,
sourceInfo,
chooserTarget,
modifiedScore,
selectableTargetInfoCommunicator,
- shortcutInfo);
+ shortcutInfo,
+ appTarget);
}
private SelectableTargetInfo(
@@ -96,7 +105,8 @@ public final class SelectableTargetInfo extends ChooserTargetInfo {
ChooserTarget chooserTarget,
float modifiedScore,
SelectableTargetInfoCommunicator selectableTargetInfoComunicator,
- @Nullable ShortcutInfo shortcutInfo) {
+ @Nullable ShortcutInfo shortcutInfo,
+ @Nullable AppTarget appTarget) {
mContext = context;
mSourceInfo = sourceInfo;
mChooserTarget = chooserTarget;
@@ -104,20 +114,17 @@ public final class SelectableTargetInfo extends ChooserTargetInfo {
mPm = mContext.getPackageManager();
mSelectableTargetInfoCommunicator = selectableTargetInfoComunicator;
mShortcutInfo = shortcutInfo;
+ mAppTarget = appTarget;
mIsPinned = shortcutInfo != null && shortcutInfo.isPinned();
- if (sourceInfo != null) {
- final ResolveInfo ri = sourceInfo.getResolveInfo();
- if (ri != null) {
- final ActivityInfo ai = ri.activityInfo;
- if (ai != null && ai.applicationInfo != null) {
- final PackageManager pm = mContext.getPackageManager();
- mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
- mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
- mIsSuspended =
- (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
- }
- }
- }
+
+ final PackageManager pm = mContext.getPackageManager();
+ final ApplicationInfo applicationInfo = getApplicationInfoFromSource(sourceInfo);
+
+ mBadgeIcon = (applicationInfo == null) ? null : pm.getApplicationIcon(applicationInfo);
+ mBadgeContentDescription =
+ (applicationInfo == null) ? null : pm.getApplicationLabel(applicationInfo);
+ mIsSuspended = (applicationInfo != null)
+ && ((applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0);
if (sourceInfo != null) {
mBackupResolveInfo = null;
@@ -141,9 +148,12 @@ public final class SelectableTargetInfo extends ChooserTargetInfo {
mChooserTarget = other.mChooserTarget;
mBadgeIcon = other.mBadgeIcon;
mBadgeContentDescription = other.mBadgeContentDescription;
+ mShortcutInfo = other.mShortcutInfo;
+ mAppTarget = other.mAppTarget;
+ mIsSuspended = other.mIsSuspended;
synchronized (other) {
- mShortcutInfo = other.mShortcutInfo;
mDisplayIcon = other.mDisplayIcon;
+ mHasAttemptedIconLoad = other.mHasAttemptedIconLoad;
}
mFillInIntent = fillInIntent;
mFillInFlags = flags;
@@ -158,12 +168,6 @@ public final class SelectableTargetInfo extends ChooserTargetInfo {
return true;
}
- private String sanitizeDisplayLabel(CharSequence label) {
- SpannableStringBuilder sb = new SpannableStringBuilder(label);
- sb.clearSpans();
- return sb.toString();
- }
-
@Override
public boolean isSuspended() {
return mIsSuspended;
@@ -180,24 +184,35 @@ public final class SelectableTargetInfo extends ChooserTargetInfo {
*/
@Override
public boolean loadIcon() {
- ShortcutInfo shortcutInfo;
- Drawable icon;
synchronized (this) {
- shortcutInfo = mShortcutInfo;
- icon = mDisplayIcon;
- }
- boolean shouldLoadIcon = (icon == null) && (shortcutInfo != null);
- if (shouldLoadIcon) {
- icon = getChooserTargetIconDrawable(mChooserTarget, shortcutInfo);
- if (icon == null) {
+ // TODO: evaluating these conditions while `synchronized` ensures that we get consistent
+ // reads between `mDisplayIcon` and `mHasAttemptedIconLoad`, but doesn't otherwise
+ // prevent races where two threads might check the conditions (in synchrony) and then
+ // both go on to load the icon (in parallel, even though one of the loads would be
+ // redundant, and even though we have no logic to decide which result to keep if they
+ // differ). This is probably a "safe optimization" in some cases, but our correctness
+ // can't rely on this eliding the duplicate load, and with a more careful design we
+ // could probably optimize it out in more cases (or else maybe we should get rid of
+ // this complexity altogether).
+ if ((mDisplayIcon != null) || (mShortcutInfo == null) || mHasAttemptedIconLoad) {
return false;
}
- synchronized (this) {
- mDisplayIcon = icon;
- mShortcutInfo = null;
- }
}
- return shouldLoadIcon;
+
+ Drawable icon = getChooserTargetIconDrawable(mChooserTarget, mShortcutInfo);
+ if (icon == null) {
+ return false;
+ }
+
+ synchronized (this) {
+ mDisplayIcon = icon;
+ // TODO: we only end up setting `mHasAttemptedIconLoad` if we were successful in loading
+ // a (non-null) display icon; in that case, our guard clause above will already
+ // early-return `false` regardless of `mHasAttemptedIconLoad`. This should be refined,
+ // or removed if we don't need the extra complexity (including the synchronizaiton?).
+ mHasAttemptedIconLoad = true;
+ }
+ return true;
}
private Drawable getChooserTargetIconDrawable(ChooserTarget target,
@@ -349,6 +364,18 @@ public final class SelectableTargetInfo extends ChooserTargetInfo {
}
@Override
+ @Nullable
+ public ShortcutInfo getDirectShareShortcutInfo() {
+ return mShortcutInfo;
+ }
+
+ @Override
+ @Nullable
+ public AppTarget getDirectShareAppTarget() {
+ return mAppTarget;
+ }
+
+ @Override
public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
return new SelectableTargetInfo(this, fillInIntent, flags);
}
@@ -368,6 +395,32 @@ public final class SelectableTargetInfo extends ChooserTargetInfo {
return mIsPinned;
}
+ @Nullable
+ private static ApplicationInfo getApplicationInfoFromSource(
+ @Nullable DisplayResolveInfo sourceInfo) {
+ if (sourceInfo == null) {
+ return null;
+ }
+
+ final ResolveInfo resolveInfo = sourceInfo.getResolveInfo();
+ if (resolveInfo == null) {
+ return null;
+ }
+
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ return null;
+ }
+
+ return activityInfo.applicationInfo;
+ }
+
+ private static String sanitizeDisplayLabel(CharSequence label) {
+ SpannableStringBuilder sb = new SpannableStringBuilder(label);
+ sb.clearSpans();
+ return sb.toString();
+ }
+
/**
* Necessary methods to communicate between {@link SelectableTargetInfo}
* and {@link ResolverActivity} or {@link ChooserActivity}.
diff --git a/java/src/com/android/intentresolver/chooser/TargetInfo.java b/java/src/com/android/intentresolver/chooser/TargetInfo.java
index bde2fcf0..29c4cfc3 100644
--- a/java/src/com/android/intentresolver/chooser/TargetInfo.java
+++ b/java/src/com/android/intentresolver/chooser/TargetInfo.java
@@ -19,10 +19,12 @@ package com.android.intentresolver.chooser;
import android.annotation.Nullable;
import android.app.Activity;
+import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
@@ -202,6 +204,23 @@ public interface TargetInfo {
}
/**
+ * @return the {@link ShortcutManager} data for any shortcut associated with this target.
+ */
+ @Nullable
+ default ShortcutInfo getDirectShareShortcutInfo() {
+ return null;
+ }
+
+ /**
+ * @return the {@link AppTarget} metadata if this target was sourced from App Prediction
+ * service, or null otherwise.
+ */
+ @Nullable
+ default AppTarget getDirectShareAppTarget() {
+ return null;
+ }
+
+ /**
* Attempt to load the display icon, if we have the info for one but it hasn't been loaded yet.
* @return true if an icon may have been loaded as the result of this operation, potentially
* prompting a UI refresh. If this returns false, clients can safely assume there was no change.