diff options
author | Song Hu <songhu@google.com> | 2020-05-14 10:12:53 -0700 |
---|---|---|
committer | Song Hu <songhu@google.com> | 2020-05-20 15:37:26 -0700 |
commit | bf9cb7e6e3cfd37618383a2ff2f0c0a81810926a (patch) | |
tree | 44a87da2a3527923e24b148ddd430ab796508c16 /services/people | |
parent | 1a66adcc562bf9c39042a2fc7de5d5660aa7b180 (diff) |
Promote frequently sharing apps as per monthly stats fetched from UsageStatsManager, in case PeopleService does not store enough sharing events when users just swicth to Sharesheet ranking in PeopleService.
Bug: 156320324
Test: atest com.android.server.people.prediction.ShareTargetPredictorTest
Test: atest com.android.server.people.prediction.SharesheetModelScorerTest
Test: atest com.android.server.people.data.UsageStatsQueryHelperTest
Change-Id: Iae7a3a8195ae4901cd1d9d673fe0cf8a27845488
Diffstat (limited to 'services/people')
4 files changed, 137 insertions, 47 deletions
diff --git a/services/people/java/com/android/server/people/data/AppUsageStatsData.java b/services/people/java/com/android/server/people/data/AppUsageStatsData.java new file mode 100644 index 000000000000..6baef38fffc1 --- /dev/null +++ b/services/people/java/com/android/server/people/data/AppUsageStatsData.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.people.data; + +import com.android.internal.annotations.VisibleForTesting; + +/** The data containing package usage info. */ +public class AppUsageStatsData { + + private int mLaunchCount; + + private int mChosenCount; + + @VisibleForTesting + public AppUsageStatsData(int chosenCount, int launchCount) { + this.mChosenCount = chosenCount; + this.mLaunchCount = launchCount; + } + + public AppUsageStatsData() { + } + + public int getLaunchCount() { + return mLaunchCount; + } + + void incrementLaunchCountBy(int launchCount) { + this.mLaunchCount += launchCount; + } + + public int getChosenCount() { + return mChosenCount; + } + + void incrementChosenCountBy(int chosenCount) { + this.mChosenCount += chosenCount; + } +} diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index 107c41a47507..bbb0215788fb 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -257,13 +257,16 @@ public class DataManager { } /** - * Queries launch counts of apps within {@code packageNameFilter} between {@code startTime} - * and {@code endTime}. + * Queries usage stats of apps within {@code packageNameFilter} between {@code startTime} and + * {@code endTime}. + * + * @return a map which keys are package names and values are {@link AppUsageStatsData}. */ @NonNull - public Map<String, Integer> queryAppLaunchCount(@UserIdInt int callingUserId, long startTime, + public Map<String, AppUsageStatsData> queryAppUsageStats( + @UserIdInt int callingUserId, long startTime, long endTime, Set<String> packageNameFilter) { - return UsageStatsQueryHelper.queryAppLaunchCount(callingUserId, startTime, endTime, + return UsageStatsQueryHelper.queryAppUsageStats(callingUserId, startTime, endTime, packageNameFilter); } diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java index 6e6fea93c803..d89bbe9dd14e 100644 --- a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java +++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java @@ -137,27 +137,48 @@ class UsageStatsQueryHelper { } /** - * Queries {@link UsageStatsManagerInternal} for launch count of apps within {@code - * packageNameFilter} between {@code startTime} and {@code endTime}.obfuscateInstantApps + * Queries {@link UsageStatsManagerInternal} for usage stats of apps within {@code + * packageNameFilter} between {@code startTime} and {@code endTime}. * - * @return a map which keys are package names and values are app launch counts. + * @return a map which keys are package names and values are {@link AppUsageStatsData}. */ - static Map<String, Integer> queryAppLaunchCount(@UserIdInt int userId, long startTime, + static Map<String, AppUsageStatsData> queryAppUsageStats(@UserIdInt int userId, long startTime, long endTime, Set<String> packageNameFilter) { List<UsageStats> stats = getUsageStatsManagerInternal().queryUsageStatsForUser(userId, UsageStatsManager.INTERVAL_BEST, startTime, endTime, /* obfuscateInstantApps= */ false); - Map<String, Integer> aggregatedStats = new ArrayMap<>(); + Map<String, AppUsageStatsData> aggregatedStats = new ArrayMap<>(); for (UsageStats stat : stats) { String packageName = stat.getPackageName(); if (packageNameFilter.contains(packageName)) { - aggregatedStats.put(packageName, - aggregatedStats.getOrDefault(packageName, 0) + stat.getAppLaunchCount()); + AppUsageStatsData packageStats = aggregatedStats.computeIfAbsent(packageName, + (key) -> new AppUsageStatsData()); + packageStats.incrementChosenCountBy(sumChooserCounts(stat.mChooserCounts)); + packageStats.incrementLaunchCountBy(stat.getAppLaunchCount()); } } return aggregatedStats; } + private static int sumChooserCounts(ArrayMap<String, ArrayMap<String, Integer>> chooserCounts) { + int sum = 0; + if (chooserCounts == null) { + return sum; + } + int chooserCountsSize = chooserCounts.size(); + for (int i = 0; i < chooserCountsSize; i++) { + ArrayMap<String, Integer> counts = chooserCounts.valueAt(i); + if (counts == null) { + continue; + } + final int annotationSize = counts.size(); + for (int j = 0; j < annotationSize; j++) { + sum += counts.valueAt(j); + } + } + return sum; + } + private void onInAppConversationEnded(@NonNull PackageData packageData, @NonNull UsageEvents.Event endEvent) { ComponentName activityName = diff --git a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java index 76f252efb412..c77843cfb044 100644 --- a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java +++ b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java @@ -27,17 +27,18 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ChooserActivity; +import com.android.server.people.data.AppUsageStatsData; import com.android.server.people.data.DataManager; import com.android.server.people.data.Event; import java.time.Duration; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.concurrent.TimeUnit; +import java.util.function.Function; /** Ranking scorer for Sharesheet targets. */ class SharesheetModelScorer { @@ -50,8 +51,8 @@ class SharesheetModelScorer { private static final float RECENCY_SCORE_SUBSEQUENT_DECAY = 0.02F; private static final long ONE_MONTH_WINDOW = TimeUnit.DAYS.toMillis(30); private static final long FOREGROUND_APP_PROMO_TIME_WINDOW = TimeUnit.MINUTES.toMillis(10); + private static final float USAGE_STATS_CHOOSER_SCORE_INITIAL_DECAY = 0.9F; private static final float FREQUENTLY_USED_APP_SCORE_INITIAL_DECAY = 0.3F; - private static final float FREQUENTLY_USED_APP_SCORE_DECAY = 0.9F; @VisibleForTesting static final float FOREGROUND_APP_WEIGHT = 0F; @VisibleForTesting @@ -192,14 +193,16 @@ class SharesheetModelScorer { targetsList.add(index, shareTarget); } promoteForegroundApp(shareTargetMap, dataManager, callingUserId); - promoteFrequentlyUsedApps(shareTargetMap, targetsLimit, dataManager, callingUserId); + promoteMostChosenAndFrequentlyUsedApps(shareTargetMap, targetsLimit, dataManager, + callingUserId); } /** - * Promotes frequently used sharing apps, if recommended apps based on sharing history have not - * reached the limit (e.g. user did not share any content in last couple weeks) + * Promotes frequently chosen sharing apps and frequently used sharing apps as per + * UsageStatsManager, if recommended apps based on sharing history have not reached the limit + * (e.g. user did not share any content in last couple weeks) */ - private static void promoteFrequentlyUsedApps( + private static void promoteMostChosenAndFrequentlyUsedApps( Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap, int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId) { int validPredictionNum = 0; @@ -217,39 +220,50 @@ class SharesheetModelScorer { return; } long now = System.currentTimeMillis(); - Map<String, Integer> appLaunchCountsMap = dataManager.queryAppLaunchCount( - callingUserId, now - ONE_MONTH_WINDOW, now, shareTargetMap.keySet()); - List<Pair<String, Integer>> appLaunchCounts = new ArrayList<>(); - minValidScore *= FREQUENTLY_USED_APP_SCORE_INITIAL_DECAY; - for (Map.Entry<String, Integer> entry : appLaunchCountsMap.entrySet()) { - if (entry.getValue() > 0) { - appLaunchCounts.add(new Pair(entry.getKey(), entry.getValue())); - } + Map<String, AppUsageStatsData> appStatsMap = + dataManager.queryAppUsageStats( + callingUserId, now - ONE_MONTH_WINDOW, now, shareTargetMap.keySet()); + // Promotes frequently chosen sharing apps as per UsageStatsManager. + minValidScore = promoteApp(shareTargetMap, appStatsMap, AppUsageStatsData::getChosenCount, + USAGE_STATS_CHOOSER_SCORE_INITIAL_DECAY * minValidScore, minValidScore); + // Promotes frequently used sharing apps as per UsageStatsManager. + promoteApp(shareTargetMap, appStatsMap, AppUsageStatsData::getLaunchCount, + FREQUENTLY_USED_APP_SCORE_INITIAL_DECAY * minValidScore, minValidScore); + } + + private static float promoteApp( + Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap, + Map<String, AppUsageStatsData> appStatsMap, + Function<AppUsageStatsData, Integer> countFunc, float baseScore, float minValidScore) { + int maxCount = 0; + for (AppUsageStatsData data : appStatsMap.values()) { + maxCount = Math.max(maxCount, countFunc.apply(data)); } - Collections.sort(appLaunchCounts, (p1, p2) -> -Integer.compare(p1.second, p2.second)); - for (Pair<String, Integer> entry : appLaunchCounts) { - if (!shareTargetMap.containsKey(entry.first)) { - continue; - } - ShareTargetPredictor.ShareTarget target = shareTargetMap.get(entry.first).get(0); - if (target.getScore() > 0f) { - continue; - } - target.setScore(minValidScore); - minValidScore *= FREQUENTLY_USED_APP_SCORE_DECAY; - if (DEBUG) { - Slog.d(TAG, String.format( - "SharesheetModel: promoteFrequentUsedApps packageName: %s, className: %s," - + " total:%.2f", - target.getAppTarget().getPackageName(), - target.getAppTarget().getClassName(), - target.getScore())); - } - validPredictionNum++; - if (validPredictionNum == targetsLimit) { - return; + if (maxCount > 0) { + for (Map.Entry<String, AppUsageStatsData> entry : appStatsMap.entrySet()) { + if (!shareTargetMap.containsKey(entry.getKey())) { + continue; + } + ShareTargetPredictor.ShareTarget target = shareTargetMap.get(entry.getKey()).get(0); + if (target.getScore() > 0f) { + continue; + } + float curScore = baseScore * countFunc.apply(entry.getValue()) / maxCount; + target.setScore(curScore); + if (curScore > 0) { + minValidScore = Math.min(minValidScore, curScore); + } + if (DEBUG) { + Slog.d(TAG, String.format( + "SharesheetModel: promote as per AppUsageStats packageName: %s, " + + "className: %s, total:%.2f", + target.getAppTarget().getPackageName(), + target.getAppTarget().getClassName(), + target.getScore())); + } } } + return minValidScore; } /** |