From 9fa2a2cd46e11be0ea50ad7be0d3dd2c9273f55a Mon Sep 17 00:00:00 2001 From: Kang Li Date: Fri, 6 Jan 2017 13:33:24 -0800 Subject: [PATCH] Adds annotations to Intent, and enables ChooserActivity to rank apps according to annotations. Test: Unit tests and manul tests. More unit tests to be added. Change-Id: I3cbfd0cc10007290585fa3e7e3c92e1731f1f7b8 --- api/current.txt | 1 + api/system-current.txt | 1 + api/test-current.txt | 1 + core/java/android/content/Intent.java | 46 +++++++++++++++++++ .../android/internal/app/ChooserActivity.java | 19 ++------ .../internal/app/ResolverActivity.java | 4 ++ .../internal/app/ResolverComparator.java | 35 +++++++++++++- .../internal/app/ResolverListController.java | 6 +++ .../internal/app/ChooserActivityTest.java | 10 ++-- .../server/usage/UserUsageStatsService.java | 1 - 10 files changed, 100 insertions(+), 24 deletions(-) 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:

+ * + */ + 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); } }