Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 68cea0b1 authored by Song Hu's avatar Song Hu Committed by Automerger Merge Worker
Browse files

Merge "Factor freqeuncy and mimetype of past sharings, foreground app into...

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: 7abd9be7

Change-Id: I108113b630bf507b09e2ce85da78198e21bd1049
parents d895f37e 7abd9be7
Loading
Loading
Loading
Loading
+30 −1
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ import android.app.NotificationManager;
import android.app.Person;
import android.app.Person;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetEvent;
import android.app.usage.UsageEvents;
import android.content.BroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentResolver;
@@ -70,6 +71,7 @@ import com.android.server.notification.NotificationManagerInternal;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collections;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Executors;
@@ -237,6 +239,27 @@ public class DataManager {
        eventHistory.addEvent(new Event(System.currentTimeMillis(), eventType));
        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. */
    /** Prunes the data for the specified user. */
    public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
    public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
        UserData userData = getUnlockedUserData(userId);
        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/")) {
        if (mimeType.startsWith("text/")) {
            return Event.TYPE_SHARE_TEXT;
            return Event.TYPE_SHARE_TEXT;
        } else if (mimeType.startsWith("image/")) {
        } else if (mimeType.startsWith("image/")) {
+57 −1
Original line number Original line Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.server.people.data;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.LocusId;
import android.content.LocusId;
@@ -27,7 +29,10 @@ import android.util.ArrayMap;


import com.android.server.LocalServices;
import com.android.server.LocalServices;


import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Function;


/** A helper class that queries {@link UsageStatsManagerInternal}. */
/** A helper class that queries {@link UsageStatsManagerInternal}. */
@@ -46,7 +51,7 @@ class UsageStatsQueryHelper {
     */
     */
    UsageStatsQueryHelper(@UserIdInt int userId,
    UsageStatsQueryHelper(@UserIdInt int userId,
            Function<String, PackageData> packageDataGetter) {
            Function<String, PackageData> packageDataGetter) {
        mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
        mUsageStatsManagerInternal = getUsageStatsManagerInternal();
        mUserId = userId;
        mUserId = userId;
        mPackageDataGetter = packageDataGetter;
        mPackageDataGetter = packageDataGetter;
    }
    }
@@ -106,6 +111,53 @@ class UsageStatsQueryHelper {
        return mLastEventTimestamp;
        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,
    private void onInAppConversationEnded(@NonNull PackageData packageData,
            @NonNull UsageEvents.Event endEvent) {
            @NonNull UsageEvents.Event endEvent) {
        ComponentName activityName =
        ComponentName activityName =
@@ -138,4 +190,8 @@ class UsageStatsQueryHelper {
                EventStore.CATEGORY_LOCUS_ID_BASED, locusId.getId());
                EventStore.CATEGORY_LOCUS_ID_BASED, locusId.getId());
        eventHistory.addEvent(event);
        eventHistory.addEvent(event);
    }
    }

    private static UsageStatsManagerInternal getUsageStatsManagerInternal() {
        return LocalServices.getService(UsageStatsManagerInternal.class);
    }
}
}
+30 −29
Original line number Original line Diff line number Diff line
@@ -27,13 +27,11 @@ import android.app.prediction.AppTargetId;
import android.content.IntentFilter;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
import android.util.Range;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
import com.android.internal.app.ChooserActivity;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
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.EventHistory;
import com.android.server.people.data.PackageData;
import com.android.server.people.data.PackageData;


