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

Commit 0660f710 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Promote frequently sharing apps as per monthly stats fetched from...

Merge "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" into rvc-dev am: 1de6b7ed

Change-Id: If3c21f01678de3fa976d9fe736b595e2997ab10d
parents 50a9cdf2 1de6b7ed
Loading
Loading
Loading
Loading
+52 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+7 −4
Original line number Diff line number Diff line
@@ -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);
    }

+28 −7
Original line number Diff line number Diff line
@@ -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 =
+50 −36
Original line number Diff line number Diff line
@@ -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(
        Map<String, AppUsageStatsData> appStatsMap =
                dataManager.queryAppUsageStats(
                        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()));
            }
        // 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);
    }
        Collections.sort(appLaunchCounts, (p1, p2) -> -Integer.compare(p1.second, p2.second));
        for (Pair<String, Integer> entry : appLaunchCounts) {
            if (!shareTargetMap.containsKey(entry.first)) {

    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));
        }
        if (maxCount > 0) {
            for (Map.Entry<String, AppUsageStatsData> entry : appStatsMap.entrySet()) {
                if (!shareTargetMap.containsKey(entry.getKey())) {
                    continue;
                }
            ShareTargetPredictor.ShareTarget target = shareTargetMap.get(entry.first).get(0);
                ShareTargetPredictor.ShareTarget target = shareTargetMap.get(entry.getKey()).get(0);
                if (target.getScore() > 0f) {
                    continue;
                }
            target.setScore(minValidScore);
            minValidScore *= FREQUENTLY_USED_APP_SCORE_DECAY;
                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: promoteFrequentUsedApps packageName: %s, className: %s,"
                                + " total:%.2f",
                            "SharesheetModel: promote as per AppUsageStats packageName: %s, "
                                    + "className: %s, total:%.2f",
                            target.getAppTarget().getPackageName(),
                            target.getAppTarget().getClassName(),
                            target.getScore()));
                }
            validPredictionNum++;
            if (validPredictionNum == targetsLimit) {
                return;
            }
        }
        return minValidScore;
    }

    /**
+39 −21
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.LocusId;
import android.util.ArrayMap;

import androidx.test.InstrumentationRegistry;

@@ -196,39 +197,42 @@ public final class UsageStatsQueryHelperTest {
    }

    @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);
    public void testQueryAppUsageStats() {
        UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2, createDummyChooserCounts());
        UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3, null);
        UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1, createDummyChooserCounts());
        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,
        Map<String, AppUsageStatsData> appLaunchChooserCountCounts =
                mHelper.queryAppUsageStats(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));
        assertEquals(2, appLaunchChooserCountCounts.size());
        assertEquals(4, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getChosenCount());
        assertEquals(5, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getLaunchCount());
        assertEquals(4, (long) appLaunchChooserCountCounts.get(PKG_NAME_2).getChosenCount());
        assertEquals(1, (long) appLaunchChooserCountCounts.get(PKG_NAME_2).getLaunchCount());
    }

    @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);
    public void testQueryAppUsageStats_packageNameFiltered() {
        UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2, createDummyChooserCounts());
        UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3, createDummyChooserCounts());
        UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1, null);
        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,
        Map<String, AppUsageStatsData> appLaunchChooserCountCounts =
                mHelper.queryAppUsageStats(USER_ID_PRIMARY, 90_000L,
                        200_000L,
                        Set.of(PKG_NAME_1));

        assertEquals(1, appLaunchCounts.size());
        assertEquals(5, (long) appLaunchCounts.get(PKG_NAME_1));
        assertEquals(1, appLaunchChooserCountCounts.size());
        assertEquals(8, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getChosenCount());
        assertEquals(5, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getLaunchCount());
    }

    private void addUsageEvents(UsageEvents.Event... events) {
@@ -237,13 +241,27 @@ public final class UsageStatsQueryHelperTest {
                anyInt())).thenReturn(usageEvents);
    }

    private static UsageStats createUsageStats(String packageName, int launchCount) {
    private static UsageStats createUsageStats(String packageName, int launchCount,
            ArrayMap<String, ArrayMap<String, Integer>> chooserCounts) {
        UsageStats packageStats = new UsageStats();
        packageStats.mPackageName = packageName;
        packageStats.mAppLaunchCount = launchCount;
        packageStats.mChooserCounts = chooserCounts;
        return packageStats;
    }

    private static ArrayMap<String, ArrayMap<String, Integer>> createDummyChooserCounts() {
        ArrayMap<String, ArrayMap<String, Integer>> chooserCounts = new ArrayMap<>();
        ArrayMap<String, Integer> counts1 = new ArrayMap<>();
        counts1.put("text", 2);
        counts1.put("image", 1);
        chooserCounts.put("intent1", counts1);
        ArrayMap<String, Integer> counts2 = new ArrayMap<>();
        counts2.put("video", 1);
        chooserCounts.put("intent2", counts2);
        return chooserCounts;
    }

    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
        LocalServices.removeServiceForTest(clazz);
        LocalServices.addService(clazz, mock);
Loading