Merge "Factor freqeuncy and mimetype of past sharings, foreground app into sharesheet model. Promote most frequent sharable apps to tackle cold start issue when device doesn't hold enough sharing history." into rvc-dev am: 7abd9be7a2

Change-Id: I108113b630bf507b09e2ce85da78198e21bd1049
This commit is contained in:
Song Hu
2020-03-26 22:04:50 +00:00
committed by Automerger Merge Worker
7 changed files with 1024 additions and 42 deletions

View File

@@ -26,6 +26,7 @@ import android.app.NotificationManager;
import android.app.Person;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.usage.UsageEvents;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -70,6 +71,7 @@ import com.android.server.notification.NotificationManagerInternal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -237,6 +239,27 @@ public class DataManager {
eventHistory.addEvent(new Event(System.currentTimeMillis(), eventType));
}
/**
* Queries events for moving app to foreground between {@code startTime} and {@code endTime}.
*/
@NonNull
public List<UsageEvents.Event> queryAppMovingToForegroundEvents(@UserIdInt int callingUserId,
long startTime, long endTime) {
return UsageStatsQueryHelper.queryAppMovingToForegroundEvents(callingUserId, startTime,
endTime);
}
/**
* Queries launch counts of apps within {@code packageNameFilter} between {@code startTime}
* and {@code endTime}.
*/
@NonNull
public Map<String, Integer> queryAppLaunchCount(@UserIdInt int callingUserId, long startTime,
long endTime, Set<String> packageNameFilter) {
return UsageStatsQueryHelper.queryAppLaunchCount(callingUserId, startTime, endTime,
packageNameFilter);
}
/** Prunes the data for the specified user. */
public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
UserData userData = getUnlockedUserData(userId);
@@ -382,7 +405,13 @@ public class DataManager {
}
}
private int mimeTypeToShareEventType(String mimeType) {
/**
* Converts {@code mimeType} to {@link Event.EventType}.
*/
public int mimeTypeToShareEventType(String mimeType) {
if (mimeType == null) {
return Event.TYPE_SHARE_OTHER;
}
if (mimeType.startsWith("text/")) {
return Event.TYPE_SHARE_TEXT;
} else if (mimeType.startsWith("image/")) {

View File

@@ -19,6 +19,8 @@ package com.android.server.people.data;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.LocusId;
@@ -27,7 +29,10 @@ import android.util.ArrayMap;
import com.android.server.LocalServices;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
/** A helper class that queries {@link UsageStatsManagerInternal}. */
@@ -46,7 +51,7 @@ class UsageStatsQueryHelper {
*/
UsageStatsQueryHelper(@UserIdInt int userId,
Function<String, PackageData> packageDataGetter) {
mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
mUsageStatsManagerInternal = getUsageStatsManagerInternal();
mUserId = userId;
mPackageDataGetter = packageDataGetter;
}
@@ -106,6 +111,53 @@ class UsageStatsQueryHelper {
return mLastEventTimestamp;
}
/**
* Queries {@link UsageStatsManagerInternal} events for moving app to foreground between
* {@code startTime} and {@code endTime}.
*
* @return a list containing events moving app to foreground.
*/
static List<UsageEvents.Event> queryAppMovingToForegroundEvents(@UserIdInt int userId,
long startTime, long endTime) {
List<UsageEvents.Event> res = new ArrayList<>();
UsageEvents usageEvents = getUsageStatsManagerInternal().queryEventsForUser(userId,
startTime, endTime,
UsageEvents.HIDE_SHORTCUT_EVENTS | UsageEvents.HIDE_LOCUS_EVENTS);
if (usageEvents == null) {
return res;
}
while (usageEvents.hasNextEvent()) {
UsageEvents.Event e = new UsageEvents.Event();
usageEvents.getNextEvent(e);
if (e.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED) {
res.add(e);
}
}
return res;
}
/**
* Queries {@link UsageStatsManagerInternal} for launch count of apps within {@code
* packageNameFilter} between {@code startTime} and {@code endTime}.obfuscateInstantApps
*
* @return a map which keys are package names and values are app launch counts.
*/
static Map<String, Integer> queryAppLaunchCount(@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<>();
for (UsageStats stat : stats) {
String packageName = stat.getPackageName();
if (packageNameFilter.contains(packageName)) {
aggregatedStats.put(packageName,
aggregatedStats.getOrDefault(packageName, 0) + stat.getAppLaunchCount());
}
}
return aggregatedStats;
}
private void onInAppConversationEnded(@NonNull PackageData packageData,
@NonNull UsageEvents.Event endEvent) {
ComponentName activityName =
@@ -138,4 +190,8 @@ class UsageStatsQueryHelper {
EventStore.CATEGORY_LOCUS_ID_BASED, locusId.getId());
eventHistory.addEvent(event);
}
private static UsageStatsManagerInternal getUsageStatsManagerInternal() {
return LocalServices.getService(UsageStatsManagerInternal.class);
}
}

View File

@@ -27,13 +27,11 @@ import android.app.prediction.AppTargetId;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
import android.util.Range;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.Event;
import com.android.server.people.data.EventHistory;
import com.android.server.people.data.PackageData;
@@ -42,6 +40,9 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/**
* Predictor that predicts the {@link AppTarget} the user is most likely to open on share sheet.
*/
class ShareTargetPredictor extends AppTargetPredictor {
private final IntentFilter mIntentFilter;
@@ -66,7 +67,9 @@ class ShareTargetPredictor extends AppTargetPredictor {
@Override
void predictTargets() {
List<ShareTarget> shareTargets = getDirectShareTargets();
rankTargets(shareTargets);
SharesheetModelScorer.computeScore(shareTargets, getShareEventType(mIntentFilter),
System.currentTimeMillis());
Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore()));
List<AppTarget> res = new ArrayList<>();
for (int i = 0; i < Math.min(getPredictionContext().getPredictedTargetCount(),
shareTargets.size()); i++) {
@@ -80,36 +83,16 @@ class ShareTargetPredictor extends AppTargetPredictor {
@Override
void sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
List<ShareTarget> shareTargets = getAppShareTargets(targets);
rankTargets(shareTargets);
SharesheetModelScorer.computeScoreForAppShare(shareTargets,
getShareEventType(mIntentFilter), getPredictionContext().getPredictedTargetCount(),
System.currentTimeMillis(), getDataManager(),
mCallingUserId);
Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore()));
List<AppTarget> appTargetList = new ArrayList<>();
shareTargets.forEach(t -> appTargetList.add(t.getAppTarget()));
callback.accept(appTargetList);
}
private void rankTargets(List<ShareTarget> shareTargets) {
// Rank targets based on recency of sharing history only for the moment.
// TODO: Take more factors into ranking, e.g. frequency, mime type, foreground app.
Collections.sort(shareTargets, (t1, t2) -> {
if (t1.getEventHistory() == null) {
return 1;
}
if (t2.getEventHistory() == null) {
return -1;
}
Range<Long> timeSlot1 = t1.getEventHistory().getEventIndex(
Event.SHARE_EVENT_TYPES).getMostRecentActiveTimeSlot();
Range<Long> timeSlot2 = t2.getEventHistory().getEventIndex(
Event.SHARE_EVENT_TYPES).getMostRecentActiveTimeSlot();
if (timeSlot1 == null) {
return 1;
} else if (timeSlot2 == null) {
return -1;
} else {
return -Long.compare(timeSlot1.getUpper(), timeSlot2.getUpper());
}
});
}
private List<ShareTarget> getDirectShareTargets() {
List<ShareTarget> shareTargets = new ArrayList<>();
List<ShareShortcutInfo> shareShortcuts =
@@ -153,6 +136,11 @@ class ShareTargetPredictor extends AppTargetPredictor {
return shareTargets;
}
private int getShareEventType(IntentFilter intentFilter) {
String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
return getDataManager().mimeTypeToShareEventType(mimeType);
}
@VisibleForTesting
static class ShareTarget {
@@ -162,13 +150,16 @@ class ShareTargetPredictor extends AppTargetPredictor {
private final EventHistory mEventHistory;
@Nullable
private final ConversationInfo mConversationInfo;
private float mScore;
private ShareTarget(@NonNull AppTarget appTarget,
@VisibleForTesting
ShareTarget(@NonNull AppTarget appTarget,
@Nullable EventHistory eventHistory,
@Nullable ConversationInfo conversationInfo) {
mAppTarget = appTarget;
mEventHistory = eventHistory;
mConversationInfo = conversationInfo;
mScore = 0f;
}
@NonNull
@@ -188,5 +179,15 @@ class ShareTargetPredictor extends AppTargetPredictor {
ConversationInfo getConversationInfo() {
return mConversationInfo;
}
@VisibleForTesting
float getScore() {
return mScore;
}
@VisibleForTesting
void setScore(float score) {
mScore = score;
}
}
}

View File

@@ -0,0 +1,406 @@
/*
* 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.prediction;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Range;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
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;
/** Ranking scorer for Sharesheet targets. */
class SharesheetModelScorer {
private static final String TAG = "SharesheetModelScorer";
private static final boolean DEBUG = false;
private static final Integer RECENCY_SCORE_COUNT = 6;
private static final float RECENCY_INITIAL_BASE_SCORE = 0.4F;
private static final float RECENCY_SCORE_INITIAL_DECAY = 0.05F;
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 FREQUENTLY_USED_APP_SCORE_DECAY = 0.9F;
@VisibleForTesting
static final float FOREGROUND_APP_WEIGHT = 0F;
@VisibleForTesting
static final String CHOOSER_ACTIVITY = ChooserActivity.class.getSimpleName();
// Keep constructor private to avoid class being instantiated.
private SharesheetModelScorer() {
}
/**
* Computes each target's recency, frequency and frequency of the same {@code shareEventType}
* based on past sharing history. Update {@link ShareTargetPredictor.ShareTargetScore}.
*/
static void computeScore(List<ShareTargetPredictor.ShareTarget> shareTargets,
int shareEventType, long now) {
if (shareTargets.isEmpty()) {
return;
}
float totalFreqScore = 0f;
int freqScoreCount = 0;
float totalMimeFreqScore = 0f;
int mimeFreqScoreCount = 0;
// Top of this heap has lowest rank.
PriorityQueue<Pair<ShareTargetRankingScore, Range<Long>>> recencyMinHeap =
new PriorityQueue<>(RECENCY_SCORE_COUNT,
Comparator.comparingLong(p -> p.second.getUpper()));
List<ShareTargetRankingScore> scoreList = new ArrayList<>(shareTargets.size());
for (ShareTargetPredictor.ShareTarget target : shareTargets) {
ShareTargetRankingScore shareTargetScore = new ShareTargetRankingScore();
scoreList.add(shareTargetScore);
if (target.getEventHistory() == null) {
continue;
}
// Counts frequency
List<Range<Long>> timeSlots = target.getEventHistory().getEventIndex(
Event.SHARE_EVENT_TYPES).getActiveTimeSlots();
if (!timeSlots.isEmpty()) {
for (Range<Long> timeSlot : timeSlots) {
shareTargetScore.incrementFrequencyScore(
getFreqDecayedOnElapsedTime(now - timeSlot.getLower()));
}
totalFreqScore += shareTargetScore.getFrequencyScore();
freqScoreCount++;
}
// Counts frequency for sharing same mime type
List<Range<Long>> timeSlotsOfSameType = target.getEventHistory().getEventIndex(
shareEventType).getActiveTimeSlots();
if (!timeSlotsOfSameType.isEmpty()) {
for (Range<Long> timeSlot : timeSlotsOfSameType) {
shareTargetScore.incrementMimeFrequencyScore(
getFreqDecayedOnElapsedTime(now - timeSlot.getLower()));
}
totalMimeFreqScore += shareTargetScore.getMimeFrequencyScore();
mimeFreqScoreCount++;
}
// Records most recent targets
Range<Long> mostRecentTimeSlot = target.getEventHistory().getEventIndex(
Event.SHARE_EVENT_TYPES).getMostRecentActiveTimeSlot();
if (mostRecentTimeSlot == null) {
continue;
}
if (recencyMinHeap.size() < RECENCY_SCORE_COUNT
|| mostRecentTimeSlot.getUpper() > recencyMinHeap.peek().second.getUpper()) {
if (recencyMinHeap.size() == RECENCY_SCORE_COUNT) {
recencyMinHeap.poll();
}
recencyMinHeap.offer(new Pair(shareTargetScore, mostRecentTimeSlot));
}
}
// Calculates recency score
while (!recencyMinHeap.isEmpty()) {
float recencyScore = RECENCY_INITIAL_BASE_SCORE;
if (recencyMinHeap.size() > 1) {
recencyScore = RECENCY_INITIAL_BASE_SCORE - RECENCY_SCORE_INITIAL_DECAY
- RECENCY_SCORE_SUBSEQUENT_DECAY * (recencyMinHeap.size() - 2);
}
recencyMinHeap.poll().first.setRecencyScore(recencyScore);
}
Float avgFreq = freqScoreCount != 0 ? totalFreqScore / freqScoreCount : 0f;
Float avgMimeFreq = mimeFreqScoreCount != 0 ? totalMimeFreqScore / mimeFreqScoreCount : 0f;
for (int i = 0; i < scoreList.size(); i++) {
ShareTargetPredictor.ShareTarget target = shareTargets.get(i);
ShareTargetRankingScore targetScore = scoreList.get(i);
// Normalizes freq and mimeFreq score
targetScore.setFrequencyScore(normalizeFreqScore(
avgFreq.equals(0f) ? 0f : targetScore.getFrequencyScore() / avgFreq));
targetScore.setMimeFrequencyScore(normalizeMimeFreqScore(avgMimeFreq.equals(0f) ? 0f
: targetScore.getMimeFrequencyScore() / avgMimeFreq));
// Calculates total score
targetScore.setTotalScore(
probOR(probOR(targetScore.getRecencyScore(), targetScore.getFrequencyScore()),
targetScore.getMimeFrequencyScore()));
target.setScore(targetScore.getTotalScore());
if (DEBUG) {
Slog.d(TAG, String.format(
"SharesheetModel: packageName: %s, className: %s, shortcutId: %s, "
+ "recency:%.2f, freq_all:%.2f, freq_mime:%.2f, total:%.2f",
target.getAppTarget().getPackageName(),
target.getAppTarget().getClassName(),
target.getAppTarget().getShortcutInfo() != null
? target.getAppTarget().getShortcutInfo().getId() : null,
targetScore.getRecencyScore(),
targetScore.getFrequencyScore(),
targetScore.getMimeFrequencyScore(),
targetScore.getTotalScore()));
}
}
}
/**
* Computes ranking score for app sharing. Update {@link ShareTargetPredictor.ShareTargetScore}.
*/
static void computeScoreForAppShare(List<ShareTargetPredictor.ShareTarget> shareTargets,
int shareEventType, int targetsLimit, long now, @NonNull DataManager dataManager,
@UserIdInt int callingUserId) {
computeScore(shareTargets, shareEventType, now);
postProcess(shareTargets, targetsLimit, dataManager, callingUserId);
}
private static void postProcess(List<ShareTargetPredictor.ShareTarget> shareTargets,
int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
// Populates a map which key is package name and value is list of shareTargets descended
// on total score.
Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap = new ArrayMap<>();
for (ShareTargetPredictor.ShareTarget shareTarget : shareTargets) {
String packageName = shareTarget.getAppTarget().getPackageName();
shareTargetMap.computeIfAbsent(packageName, key -> new ArrayList<>());
List<ShareTargetPredictor.ShareTarget> targetsList = shareTargetMap.get(packageName);
int index = 0;
while (index < targetsList.size()) {
if (shareTarget.getScore() > targetsList.get(index).getScore()) {
break;
}
index++;
}
targetsList.add(index, shareTarget);
}
promoteForegroundApp(shareTargetMap, dataManager, callingUserId);
promoteFrequentlyUsedApps(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)
*/
private static void promoteFrequentlyUsedApps(
Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap, int targetsLimit,
@NonNull DataManager dataManager, @UserIdInt int callingUserId) {
int validPredictionNum = 0;
float minValidScore = 1f;
for (List<ShareTargetPredictor.ShareTarget> targets : shareTargetMap.values()) {
for (ShareTargetPredictor.ShareTarget target : targets) {
if (target.getScore() > 0f) {
validPredictionNum++;
minValidScore = Math.min(target.getScore(), minValidScore);
}
}
}
// Skips if recommended apps based on sharing history have already reached the limit.
if (validPredictionNum >= targetsLimit) {
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<>();
for (Map.Entry<String, Integer> entry : appLaunchCountsMap.entrySet()) {
if (entry.getValue() > 0) {
appLaunchCounts.add(new Pair(entry.getKey(), entry.getValue()));
}
}
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;
}
minValidScore *= FREQUENTLY_USED_APP_SCORE_DECAY;
target.setScore(minValidScore);
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;
}
}
}
/**
* Promotes the foreground app just prior to source sharing app. Share often occurs between
* two apps the user is switching.
*/
private static void promoteForegroundApp(
Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
@NonNull DataManager dataManager, @UserIdInt int callingUserId) {
String sharingForegroundApp = findSharingForegroundApp(shareTargetMap, dataManager,
callingUserId);
if (sharingForegroundApp != null) {
ShareTargetPredictor.ShareTarget target = shareTargetMap.get(sharingForegroundApp).get(
0);
target.setScore(probOR(target.getScore(), FOREGROUND_APP_WEIGHT));
if (DEBUG) {
Slog.d(TAG, String.format(
"SharesheetModel: promoteForegroundApp packageName: %s, className: %s, "
+ "total:%.2f",
target.getAppTarget().getPackageName(),
target.getAppTarget().getClassName(),
target.getScore()));
}
}
}
/**
* Find the foreground app just prior to source sharing app from usageStatsManager. Returns null
* if it is not available.
*/
@Nullable
private static String findSharingForegroundApp(
Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
@NonNull DataManager dataManager, @UserIdInt int callingUserId) {
String sharingForegroundApp = null;
long now = System.currentTimeMillis();
List<UsageEvents.Event> events = dataManager.queryAppMovingToForegroundEvents(
callingUserId, now - FOREGROUND_APP_PROMO_TIME_WINDOW, now);
String sourceApp = null;
for (int i = events.size() - 1; i >= 0; i--) {
String className = events.get(i).getClassName();
String packageName = events.get(i).getPackageName();
if (packageName == null || (className != null && className.contains(CHOOSER_ACTIVITY))
|| packageName.contains(CHOOSER_ACTIVITY)) {
continue;
}
if (sourceApp == null) {
sourceApp = packageName;
} else if (!packageName.equals(sourceApp) && shareTargetMap.containsKey(packageName)) {
sharingForegroundApp = packageName;
break;
}
}
return sharingForegroundApp;
}
/**
* Probabilistic OR (also known as the algebraic sum). If a <= 1 and b <= 1, the result will be
* <= 1.0.
*/
private static float probOR(float a, float b) {
return 1f - (1f - a) * (1f - b);
}
/** Counts frequency of share targets. Decays frequency for old shares. */
private static float getFreqDecayedOnElapsedTime(long elapsedTimeMillis) {
Duration duration = Duration.ofMillis(elapsedTimeMillis);
if (duration.compareTo(Duration.ofDays(1)) <= 0) {
return 1.0f;
} else if (duration.compareTo(Duration.ofDays(3)) <= 0) {
return 0.9f;
} else if (duration.compareTo(Duration.ofDays(7)) <= 0) {
return 0.8f;
} else if (duration.compareTo(Duration.ofDays(14)) <= 0) {
return 0.7f;
} else {
return 0.6f;
}
}
/** Normalizes frequency score. */
private static float normalizeFreqScore(double freqRatio) {
if (freqRatio >= 2.5) {
return 0.2f;
} else if (freqRatio >= 1.5) {
return 0.15f;
} else if (freqRatio >= 1.0) {
return 0.1f;
} else if (freqRatio >= 0.75) {
return 0.05f;
} else {
return 0f;
}
}
/** Normalizes mimetype-specific frequency score. */
private static float normalizeMimeFreqScore(double freqRatio) {
if (freqRatio >= 2.0) {
return 0.2f;
} else if (freqRatio >= 1.2) {
return 0.15f;
} else if (freqRatio > 0.0) {
return 0.1f;
} else {
return 0f;
}
}
private static class ShareTargetRankingScore {
private float mRecencyScore = 0f;
private float mFrequencyScore = 0f;
private float mMimeFrequencyScore = 0f;
private float mTotalScore = 0f;
float getTotalScore() {
return mTotalScore;
}
void setTotalScore(float totalScore) {
mTotalScore = totalScore;
}
float getRecencyScore() {
return mRecencyScore;
}
void setRecencyScore(float recencyScore) {
mRecencyScore = recencyScore;
}
float getFrequencyScore() {
return mFrequencyScore;
}
void setFrequencyScore(float frequencyScore) {
mFrequencyScore = frequencyScore;
}
void incrementFrequencyScore(float incremental) {
mFrequencyScore += incremental;
}
float getMimeFrequencyScore() {
return mMimeFrequencyScore;
}
void setMimeFrequencyScore(float mimeFrequencyScore) {
mMimeFrequencyScore = mimeFrequencyScore;
}
void incrementMimeFrequencyScore(float incremental) {
mMimeFrequencyScore += incremental;
}
}
}

View File

@@ -21,15 +21,16 @@ import static com.android.server.people.data.TestUtils.timestamp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.LocusId;
@@ -50,6 +51,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;
@@ -58,7 +60,8 @@ import java.util.function.Predicate;
public final class UsageStatsQueryHelperTest {
private static final int USER_ID_PRIMARY = 0;
private static final String PKG_NAME = "pkg";
private static final String PKG_NAME_1 = "pkg_1";
private static final String PKG_NAME_2 = "pkg_2";
private static final String ACTIVITY_NAME = "TestActivity";
private static final String SHORTCUT_ID = "abc";
private static final LocusId LOCUS_ID_1 = new LocusId("locus_1");
@@ -80,7 +83,7 @@ public final class UsageStatsQueryHelperTest {
File testDir = new File(ctx.getCacheDir(), "testdir");
ScheduledExecutorService scheduledExecutorService = new MockScheduledExecutorService();
mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false,
mPackageData = new TestPackageData(PKG_NAME_1, USER_ID_PRIMARY, pkg -> false, pkg -> false,
scheduledExecutorService, testDir);
mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
.setShortcutId(SHORTCUT_ID)
@@ -173,10 +176,72 @@ public final class UsageStatsQueryHelperTest {
assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2));
}
@Test
public void testQueryAppMovingToForegroundEvents() {
addUsageEvents(
createShortcutInvocationEvent(100_000L),
createActivityResumedEvent(110_000L),
createActivityStoppedEvent(120_000L),
createActivityResumedEvent(130_000L));
List<UsageEvents.Event> events = mHelper.queryAppMovingToForegroundEvents(USER_ID_PRIMARY,
90_000L,
200_000L);
assertEquals(2, events.size());
assertEquals(UsageEvents.Event.ACTIVITY_RESUMED, events.get(0).getEventType());
assertEquals(110_000L, events.get(0).getTimeStamp());
assertEquals(UsageEvents.Event.ACTIVITY_RESUMED, events.get(1).getEventType());
assertEquals(130_000L, events.get(1).getTimeStamp());
}
@Test
public void testQueryAppLaunchCount() {
UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2);
UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3);
UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1);
when(mUsageStatsManagerInternal.queryUsageStatsForUser(anyInt(), anyInt(), anyLong(),
anyLong(), anyBoolean())).thenReturn(
List.of(packageStats1, packageStats2, packageStats3));
Map<String, Integer> appLaunchCounts = mHelper.queryAppLaunchCount(USER_ID_PRIMARY, 90_000L,
200_000L, Set.of(PKG_NAME_1, PKG_NAME_2));
assertEquals(2, appLaunchCounts.size());
assertEquals(5, (long) appLaunchCounts.get(PKG_NAME_1));
assertEquals(1, (long) appLaunchCounts.get(PKG_NAME_2));
}
@Test
public void testQueryAppLaunchCount_packageNameFiltered() {
UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2);
UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3);
UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1);
when(mUsageStatsManagerInternal.queryUsageStatsForUser(anyInt(), anyInt(), anyLong(),
anyLong(), anyBoolean())).thenReturn(
List.of(packageStats1, packageStats2, packageStats3));
Map<String, Integer> appLaunchCounts = mHelper.queryAppLaunchCount(USER_ID_PRIMARY, 90_000L,
200_000L,
Set.of(PKG_NAME_1));
assertEquals(1, appLaunchCounts.size());
assertEquals(5, (long) appLaunchCounts.get(PKG_NAME_1));
}
private void addUsageEvents(UsageEvents.Event... events) {
UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
eq(UsageEvents.SHOW_ALL_EVENT_DATA))).thenReturn(usageEvents);
anyInt())).thenReturn(usageEvents);
}
private static UsageStats createUsageStats(String packageName, int launchCount) {
UsageStats packageStats = new UsageStats();
packageStats.mPackageName = packageName;
packageStats.mAppLaunchCount = launchCount;
return packageStats;
}
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
@@ -203,9 +268,15 @@ public final class UsageStatsQueryHelperTest {
return e;
}
private static UsageEvents.Event createActivityResumedEvent(long timestamp) {
UsageEvents.Event e = createUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, timestamp);
e.mClass = ACTIVITY_NAME;
return e;
}
private static UsageEvents.Event createUsageEvent(int eventType, long timestamp) {
UsageEvents.Event e = new UsageEvents.Event(eventType, timestamp);
e.mPackage = PKG_NAME;
e.mPackage = PKG_NAME_1;
return e;
}

