From 53b4314ad9b43b1890cbd765b896ccb0f005fdeb Mon Sep 17 00:00:00 2001 From: Kang Li Date: Mon, 14 Nov 2016 14:38:25 -0800 Subject: [PATCH] Add Sharing Histories to UsageStatsManager. This CL is for Android O Smart-Sharing (b/30982298). By this CL, sharing counts are logged with UsageStatsManager. Bug: 30982298 Test: manual - shared images in Camera and texts in Chrome using a mobile device. Change-Id: I0b4aa0506f99b3083d140a48f7b4bdd5b1c5afb6 --- .../android/app/usage/IUsageStatsManager.aidl | 2 + core/java/android/app/usage/UsageEvents.java | 40 +++++++++ core/java/android/app/usage/UsageStats.java | 62 +++++++++++++ .../android/app/usage/UsageStatsManager.java | 19 ++++ .../android/internal/app/ChooserActivity.java | 36 +++++++- .../internal/app/ResolverActivity.java | 4 +- .../internal/app/ResolverComparator.java | 36 +++++++- .../android/server/usage/IntervalStats.java | 17 ++++ .../server/usage/UsageStatsDatabase.java | 44 +++++++++ .../server/usage/UsageStatsService.java | 25 ++++++ .../android/server/usage/UsageStatsXmlV1.java | 90 ++++++++++++++++++- .../server/usage/UserUsageStatsService.java | 37 ++++++++ 12 files changed, 404 insertions(+), 8 deletions(-) diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index 342c2858ab0f8..31b235977a049 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -34,4 +34,6 @@ interface IUsageStatsManager { boolean isAppInactive(String packageName, int userId); void whitelistAppTemporarily(String packageName, long duration, int userId); void onCarrierPrivilegedAppsChanged(); + void reportChooserSelection(String packageName, int userId, String contentType, + in String[] annotations, String action); } diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index a0da258bdf1cd..ce8b05abf365b 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -85,6 +85,12 @@ public final class UsageEvents implements Parcelable { */ public static final int SHORTCUT_INVOCATION = 8; + /** + * An event type denoting that a package was selected by the user for ChooserActivity. + * @hide + */ + public static final int CHOOSER_ACTION = 9; + /** * {@hide} */ @@ -118,6 +124,27 @@ public final class UsageEvents implements Parcelable { */ public String mShortcutId; + /** + * Action type passed to ChooserActivity + * Only present for {@link #CHOOSER_ACTION} event types. + * {@hide} + */ + public String mAction; + + /** + * Content type passed to ChooserActivity. + * Only present for {@link #CHOOSER_ACTION} event types. + * {@hide} + */ + public String mContentType; + + /** + * Content annotations passed to ChooserActivity. + * Only present for {@link #CHOOSER_ACTION} event types. + * {@hide} + */ + public String[] mContentAnnotations; + /** * The package name of the source of this event. */ @@ -307,6 +334,11 @@ public final class UsageEvents implements Parcelable { case Event.SHORTCUT_INVOCATION: p.writeString(event.mShortcutId); break; + case Event.CHOOSER_ACTION: + p.writeString(event.mAction); + p.writeString(event.mContentType); + p.writeStringArray(event.mContentAnnotations); + break; } } @@ -333,6 +365,9 @@ public final class UsageEvents implements Parcelable { // Fill out the event-dependant fields. eventOut.mConfiguration = null; eventOut.mShortcutId = null; + eventOut.mAction = null; + eventOut.mContentType = null; + eventOut.mContentAnnotations = null; switch (eventOut.mEventType) { case Event.CONFIGURATION_CHANGE: @@ -342,6 +377,11 @@ public final class UsageEvents implements Parcelable { case Event.SHORTCUT_INVOCATION: eventOut.mShortcutId = p.readString(); break; + case Event.CHOOSER_ACTION: + eventOut.mAction = p.readString(); + eventOut.mContentType = p.readString(); + eventOut.mContentAnnotations = p.createStringArray(); + break; } } diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java index 2937ccc879a57..57f18f1a0fc0d 100644 --- a/core/java/android/app/usage/UsageStats.java +++ b/core/java/android/app/usage/UsageStats.java @@ -16,8 +16,10 @@ package android.app.usage; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArrayMap; /** * Contains usage statistics for an app package for a specific @@ -61,6 +63,11 @@ public final class UsageStats implements Parcelable { */ public int mLastEvent; + /** + * {@hide} + */ + public ArrayMap> mChooserCounts; + /** * {@hide} */ @@ -75,6 +82,7 @@ public final class UsageStats implements Parcelable { mTotalTimeInForeground = stats.mTotalTimeInForeground; mLaunchCount = stats.mLaunchCount; mLastEvent = stats.mLastEvent; + mChooserCounts = stats.mChooserCounts; } public String getPackageName() { @@ -142,6 +150,26 @@ public final class UsageStats implements Parcelable { mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp); mTotalTimeInForeground += right.mTotalTimeInForeground; mLaunchCount += right.mLaunchCount; + if (mChooserCounts == null) { + mChooserCounts = right.mChooserCounts; + } else if (right.mChooserCounts != null) { + final int chooserCountsSize = right.mChooserCounts.size(); + for (int i = 0; i < chooserCountsSize; i++) { + String action = right.mChooserCounts.keyAt(i); + ArrayMap counts = right.mChooserCounts.valueAt(i); + if (!mChooserCounts.containsKey(action) || mChooserCounts.get(action) == null) { + mChooserCounts.put(action, counts); + continue; + } + final int annotationSize = counts.size(); + for (int j = 0; j < annotationSize; j++) { + String key = counts.keyAt(j); + int rightValue = counts.valueAt(j); + int leftValue = mChooserCounts.get(action).getOrDefault(key, 0); + mChooserCounts.get(action).put(key, leftValue + rightValue); + } + } + } } @Override @@ -158,6 +186,21 @@ public final class UsageStats implements Parcelable { dest.writeLong(mTotalTimeInForeground); dest.writeInt(mLaunchCount); dest.writeInt(mLastEvent); + Bundle allCounts = new Bundle(); + if (mChooserCounts != null) { + final int chooserCountSize = mChooserCounts.size(); + for (int i = 0; i < chooserCountSize; i++) { + String action = mChooserCounts.keyAt(i); + ArrayMap counts = mChooserCounts.valueAt(i); + Bundle currentCounts = new Bundle(); + final int annotationSize = counts.size(); + for (int j = 0; j < annotationSize; j++) { + currentCounts.putInt(counts.keyAt(j), counts.valueAt(j)); + } + allCounts.putBundle(action, currentCounts); + } + } + dest.writeBundle(allCounts); } public static final Creator CREATOR = new Creator() { @@ -171,6 +214,25 @@ public final class UsageStats implements Parcelable { stats.mTotalTimeInForeground = in.readLong(); stats.mLaunchCount = in.readInt(); stats.mLastEvent = in.readInt(); + Bundle allCounts = in.readBundle(); + if (allCounts != null) { + stats.mChooserCounts = new ArrayMap<>(); + for (String action : allCounts.keySet()) { + if (!stats.mChooserCounts.containsKey(action)) { + ArrayMap newCounts = new ArrayMap<>(); + stats.mChooserCounts.put(action, newCounts); + } + Bundle currentCounts = allCounts.getBundle(action); + if (currentCounts != null) { + for (String key : currentCounts.keySet()) { + int value = currentCounts.getInt(key); + if (value > 0) { + stats.mChooserCounts.get(action).put(key, value); + } + } + } + } + } return stats; } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 2aeecfabd48c7..75a4a535186e8 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -278,4 +278,23 @@ public final class UsageStatsManager { } catch (RemoteException re) { } } + + /** + * Reports a Chooser action to the UsageStatsManager. + * + * @param packageName The package name of the app that is selected. + * @param userId The user id of who makes the selection. + * @param contentType The type of the content, e.g., Image, Video, App. + * @param annotations The annotations of the content, e.g., Game, Selfie. + * @param action The action type of Intent that invokes ChooserActivity. + * {@link UsageEvents} + * @hide + */ + public void reportChooserSelection(String packageName, int userId, String contentType, + String[] annotations, String action) { + try { + mService.reportChooserSelection(packageName, userId, contentType, annotations, action); + } catch (RemoteException re) { + } + } } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index d8f7907524ee5..6acd1bfaede95 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -19,6 +19,7 @@ package com.android.internal.app; import android.animation.ObjectAnimator; import android.annotation.NonNull; import android.app.Activity; +import android.app.usage.UsageStatsManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -398,6 +399,7 @@ public class ChooserActivity extends ResolverActivity { } } } + updateChooserCounts(target, mContentType); return super.onTargetSelected(target, alwaysCheck); } @@ -542,20 +544,46 @@ public class ChooserActivity extends ResolverActivity { // Do nothing. We'll send the voice stuff ourselves. } + void updateChooserCounts(TargetInfo info, String annotation) { + 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(); + if (ri != null && ri.activityInfo != null) { + usageStatsManager.reportChooserSelection(ri.activityInfo.packageName, getUserId(), + annotation, null, info.getResolvedIntent().getAction()); + if (DEBUG) { + Log.d(TAG, "ResolveInfo Package is" + ri.activityInfo.packageName); + } + } else if(DEBUG) { + Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo"); + } + } + } + void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) { if (mRefinementResultReceiver != null) { mRefinementResultReceiver.destroy(); mRefinementResultReceiver = null; } - if (selectedTarget == null) { Log.e(TAG, "Refinement result intent did not match any known targets; canceling"); } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) { Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget + " cannot match refined source intent " + matchingIntent); - } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) { - finish(); - return; + } else { + TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0); + if (super.onTargetSelected(clonedTarget, false)) { + updateChooserCounts(clonedTarget, mContentType); + finish(); + return; + } } onRefinementCanceled(); } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index dd8ef180e19cc..c516b5cd7c3ad 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -26,6 +26,7 @@ import android.app.VoiceInteractor.PickOptionRequest.Option; import android.app.VoiceInteractor.Prompt; import android.content.pm.ComponentInfo; import android.os.AsyncTask; +import android.os.RemoteException; import android.provider.MediaStore; import android.provider.Settings; import android.text.TextUtils; @@ -53,7 +54,6 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.PatternMatcher; -import android.os.RemoteException; import android.os.StrictMode; import android.os.UserHandle; import android.os.UserManager; @@ -114,6 +114,7 @@ public class ResolverActivity extends Activity { private ComponentName[] mFilteredComponents; protected ResolverDrawerLayout mResolverDrawerLayout; + protected String mContentType; private boolean mRegistered; private final PackageMonitor mPackageMonitor = new PackageMonitor() { @@ -270,6 +271,7 @@ public class ResolverActivity extends Activity { final String referrerPackage = getReferrerPackageName(); mResolverComparator = new ResolverComparator(this, getTargetIntent(), referrerPackage); + mContentType = mResolverComparator.mContentType; if (configureContentView(mIntents, initialIntents, rList, alwaysUseOption)) { return; diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java index 4d4c7ceb16550..75be906397a79 100644 --- a/core/java/com/android/internal/app/ResolverComparator.java +++ b/core/java/com/android/internal/app/ResolverComparator.java @@ -63,6 +63,8 @@ class ResolverComparator implements Comparator { private final long mSinceTime; private final LinkedHashMap mScoredTargets = new LinkedHashMap<>(); private final String mReferrerPackage; + public String mContentType; + private String mAction; public ResolverComparator(Context context, Intent intent, String referrerPackage) { mCollator = Collator.getInstance(context.getResources().getConfiguration().locale); @@ -76,6 +78,8 @@ class ResolverComparator implements Comparator { mCurrentTime = System.currentTimeMillis(); mSinceTime = mCurrentTime - USAGE_STATS_PERIOD; mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime); + mContentType = intent.getType(); + mAction = intent.getAction(); } public void compute(List targets) { @@ -86,6 +90,7 @@ class ResolverComparator implements Comparator { long mostRecentlyUsedTime = recentSinceTime + 1; long mostTimeSpent = 1; int mostLaunched = 1; + int mostSelected = 1; for (ResolvedComponentInfo target : targets) { final ScoredTarget scoredTarget @@ -114,6 +119,25 @@ 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 (DEBUG) { + if (mAction == null) { + Log.d(TAG, "Action type is null"); + } else { + Log.d(TAG, "Chooser Count of " + mAction + ":" + + target.name.getPackageName() + " is " + Integer.toString(selected)); + } + } + scoredTarget.chooserCount = selected; + if (selected > mostSelected) { + mostSelected = selected; + } } } @@ -190,7 +214,15 @@ class ResolverComparator implements Comparator { lhs.activityInfo.packageName, lhs.activityInfo.name)); final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName( rhs.activityInfo.packageName, rhs.activityInfo.name)); - final float diff = rhsTarget.score - lhsTarget.score; + + final int chooserCountDiff = Long.compare( + rhsTarget.chooserCount, lhsTarget.chooserCount); + + if (chooserCountDiff != 0) { + return chooserCountDiff > 0 ? 1 : -1; + } + + final int diff = Float.compare(rhsTarget.score, lhsTarget.score); if (diff != 0) { return diff > 0 ? 1 : -1; @@ -220,6 +252,7 @@ class ResolverComparator implements Comparator { public long lastTimeUsed; public long timeSpent; public long launchCount; + public long chooserCount; public ScoredTarget(ComponentInfo ci) { componentInfo = ci; @@ -232,6 +265,7 @@ class ResolverComparator implements Comparator { + " lastTimeUsed: " + lastTimeUsed + " timeSpent: " + timeSpent + " launchCount: " + launchCount + + " chooserCount: " + chooserCount + "}"; } } diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index f541f70d4334e..cb32d1fb97c1b 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -121,6 +121,23 @@ class IntervalStats { endTime = timeStamp; } + void updateChooserCounts(String packageName, String category, String action) { + UsageStats usageStats = getOrCreateUsageStats(packageName); + if (usageStats.mChooserCounts == null) { + usageStats.mChooserCounts = new ArrayMap<>(); + } + ArrayMap chooserCounts; + final int idx = usageStats.mChooserCounts.indexOfKey(action); + if (idx < 0) { + chooserCounts = new ArrayMap<>(); + usageStats.mChooserCounts.put(action, chooserCounts); + } else { + chooserCounts = usageStats.mChooserCounts.valueAt(idx); + } + int currentCount = chooserCounts.getOrDefault(category, 0); + chooserCounts.put(category, currentCount + 1); + } + void updateConfigurationStats(Configuration config, long timeStamp) { if (activeConfiguration != null) { ConfigurationStats activeStats = configurations.get(activeConfiguration); diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index 849262e47045e..1b28db72b9b30 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -17,6 +17,7 @@ package com.android.server.usage; import android.app.usage.TimeSparseArray; +import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.os.Build; import android.util.AtomicFile; @@ -502,6 +503,12 @@ class UsageStatsDatabase { pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY], mCal.getTimeInMillis()); + mCal.setTimeInMillis(currentTimeMillis); + mCal.addDays(-14); + for (int i = 0; i < mIntervalDirs.length; ++i) { + pruneChooserCountsOlderThan(mIntervalDirs[i], mCal.getTimeInMillis()); + } + // We must re-index our file list or we will be trying to read // deleted files. indexFilesLocked(); @@ -531,6 +538,43 @@ class UsageStatsDatabase { } } + private static void pruneChooserCountsOlderThan(File dir, long expiryTime) { + File[] files = dir.listFiles(); + if (files != null) { + for (File f : files) { + String path = f.getPath(); + if (path.endsWith(BAK_SUFFIX)) { + f = new File(path.substring(0, path.length() - BAK_SUFFIX.length())); + } + + long beginTime; + try { + beginTime = UsageStatsXml.parseBeginTime(f); + } catch (IOException e) { + beginTime = 0; + } + + if (beginTime < expiryTime) { + try { + final AtomicFile af = new AtomicFile(f); + final IntervalStats stats = new IntervalStats(); + UsageStatsXml.read(af, stats); + final int pkgCount = stats.packageStats.size(); + for (int i = 0; i < pkgCount; i++) { + UsageStats pkgStats = stats.packageStats.valueAt(i); + if (pkgStats.mChooserCounts != null) { + pkgStats.mChooserCounts.clear(); + } + } + UsageStatsXml.write(af, stats); + } catch (IOException e) { + Slog.e(TAG, "Failed to delete chooser counts from usage stats file", e); + } + } + } + } + } + /** * Update the stats in the database. They may not be written to disk immediately. */ diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 7217c3b973ec8..f247300994b8f 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1379,6 +1379,31 @@ public class UsageStatsService extends SystemService implements } UsageStatsService.this.dump(args, pw); } + + @Override + public void reportChooserSelection(String packageName, int userId, String contentType, + String[] annotations, String action) { + if (packageName == null) { + Slog.w(TAG, "Event report user selecting a null package"); + return; + } + + UsageEvents.Event event = new UsageEvents.Event(); + event.mPackage = packageName; + + // This will later be converted to system time. + event.mTimeStamp = SystemClock.elapsedRealtime(); + + event.mEventType = Event.CHOOSER_ACTION; + + event.mAction = action; + + event.mContentType = contentType; + + event.mContentAnnotations = annotations; + + mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); + } } /** diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index 03cee9c26da9e..96f3305e2538c 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -26,6 +26,7 @@ import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; import android.content.res.Configuration; +import android.util.ArrayMap; import java.io.IOException; import java.net.ProtocolException; @@ -37,6 +38,11 @@ final class UsageStatsXmlV1 { private static final String PACKAGES_TAG = "packages"; private static final String PACKAGE_TAG = "package"; + private static final String CHOOSER_COUNT_TAG = "chosen_action"; + private static final String CATEGORY_TAG = "category"; + private static final String NAME = "name"; + private static final String COUNT = "count"; + private static final String CONFIGURATIONS_TAG = "configurations"; private static final String CONFIG_TAG = "config"; @@ -59,7 +65,7 @@ final class UsageStatsXmlV1 { private static final String TIME_ATTR = "time"; private static void loadUsageStats(XmlPullParser parser, IntervalStats statsOut) - throws IOException { + throws XmlPullParserException, IOException { final String pkg = parser.getAttributeValue(null, PACKAGE_ATTR); if (pkg == null) { throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present"); @@ -72,6 +78,51 @@ final class UsageStatsXmlV1 { parser, LAST_TIME_ACTIVE_ATTR); stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR); stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR); + int eventCode; + while ((eventCode = parser.next()) != XmlPullParser.END_DOCUMENT) { + final String tag = parser.getName(); + if (eventCode == XmlPullParser.END_TAG && tag.equals(PACKAGE_TAG)) { + break; + } + if (eventCode != XmlPullParser.START_TAG) { + continue; + } + if (tag.equals(CHOOSER_COUNT_TAG)) { + String action = XmlUtils.readStringAttribute(parser, NAME); + loadChooserCounts(parser, stats, action); + } + } + } + + private static void loadChooserCounts( + XmlPullParser parser, UsageStats usageStats, String action) + throws XmlPullParserException, IOException { + if (action == null) { + return; + } + if (usageStats.mChooserCounts == null) { + usageStats.mChooserCounts = new ArrayMap<>(); + } + if (!usageStats.mChooserCounts.containsKey(action)) { + ArrayMap counts = new ArrayMap<>(); + usageStats.mChooserCounts.put(action, counts); + } + + int eventCode; + while ((eventCode = parser.next()) != XmlPullParser.END_DOCUMENT) { + final String tag = parser.getName(); + if (eventCode == XmlPullParser.END_TAG && tag.equals(CHOOSER_COUNT_TAG)) { + break; + } + if (eventCode != XmlPullParser.START_TAG) { + continue; + } + if (tag.equals(CATEGORY_TAG)) { + String category = XmlUtils.readStringAttribute(parser, NAME); + int count = XmlUtils.readIntAttribute(parser, COUNT); + usageStats.mChooserCounts.get(action).put(category, count); + } + } } private static void loadConfigStats(XmlPullParser parser, IntervalStats statsOut) @@ -135,10 +186,45 @@ final class UsageStatsXmlV1 { XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName); XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground); XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent); - + writeChooserCounts(xml, usageStats); xml.endTag(null, PACKAGE_TAG); } + private static void writeChooserCounts(XmlSerializer xml, final UsageStats usageStats) + throws IOException { + if (usageStats == null || usageStats.mChooserCounts == null || + usageStats.mChooserCounts.keySet().isEmpty()) { + return; + } + final int chooserCountSize = usageStats.mChooserCounts.size(); + for (int i = 0; i < chooserCountSize; i++) { + final String action = usageStats.mChooserCounts.keyAt(i); + final ArrayMap counts = usageStats.mChooserCounts.valueAt(i); + if (action == null || counts == null || counts.isEmpty()) { + continue; + } + xml.startTag(null, CHOOSER_COUNT_TAG); + XmlUtils.writeStringAttribute(xml, NAME, action); + writeCountsForAction(xml, counts); + xml.endTag(null, CHOOSER_COUNT_TAG); + } + } + + private static void writeCountsForAction(XmlSerializer xml, ArrayMap counts) + throws IOException { + final int countsSize = counts.size(); + for (int i = 0; i < countsSize; i++) { + String key = counts.keyAt(i); + int count = counts.valueAt(i); + if (count > 0) { + xml.startTag(null, CATEGORY_TAG); + XmlUtils.writeStringAttribute(xml, NAME, key); + XmlUtils.writeIntAttribute(xml, COUNT, count); + xml.endTag(null, CATEGORY_TAG); + } + } + } + private static void writeConfigStats(XmlSerializer xml, final IntervalStats stats, final ConfigurationStats configStats, boolean isActive) throws IOException { xml.startTag(null, CONFIG_TAG); diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 59e4c8021bdc5..ba770ef5053ce 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -183,6 +183,15 @@ class UserUsageStatsService { for (IntervalStats stats : mCurrentStats) { if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) { stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); + } else if (event.mEventType == UsageEvents.Event.CHOOSER_ACTION) { + stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction); + 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); + } + } } else { stats.update(event.mPackage, event.mTimeStamp, event.mEventType); } @@ -520,6 +529,32 @@ class UserUsageStatsService { } pw.decreaseIndent(); + pw.println(); + pw.increaseIndent(); + pw.println("ChooserCounts"); + pw.increaseIndent(); + for (UsageStats usageStats : pkgStats.values()) { + pw.printPair("package", usageStats.mPackageName); + if (usageStats.mChooserCounts != null) { + final int chooserCountSize = usageStats.mChooserCounts.size(); + for (int i = 0; i < chooserCountSize; i++) { + final String action = usageStats.mChooserCounts.keyAt(i); + final ArrayMap counts = usageStats.mChooserCounts.valueAt(i); + final int annotationSize = counts.size(); + for (int j = 0; j < annotationSize; j++) { + final String key = counts.keyAt(j); + final int count = counts.valueAt(j); + if (count != 0) { + pw.printPair("ChooserCounts", action + ":" + key + " is " + + Integer.toString(count)); + pw.println(); + } + } + } + } + pw.println(); + } + pw.println("configurations"); pw.increaseIndent(); final ArrayMap configStats = stats.configurations; @@ -593,6 +628,8 @@ class UserUsageStatsService { return "USER_INTERACTION"; case UsageEvents.Event.SHORTCUT_INVOCATION: return "SHORTCUT_INVOCATION"; + case UsageEvents.Event.CHOOSER_ACTION: + return "CHOOSER_ACTION"; default: return "UNKNOWN"; }