@@ -42,6 +40,9 @@ import java.util.Collections;
import java.util.List;
import java.util.List;
import java.util.function.Consumer;
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 {
class ShareTargetPredictor extends AppTargetPredictor {


    private final IntentFilter mIntentFilter;
    private final IntentFilter mIntentFilter;
@@ -66,7 +67,9 @@ class ShareTargetPredictor extends AppTargetPredictor {
    @Override
    @Override
    void predictTargets() {
    void predictTargets() {
        List<ShareTarget> shareTargets = getDirectShareTargets();
        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<>();
        List<AppTarget> res = new ArrayList<>();
        for (int i = 0; i < Math.min(getPredictionContext().getPredictedTargetCount(),
        for (int i = 0; i < Math.min(getPredictionContext().getPredictedTargetCount(),
                shareTargets.size()); i++) {
                shareTargets.size()); i++) {
@@ -80,36 +83,16 @@ class ShareTargetPredictor extends AppTargetPredictor {
    @Override
    @Override
    void sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
    void sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
        List<ShareTarget> shareTargets = getAppShareTargets(targets);
        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<>();
        List<AppTarget> appTargetList = new ArrayList<>();
        shareTargets.forEach(t -> appTargetList.add(t.getAppTarget()));
        shareTargets.forEach(t -> appTargetList.add(t.getAppTarget()));
        callback.accept(appTargetList);
        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() {
    private List<ShareTarget> getDirectShareTargets() {
        List<ShareTarget> shareTargets = new ArrayList<>();
        List<ShareTarget> shareTargets = new ArrayList<>();
        List<ShareShortcutInfo> shareShortcuts =
        List<ShareShortcutInfo> shareShortcuts =
@@ -153,6 +136,11 @@ class ShareTargetPredictor extends AppTargetPredictor {
        return shareTargets;
        return shareTargets;
    }
    }


    private int getShareEventType(IntentFilter intentFilter) {
        String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
        return getDataManager().mimeTypeToShareEventType(mimeType);
    }

    @VisibleForTesting
    @VisibleForTesting
    static class ShareTarget {
    static class ShareTarget {


@@ -162,13 +150,16 @@ class ShareTargetPredictor extends AppTargetPredictor {
        private final EventHistory mEventHistory;
        private final EventHistory mEventHistory;
        @Nullable
        @Nullable
        private final ConversationInfo mConversationInfo;
        private final ConversationInfo mConversationInfo;
        private float mScore;


        private ShareTarget(@NonNull AppTarget appTarget,
        @VisibleForTesting
        ShareTarget(@NonNull AppTarget appTarget,
                @Nullable EventHistory eventHistory,
                @Nullable EventHistory eventHistory,
                @Nullable ConversationInfo conversationInfo) {
                @Nullable ConversationInfo conversationInfo) {
            mAppTarget = appTarget;
            mAppTarget = appTarget;
            mEventHistory = eventHistory;
            mEventHistory = eventHistory;
            mConversationInfo = conversationInfo;
            mConversationInfo = conversationInfo;
            mScore = 0f;
        }
        }


        @NonNull
        @NonNull
@@ -188,5 +179,15 @@ class ShareTargetPredictor extends AppTargetPredictor {
        ConversationInfo getConversationInfo() {
        ConversationInfo getConversationInfo() {
            return mConversationInfo;
            return mConversationInfo;
        }
        }

        @VisibleForTesting
        float getScore() {
            return mScore;
        }

        @VisibleForTesting
        void setScore(float score) {
            mScore = score;
        }
    }
    }
}
}
+406 −0

File added.

Preview size limit exceeded, changes collapsed.

+76 −5
Original line number Original line Diff line number Diff line
@@ -21,15 +21,16 @@ import static com.android.server.people.data.TestUtils.timestamp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.Context;
import android.content.LocusId;
import android.content.LocusId;
@@ -50,6 +51,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;
import java.util.function.Predicate;
@@ -58,7 +60,8 @@ import java.util.function.Predicate;
public final class UsageStatsQueryHelperTest {
public final class UsageStatsQueryHelperTest {


    private static final int USER_ID_PRIMARY = 0;
    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 ACTIVITY_NAME = "TestActivity";
    private static final String SHORTCUT_ID = "abc";
    private static final String SHORTCUT_ID = "abc";
    private static final LocusId LOCUS_ID_1 = new LocusId("locus_1");
    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");
        File testDir = new File(ctx.getCacheDir(), "testdir");
        ScheduledExecutorService scheduledExecutorService = new MockScheduledExecutorService();
        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);
                scheduledExecutorService, testDir);
        mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
        mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
                .setShortcutId(SHORTCUT_ID)
                .setShortcutId(SHORTCUT_ID)
@@ -173,10 +176,72 @@ public final class UsageStatsQueryHelperTest {
        assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2));
        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) {
    private void addUsageEvents(UsageEvents.Event... events) {
        UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
        UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
        when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
        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) {
    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
@@ -203,9 +268,15 @@ public final class UsageStatsQueryHelperTest {
        return e;
        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) {
    private static UsageEvents.Event createUsageEvent(int eventType, long timestamp) {
        UsageEvents.Event e = new UsageEvents.Event(eventType, timestamp);
        UsageEvents.Event e = new UsageEvents.Event(eventType, timestamp);
        e.mPackage = PKG_NAME;
        e.mPackage = PKG_NAME_1;
        return e;
        return e;
    }
    }


Loading