View File

@@ -127,6 +127,9 @@ public final class ShareTargetPredictorTest {
when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
when(mEventHistory1.getEventIndex(anyInt())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anyInt())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anyInt())).thenReturn(mEventIndex3);
when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L));
when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L));
when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L));
@@ -183,6 +186,12 @@ public final class ShareTargetPredictorTest {
when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
when(mEventHistory6.getEventIndex(anySet())).thenReturn(mEventIndex6);
when(mEventHistory1.getEventIndex(anyInt())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anyInt())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anyInt())).thenReturn(mEventIndex3);
when(mEventHistory4.getEventIndex(anyInt())).thenReturn(mEventIndex4);
when(mEventHistory5.getEventIndex(anyInt())).thenReturn(mEventIndex5);
when(mEventHistory6.getEventIndex(anyInt())).thenReturn(mEventIndex6);
when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L));
when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L));
when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L));
@@ -220,19 +229,19 @@ public final class ShareTargetPredictorTest {
@Test
public void testSortTargets() {
AppTarget appTarget1 = new AppTarget.Builder(
new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
.setClassName(CLASS_1)
.build();
AppTarget appTarget2 = new AppTarget.Builder(
new AppTargetId("cls2#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
new AppTargetId("cls2#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
.setClassName(CLASS_2)
.build();
AppTarget appTarget3 = new AppTarget.Builder(
new AppTargetId("cls1#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
new AppTargetId("cls1#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
.setClassName(CLASS_1)
.build();
AppTarget appTarget4 = new AppTarget.Builder(
new AppTargetId("cls2#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
new AppTargetId("cls2#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
.setClassName(CLASS_2)
.build();
AppTarget appTarget5 = new AppTarget.Builder(
@@ -251,6 +260,10 @@ public final class ShareTargetPredictorTest {
when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
when(mEventHistory1.getEventIndex(anyInt())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anyInt())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anyInt())).thenReturn(mEventIndex3);
when(mEventHistory4.getEventIndex(anyInt())).thenReturn(mEventIndex4);
when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L));
when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L));
when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L));
@@ -265,14 +278,14 @@ public final class ShareTargetPredictorTest {
appTarget4, appTarget3, appTarget2, appTarget1, appTarget5);
}
private ShareShortcutInfo buildShareShortcut(
private static ShareShortcutInfo buildShareShortcut(
String packageName, String className, String shortcutId) {
ShortcutInfo shortcutInfo = buildShortcut(packageName, shortcutId);
ComponentName componentName = new ComponentName(packageName, className);
return new ShareShortcutInfo(shortcutInfo, componentName);
}
private ShortcutInfo buildShortcut(String packageName, String shortcutId) {
private static ShortcutInfo buildShortcut(String packageName, String shortcutId) {
Context mockContext = mock(Context.class);
when(mockContext.getPackageName()).thenReturn(packageName);
when(mockContext.getUserId()).thenReturn(USER_ID);

View File

@@ -0,0 +1,406 @@
/*
* 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.prediction;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetId;
import android.app.usage.UsageEvents;
import android.os.UserHandle;
import android.util.Range;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.Event;
import com.android.server.people.data.EventHistory;
import com.android.server.people.data.EventIndex;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.time.Duration;
import java.util.List;
import java.util.Map;
@RunWith(JUnit4.class)
public final class SharesheetModelScorerTest {
private static final int USER_ID = 0;
private static final String PACKAGE_1 = "pkg1";
private static final String PACKAGE_2 = "pkg2";
private static final String PACKAGE_3 = "pkg3";
private static final String CLASS_1 = "cls1";
private static final String CLASS_2 = "cls2";
private static final double DELTA = 1e-6;
private static final long NOW = System.currentTimeMillis();
private static final Range<Long> WITHIN_ONE_DAY = new Range(
NOW - Duration.ofHours(23).toMillis(),
NOW - Duration.ofHours(22).toMillis());
private static final Range<Long> TWO_DAYS_AGO = new Range(
NOW - Duration.ofHours(50).toMillis(),
NOW - Duration.ofHours(49).toMillis());
private static final Range<Long> FIVE_DAYS_AGO = new Range(
NOW - Duration.ofDays(6).toMillis(),
NOW - Duration.ofDays(5).toMillis());
private static final Range<Long> EIGHT_DAYS_AGO = new Range(
NOW - Duration.ofDays(9).toMillis(),
NOW - Duration.ofDays(8).toMillis());
private static final Range<Long> TWELVE_DAYS_AGO = new Range(
NOW - Duration.ofDays(13).toMillis(),
NOW - Duration.ofDays(12).toMillis());
private static final Range<Long> TWENTY_DAYS_AGO = new Range(
NOW - Duration.ofDays(21).toMillis(),
NOW - Duration.ofDays(20).toMillis());
private static final Range<Long> FOUR_WEEKS_AGO = new Range(
NOW - Duration.ofDays(29).toMillis(),
NOW - Duration.ofDays(28).toMillis());
@Mock
private DataManager mDataManager;
@Mock
private EventHistory mEventHistory1;
@Mock
private EventHistory mEventHistory2;
@Mock
private EventHistory mEventHistory3;
@Mock
private EventHistory mEventHistory4;
@Mock
private EventHistory mEventHistory5;
@Mock
private EventIndex mEventIndex1;
@Mock
private EventIndex mEventIndex2;
@Mock
private EventIndex mEventIndex3;
@Mock
private EventIndex mEventIndex4;
@Mock
private EventIndex mEventIndex5;
@Mock
private EventIndex mEventIndex6;
@Mock
private EventIndex mEventIndex7;
@Mock
private EventIndex mEventIndex8;
@Mock
private EventIndex mEventIndex9;
@Mock
private EventIndex mEventIndex10;
private ShareTargetPredictor.ShareTarget mShareTarget1;
private ShareTargetPredictor.ShareTarget mShareTarget2;
private ShareTargetPredictor.ShareTarget mShareTarget3;
private ShareTargetPredictor.ShareTarget mShareTarget4;
private ShareTargetPredictor.ShareTarget mShareTarget5;
private ShareTargetPredictor.ShareTarget mShareTarget6;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mShareTarget1 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(
new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
.setClassName(CLASS_1).build(),
mEventHistory1, null);
mShareTarget2 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(new AppTargetId("cls2#pkg1"), PACKAGE_1,
UserHandle.of(USER_ID)).setClassName(CLASS_2).build(),
mEventHistory2, null);
mShareTarget3 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(
new AppTargetId("cls1#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
.setClassName(CLASS_1).build(),
mEventHistory3, null);
mShareTarget4 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(
new AppTargetId("cls2#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
.setClassName(CLASS_2).build(),
mEventHistory4, null);
mShareTarget5 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(
new AppTargetId("cls1#pkg3"), PACKAGE_3, UserHandle.of(USER_ID))
.setClassName(CLASS_1).build(),
mEventHistory5, null);
mShareTarget6 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(
new AppTargetId("cls2#pkg3"), PACKAGE_3, UserHandle.of(USER_ID))
.setClassName(CLASS_2).build(),
null, null);
}
@Test
public void testComputeScore() {
// Frequency and recency
when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
when(mEventIndex1.getActiveTimeSlots()).thenReturn(
List.of(WITHIN_ONE_DAY, TWO_DAYS_AGO, FIVE_DAYS_AGO));
when(mEventIndex2.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
when(mEventIndex3.getActiveTimeSlots()).thenReturn(List.of(FIVE_DAYS_AGO, TWENTY_DAYS_AGO));
when(mEventIndex4.getActiveTimeSlots()).thenReturn(
List.of(EIGHT_DAYS_AGO, TWELVE_DAYS_AGO, FOUR_WEEKS_AGO));
when(mEventIndex5.getActiveTimeSlots()).thenReturn(List.of());
when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(WITHIN_ONE_DAY);
when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(TWO_DAYS_AGO);
when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(FIVE_DAYS_AGO);
when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(EIGHT_DAYS_AGO);
when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(null);
// Frequency of the same mime type
when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
when(mEventIndex6.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO));
when(mEventIndex7.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
when(mEventIndex8.getActiveTimeSlots()).thenReturn(List.of());
when(mEventIndex9.getActiveTimeSlots()).thenReturn(List.of(EIGHT_DAYS_AGO));
when(mEventIndex10.getActiveTimeSlots()).thenReturn(List.of());
SharesheetModelScorer.computeScore(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
Event.TYPE_SHARE_TEXT,
NOW);
// Verification
assertEquals(0.514f, mShareTarget1.getScore(), DELTA);
assertEquals(0.475125f, mShareTarget2.getScore(), DELTA);
assertEquals(0.33f, mShareTarget3.getScore(), DELTA);
assertEquals(0.4411f, mShareTarget4.getScore(), DELTA);
assertEquals(0f, mShareTarget5.getScore(), DELTA);
assertEquals(0f, mShareTarget6.getScore(), DELTA);
}
@Test
public void testComputeScoreForAppShare() {
// Frequency and recency
when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
when(mEventIndex1.getActiveTimeSlots()).thenReturn(
List.of(WITHIN_ONE_DAY, TWO_DAYS_AGO, FIVE_DAYS_AGO));
when(mEventIndex2.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
when(mEventIndex3.getActiveTimeSlots()).thenReturn(List.of(FIVE_DAYS_AGO, TWENTY_DAYS_AGO));
when(mEventIndex4.getActiveTimeSlots()).thenReturn(
List.of(EIGHT_DAYS_AGO, TWELVE_DAYS_AGO, FOUR_WEEKS_AGO));
when(mEventIndex5.getActiveTimeSlots()).thenReturn(List.of());
when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(WITHIN_ONE_DAY);
when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(TWO_DAYS_AGO);
when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(FIVE_DAYS_AGO);
when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(EIGHT_DAYS_AGO);
when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(null);
// Frequency of the same mime type
when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
when(mEventIndex6.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO));
when(mEventIndex7.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
when(mEventIndex8.getActiveTimeSlots()).thenReturn(List.of());
when(mEventIndex9.getActiveTimeSlots()).thenReturn(List.of(EIGHT_DAYS_AGO));
when(mEventIndex10.getActiveTimeSlots()).thenReturn(List.of());
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
// Verification
assertEquals(0.514f, mShareTarget1.getScore(), DELTA);
assertEquals(0.475125f, mShareTarget2.getScore(), DELTA);
assertEquals(0.33f, mShareTarget3.getScore(), DELTA);
assertEquals(0.4411f, mShareTarget4.getScore(), DELTA);
assertEquals(0f, mShareTarget5.getScore(), DELTA);
assertEquals(0f, mShareTarget6.getScore(), DELTA);
}
@Test
public void testComputeScoreForAppShare_promoteFrequentlyUsedApps() {
when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
when(mDataManager.queryAppLaunchCount(anyInt(), anyLong(), anyLong(), anySet()))
.thenReturn(
Map.of(PACKAGE_1, 1,
PACKAGE_2, 2,
PACKAGE_3, 3));
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
verify(mDataManager, times(1)).queryAppLaunchCount(anyInt(), anyLong(), anyLong(),
anySet());
assertEquals(0.9f, mShareTarget5.getScore(), DELTA);
assertEquals(0.81f, mShareTarget3.getScore(), DELTA);
assertEquals(0.729f, mShareTarget1.getScore(), DELTA);
assertEquals(0f, mShareTarget2.getScore(), DELTA);
assertEquals(0f, mShareTarget4.getScore(), DELTA);
assertEquals(0f, mShareTarget6.getScore(), DELTA);
}
@Test
public void testComputeScoreForAppShare_skipPromoteFrequentlyUsedAppsWhenReachesLimit() {
when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(WITHIN_ONE_DAY);
when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(TWO_DAYS_AGO);
when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(FIVE_DAYS_AGO);
when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(EIGHT_DAYS_AGO);
when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(null);
when(mDataManager.queryAppLaunchCount(anyInt(), anyLong(), anyLong(), anySet()))
.thenReturn(
Map.of(PACKAGE_1, 1,
PACKAGE_2, 2,
PACKAGE_3, 3));
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID);
verify(mDataManager, never()).queryAppLaunchCount(anyInt(), anyLong(), anyLong(), anySet());
assertEquals(0.4f, mShareTarget1.getScore(), DELTA);
assertEquals(0.35f, mShareTarget2.getScore(), DELTA);
assertEquals(0.33f, mShareTarget3.getScore(), DELTA);
assertEquals(0.31f, mShareTarget4.getScore(), DELTA);
assertEquals(0f, mShareTarget5.getScore(), DELTA);
assertEquals(0f, mShareTarget6.getScore(), DELTA);
}
@Test
public void testComputeScoreForAppShare_promoteForegroundApp() {
when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
when(mDataManager.queryAppMovingToForegroundEvents(anyInt(), anyLong(),
anyLong())).thenReturn(
List.of(createUsageEvent(PACKAGE_2),
createUsageEvent(PACKAGE_3),
createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
createUsageEvent(PACKAGE_3),
createUsageEvent(PACKAGE_3))
);
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
anyLong());
assertEquals(0f, mShareTarget1.getScore(), DELTA);
assertEquals(0f, mShareTarget2.getScore(), DELTA);
assertEquals(SharesheetModelScorer.FOREGROUND_APP_WEIGHT, mShareTarget3.getScore(), DELTA);
assertEquals(0f, mShareTarget4.getScore(), DELTA);
assertEquals(0f, mShareTarget5.getScore(), DELTA);
assertEquals(0f, mShareTarget6.getScore(), DELTA);
}
@Test
public void testComputeScoreForAppShare_skipPromoteForegroundAppWhenNoValidForegroundApp() {
when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
when(mDataManager.queryAppMovingToForegroundEvents(anyInt(), anyLong(),
anyLong())).thenReturn(
List.of(createUsageEvent(PACKAGE_3),
createUsageEvent(PACKAGE_3),
createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
createUsageEvent(PACKAGE_3),
createUsageEvent(PACKAGE_3))
);
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
anyLong());
assertEquals(0f, mShareTarget1.getScore(), DELTA);
assertEquals(0f, mShareTarget2.getScore(), DELTA);
assertEquals(0f, mShareTarget3.getScore(), DELTA);
assertEquals(0f, mShareTarget4.getScore(), DELTA);
assertEquals(0f, mShareTarget5.getScore(), DELTA);
assertEquals(0f, mShareTarget6.getScore(), DELTA);
}
private static UsageEvents.Event createUsageEvent(String packageName) {
UsageEvents.Event e = new UsageEvents.Event();
e.mPackage = packageName;
return e;
}
}