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
This commit is contained in:
Kang Li
2017-01-06 13:33:24 -08:00
parent 33f78f89ac
commit 9fa2a2cd46
10 changed files with 100 additions and 24 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}.
*
* <p>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.</p>
*
* <p>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:</p>
* <ul>
* <li>"product": represents that the topic of the content is mainly about products, e.g.,
* health & beauty, and office supplies.</li>
* <li>"emotion": represents that the topic of the content is mainly about emotions, e.g.,
* happy, and sad.</li>
* <li>"person": represents that the topic of the content is mainly about persons, e.g.,
* face, finger, standing, and walking.</li>
* <li>"child": represents that the topic of the content is mainly about children, e.g.,
* child, and baby.</li>
* <li>"selfie": represents that the topic of the content is mainly about selfies.</li>
* <li>"crowd": represents that the topic of the content is mainly about crowds.</li>
* <li>"party": represents that the topic of the content is mainly about parties.</li>
* <li>"animal": represent that the topic of the content is mainly about animals.</li>
* <li>"plant": represents that the topic of the content is mainly about plants, e.g.,
* flowers.</li>
* <li>"vacation": represents that the topic of the content is mainly about vacations.</li>
* <li>"fashion": represents that the topic of the content is mainly about fashion, e.g.
* sunglasses, jewelry, handbags and clothing.</li>
* <li>"material": represents that the topic of the content is mainly about materials, e.g.,
* paper, and silk.</li>
* <li>"vehicle": represents that the topic of the content is mainly about vehicles, like
* cars, and boats.</li>
* <li>"document": represents that the topic of the content is mainly about documents, e.g.
* posters.</li>
* <li>"design": represents that the topic of the content is mainly about design, e.g. arts
* and designs of houses.</li>
* <li>"holiday": represents that the topic of the content is mainly about holidays, e.g.,
* Christmas and Thanksgiving.</li>
* </ul>
*/
public static final String EXTRA_CONTENT_ANNOTATIONS
= "android.intent.extra.CONTENT_ANNOTATIONS";
/**
* A {@link ResultReceiver} used to return data back to the sender.
*

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -52,6 +52,8 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
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<ResolvedComponentInfo> {
private final long mSinceTime;
private final LinkedHashMap<ComponentName, ScoredTarget> 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<ResolvedComponentInfo> {
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<String> 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<ResolvedComponentInfo> targets) {
mScoredTargets.clear();
@@ -132,12 +151,18 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
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<ResolvedComponentInfo> {
}
}
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)) {

View File

@@ -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);
}
}
}

View File

@@ -124,7 +124,7 @@ public class ChooserActivityTest {
}
@Test
public void updateChooserCountsAfterUserSelection() throws InterruptedException {
public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException {
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> 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

View File

@@ -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);
}
}