diff --git a/api/current.txt b/api/current.txt
index ec0faf4950530..ad245b1d30ee7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8955,6 +8955,7 @@ package android.content {
field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+ field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS";
field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 882727969a1d4..5abebfaa0e548 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -9331,6 +9331,7 @@ package android.content {
field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+ field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS";
field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
diff --git a/api/test-current.txt b/api/test-current.txt
index dcc693ad989d3..f539ed62602f4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8980,6 +8980,7 @@ package android.content {
field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+ field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS";
field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b05ceaa8b7232..d8358f9883e8f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3842,6 +3842,52 @@ public class Intent implements Parcelable, Cloneable {
public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER
= "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
+ /**
+ * An {@code ArrayList} of {@code String} annotations describing content for
+ * {@link #ACTION_CHOOSER}.
+ *
+ *
If {@link #EXTRA_CONTENT_ANNOTATIONS} is present in an intent used to start a
+ * {@link #ACTION_CHOOSER} activity, the first three annotations will be used to rank apps.
+ *
+ * Annotations should describe the major components or topics of the content. It is up to
+ * apps initiating {@link #ACTION_CHOOSER} to learn and add annotations. Annotations should be
+ * learned in advance, e.g., when creating or saving content, to avoid increasing latency to
+ * start {@link #ACTION_CHOOSER}. Performance on customized annotations can suffer, if they are
+ * rarely used for {@link #ACTION_CHOOSER} in the past 14 days. Therefore, it is recommended to
+ * use the following annotations when applicable:
+ *
+ * - "product": represents that the topic of the content is mainly about products, e.g.,
+ * health & beauty, and office supplies.
+ * - "emotion": represents that the topic of the content is mainly about emotions, e.g.,
+ * happy, and sad.
+ * - "person": represents that the topic of the content is mainly about persons, e.g.,
+ * face, finger, standing, and walking.
+ * - "child": represents that the topic of the content is mainly about children, e.g.,
+ * child, and baby.
+ * - "selfie": represents that the topic of the content is mainly about selfies.
+ * - "crowd": represents that the topic of the content is mainly about crowds.
+ * - "party": represents that the topic of the content is mainly about parties.
+ * - "animal": represent that the topic of the content is mainly about animals.
+ * - "plant": represents that the topic of the content is mainly about plants, e.g.,
+ * flowers.
+ * - "vacation": represents that the topic of the content is mainly about vacations.
+ * - "fashion": represents that the topic of the content is mainly about fashion, e.g.
+ * sunglasses, jewelry, handbags and clothing.
+ * - "material": represents that the topic of the content is mainly about materials, e.g.,
+ * paper, and silk.
+ * - "vehicle": represents that the topic of the content is mainly about vehicles, like
+ * cars, and boats.
+ * - "document": represents that the topic of the content is mainly about documents, e.g.
+ * posters.
+ * - "design": represents that the topic of the content is mainly about design, e.g. arts
+ * and designs of houses.
+ * - "holiday": represents that the topic of the content is mainly about holidays, e.g.,
+ * Christmas and Thanksgiving.
+ *
+ */
+ public static final String EXTRA_CONTENT_ANNOTATIONS
+ = "android.intent.extra.CONTENT_ANNOTATIONS";
+
/**
* A {@link ResultReceiver} used to return data back to the sender.
*
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 7878611266926..d4baa18f7100e 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -418,7 +418,7 @@ public class ChooserActivity extends ResolverActivity {
}
}
}
- updateChooserCounts(target);
+ updateModelAndChooserCounts(target);
return super.onTargetSelected(target, alwaysCheck);
}
@@ -575,27 +575,18 @@ public class ChooserActivity extends ResolverActivity {
// Do nothing. We'll send the voice stuff ourselves.
}
- void updateChooserCounts(TargetInfo info) {
+ void updateModelAndChooserCounts(TargetInfo info) {
if (info != null) {
- UsageStatsManager usageStatsManager =
- (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
- if (usageStatsManager == null) {
- if (DEBUG) {
- Log.d(TAG, "Can not start UsageStatsManager");
- }
- return;
- }
final ResolveInfo ri = info.getResolveInfo();
Intent targetIntent = getTargetIntent();
if (ri != null && ri.activityInfo != null && targetIntent != null) {
- usageStatsManager.reportChooserSelection(ri.activityInfo.packageName, getUserId(),
- targetIntent.getType(), null, targetIntent.getAction());
if (mAdapter != null) {
mAdapter.updateModel(info.getResolvedComponentName());
+ mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
+ targetIntent.getAction());
}
if (DEBUG) {
Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
- Log.d(TAG, "Annotation to be updated is " + targetIntent.getType());
Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
}
} else if(DEBUG) {
@@ -618,7 +609,7 @@ public class ChooserActivity extends ResolverActivity {
} else {
TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
if (super.onTargetSelected(clonedTarget, false)) {
- updateChooserCounts(clonedTarget);
+ updateModelAndChooserCounts(clonedTarget);
finish();
return;
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 095063099395c..d734d17b2dc96 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1299,6 +1299,10 @@ public class ResolverActivity extends Activity {
mResolverListController.updateModel(componentName);
}
+ public void updateChooserCounts(String packageName, int userId, String action) {
+ mResolverListController.updateChooserCounts(packageName, userId, action);
+ }
+
/**
* Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
* to complete.
diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index 45fad97f6422f..d9ab47e780d9b 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -52,6 +52,8 @@ class ResolverComparator implements Comparator {
private static final boolean DEBUG = false;
+ private static final int NUM_OF_TOP_ANNOTATIONS_TO_USE = 3;
+
// One week
private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7;
@@ -74,7 +76,8 @@ class ResolverComparator implements Comparator {
private final long mSinceTime;
private final LinkedHashMap mScoredTargets = new LinkedHashMap<>();
private final String mReferrerPackage;
- public String mContentType;
+ private String mContentType;
+ private String[] mAnnotations;
private String mAction;
private LogisticRegressionAppRanker mRanker;
@@ -91,10 +94,26 @@ class ResolverComparator implements Comparator {
mSinceTime = mCurrentTime - USAGE_STATS_PERIOD;
mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime);
mContentType = intent.getType();
+ getContentAnnotations(intent);
mAction = intent.getAction();
mRanker = new LogisticRegressionAppRanker(context);
}
+ public void getContentAnnotations(Intent intent) {
+ ArrayList annotations = intent.getStringArrayListExtra(
+ Intent.EXTRA_CONTENT_ANNOTATIONS);
+ if (annotations != null) {
+ int size = annotations.size();
+ if (size > NUM_OF_TOP_ANNOTATIONS_TO_USE) {
+ size = NUM_OF_TOP_ANNOTATIONS_TO_USE;
+ }
+ mAnnotations = new String[size];
+ for (int i = 0; i < size; i++) {
+ mAnnotations[i] = annotations.get(i);
+ }
+ }
+ }
+
public void compute(List targets) {
mScoredTargets.clear();
@@ -132,12 +151,18 @@ class ResolverComparator implements Comparator {
if (launched > mostLaunched) {
mostLaunched = launched;
}
- // TODO(kanlig): get and combine counts of categories.
int selected = 0;
if (pkStats.mChooserCounts != null && mAction != null
&& pkStats.mChooserCounts.get(mAction) != null) {
selected = pkStats.mChooserCounts.get(mAction).getOrDefault(mContentType, 0);
+ if (mAnnotations != null) {
+ final int size = mAnnotations.length;
+ for (int i = 0; i < size; i++) {
+ selected += pkStats.mChooserCounts.get(mAction)
+ .getOrDefault(mAnnotations[i], 0);
+ }
+ }
}
if (DEBUG) {
if (mAction == null) {
@@ -288,6 +313,12 @@ class ResolverComparator implements Comparator {
}
}
+ public void updateChooserCounts(String packageName, int userId, String action) {
+ if (mUsm != null) {
+ mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
+ }
+ }
+
public void updateModel(ComponentName componentName) {
if (mScoredTargets == null || componentName == null ||
!mScoredTargets.containsKey(componentName)) {
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index d864a310e91be..f88f6f9a07e0b 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -224,4 +224,10 @@ public class ResolverListController {
mResolverComparator.updateModel(componentName);
}
}
+
+ public void updateChooserCounts(String packageName, int userId, String action) {
+ if (mResolverComparator != null) {
+ mResolverComparator.updateChooserCounts(packageName, userId, action);
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 8385b69f091db..aab4698fc49a1 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -124,7 +124,7 @@ public class ChooserActivityTest {
}
@Test
- public void updateChooserCountsAfterUserSelection() throws InterruptedException {
+ public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException {
Intent sendIntent = createSendImageIntent();
List resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -142,19 +142,15 @@ public class ChooserActivityTest {
sOverrides.onSafelyStartCallback = targetInfo -> {
return true;
};
- String action = sendIntent.getAction();
- String annotation = sendIntent.getType();
ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
- String packageName = toChoose.activityInfo.packageName;
- long toChooseCount = getCount(usm, packageName, action, annotation);
onView(withText(toChoose.activityInfo.name))
.perform(click());
waitForIdle();
+ verify(sOverrides.resolverListController, times(1))
+ .updateChooserCounts(Mockito.anyString(), Mockito.anyInt(), Mockito.anyString());
verify(sOverrides.resolverListController, times(1))
.updateModel(toChoose.activityInfo.getComponentName());
assertThat(activity.getIsSelected(), is(true));
- long updatedCount = getCount(usm, packageName, action, annotation);
- assertThat(updatedCount, is(toChooseCount + 1l));
}
@Test
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index ba770ef5053ce..d9f352cbbf7b0 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -188,7 +188,6 @@ class UserUsageStatsService {
String[] annotations = event.mContentAnnotations;
if (annotations != null) {
for (String annotation : annotations) {
- // TODO(kanlig): update with confidences of annotations.
stats.updateChooserCounts(event.mPackage, annotation, event.mAction);
}
}