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

Commit 4a7a1086 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Populate recent notifying apps from usage events" into qt-dev

parents 805ba401 abb04be4
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -21,10 +21,13 @@ import static com.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
import android.app.Activity;
import android.app.Application;
import android.app.settings.SettingsEnums;
import android.app.usage.IUsageStatsManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.SearchIndexableResource;
import android.text.TextUtils;

@@ -112,7 +115,9 @@ public class ConfigureNotificationSettings extends DashboardFragment implements
            lifecycle.addObserver(lockScreenNotificationController);
        }
        controllers.add(new RecentNotifyingAppsPreferenceController(
                context, new NotificationBackend(), app, host));
                context, new NotificationBackend(), IUsageStatsManager.Stub.asInterface(
                        ServiceManager.getService(Context.USAGE_STATS_SERVICE)),
                context.getSystemService(UserManager.class), app, host));
        controllers.add(lockScreenNotificationController);
        controllers.add(new NotificationRingtonePreferenceController(context) {
            @Override
+0 −9
Original line number Diff line number Diff line
@@ -312,15 +312,6 @@ public class NotificationBackend {
        }
    }

    public List<NotifyingApp> getRecentApps() {
        try {
            return sINM.getRecentNotifyingAppsForUser(UserHandle.myUserId()).getList();
        } catch (Exception e) {
            Log.w(TAG, "Error calling NoMan", e);
            return new ArrayList<>();
        }
    }

    public int getBlockedAppCount() {
        try {
            return sINM.getBlockedAppCount(UserHandle.myUserId());
+71 −10
Original line number Diff line number Diff line
@@ -18,11 +18,15 @@ package com.android.settings.notification;

import android.app.Application;
import android.app.settings.SettingsEnums;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.NotifyingApp;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -30,13 +34,8 @@ import android.util.ArraySet;
import android.util.IconDrawableFactory;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
@@ -47,11 +46,18 @@ import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.utils.StringUtil;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;

/**
 * This controller displays a list of recently used apps and a "See all" button. If there is
 * no recently used app, "See all" will be displayed as "Notifications".
@@ -66,28 +72,35 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC
    @VisibleForTesting
    static final String KEY_SEE_ALL = "all_notifications";
    private static final int SHOW_RECENT_APP_COUNT = 5;
    private static final int DAYS = 3;
    private static final Set<String> SKIP_SYSTEM_PACKAGES = new ArraySet<>();

    private final Fragment mHost;
    private final PackageManager mPm;
    private final NotificationBackend mNotificationBackend;
    private IUsageStatsManager mUsageStatsManager;
    private final int mUserId;
    private final IconDrawableFactory mIconDrawableFactory;

    private List<NotifyingApp> mApps;
    private Calendar mCal;
    List<NotifyingApp> mApps;
    private final ApplicationsState mApplicationsState;

    private PreferenceCategory mCategory;
    private Preference mSeeAllPref;
    private Preference mDivider;
    protected List<Integer> mUserIds;

    public RecentNotifyingAppsPreferenceController(Context context, NotificationBackend backend,
            IUsageStatsManager usageStatsManager, UserManager userManager,
            Application app, Fragment host) {
        this(context, backend, app == null ? null : ApplicationsState.getInstance(app), host);
        this(context, backend, usageStatsManager, userManager,
                app == null ? null : ApplicationsState.getInstance(app), host);
    }

    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
    RecentNotifyingAppsPreferenceController(Context context, NotificationBackend backend,
            IUsageStatsManager usageStatsManager, UserManager userManager,
            ApplicationsState appState, Fragment host) {
        super(context);
        mIconDrawableFactory = IconDrawableFactory.newInstance(context);
@@ -96,6 +109,13 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC
        mHost = host;
        mApplicationsState = appState;
        mNotificationBackend = backend;
        mUsageStatsManager = usageStatsManager;
        mUserIds = new ArrayList<>();
        mUserIds.add(mContext.getUserId());
        int workUserId = Utils.getManagedProfileId(userManager, mContext.getUserId());
        if (workUserId != UserHandle.USER_NULL) {
            mUserIds.add(workUserId);
        }
    }

    @Override
@@ -145,7 +165,48 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC

    @VisibleForTesting
    void reloadData() {
        mApps = mNotificationBackend.getRecentApps();
        mApps = new ArrayList<>();
        mCal = Calendar.getInstance();
        mCal.add(Calendar.DAY_OF_YEAR, -DAYS);
        for (int userId : mUserIds) {
            UsageEvents events = null;
            try {
                events = mUsageStatsManager.queryEventsForUser(mCal.getTimeInMillis(),
                        System.currentTimeMillis(), userId, mContext.getPackageName());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            if (events != null) {

                ArrayMap<String, NotifyingApp> aggregatedStats = new ArrayMap<>();

                UsageEvents.Event event = new UsageEvents.Event();
                while (events.hasNextEvent()) {
                    events.getNextEvent(event);

                    if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) {
                        NotifyingApp app =
                                aggregatedStats.get(getKey(userId, event.getPackageName()));
                        if (app == null) {
                            app = new NotifyingApp();
                            aggregatedStats.put(getKey(userId, event.getPackageName()), app);
                            app.setPackage(event.getPackageName());
                            app.setUserId(userId);
                        }
                        if (event.getTimeStamp() > app.getLastNotified()) {
                            app.setLastNotified(event.getTimeStamp());
                        }
                    }

                }

                mApps.addAll(aggregatedStats.values());
            }
        }
    }

    private static String getKey(int userId, String pkg) {
        return userId + "|" + pkg;
    }

    private void displayOnlyAllAppsLink() {
@@ -185,7 +246,7 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC
            // Bind recent apps to existing prefs if possible, or create a new pref.
            final String pkgName = app.getPackage();
            final ApplicationsState.AppEntry appEntry =
                    mApplicationsState.getEntry(app.getPackage(), mUserId);
                    mApplicationsState.getEntry(app.getPackage(), app.getUserId());
            if (appEntry == null) {
                continue;
            }
+150 −56
Original line number Diff line number Diff line
@@ -20,8 +20,10 @@ import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -31,22 +33,20 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Parcel;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.NotifyingApp;
import android.text.TextUtils;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
@@ -67,6 +67,12 @@ import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;

@RunWith(RobolectricTestRunner.class)
public class RecentNotifyingAppsPreferenceControllerTest {

@@ -94,6 +100,8 @@ public class RecentNotifyingAppsPreferenceControllerTest {
    private Fragment mHost;
    @Mock
    private FragmentActivity mActivity;
    @Mock
    private IUsageStatsManager mIUsageStatsManager;

    private Context mContext;
    private RecentNotifyingAppsPreferenceController mController;
@@ -104,9 +112,10 @@ public class RecentNotifyingAppsPreferenceControllerTest {
        mContext = spy(RuntimeEnvironment.application);
        doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE);
        doReturn(mPackageManager).when(mContext).getPackageManager();
        when(mUserManager.getProfileIdsWithDisabled(0)).thenReturn(new int[] {0});

        mController = new RecentNotifyingAppsPreferenceController(
                mContext, mBackend, mAppState, mHost);
                mContext, mBackend, mIUsageStatsManager, mUserManager, mAppState, mHost);
        when(mScreen.findPreference(anyString())).thenReturn(mCategory);

        when(mScreen.findPreference(RecentNotifyingAppsPreferenceController.KEY_SEE_ALL))
@@ -135,7 +144,7 @@ public class RecentNotifyingAppsPreferenceControllerTest {
    @Test
    public void onDisplayAndUpdateState_shouldRefreshUi() {
        mController = spy(new RecentNotifyingAppsPreferenceController(
                mContext, null, (ApplicationsState) null, null));
                mContext, null, mIUsageStatsManager, mUserManager, (ApplicationsState) null, null));

        doNothing().when(mController).refreshUi(mContext);

@@ -158,32 +167,41 @@ public class RecentNotifyingAppsPreferenceControllerTest {
    }

    @Test
    public void display_showRecents() {
        final List<NotifyingApp> apps = new ArrayList<>();
        final NotifyingApp app1 = new NotifyingApp()
                .setPackage("pkg.class")
                .setLastNotified(System.currentTimeMillis());
        final NotifyingApp app2 = new NotifyingApp()
                .setLastNotified(System.currentTimeMillis())
                .setPackage("com.android.settings");
        final NotifyingApp app3 = new NotifyingApp()
                .setLastNotified(System.currentTimeMillis() - 1000)
                .setPackage("pkg.class2");

        apps.add(app1);
        apps.add(app2);
        apps.add(app3);
    public void display_showRecents() throws Exception {

        List<Event> events = new ArrayList<>();
        Event app = new Event();
        app.mEventType = Event.NOTIFICATION_INTERRUPTION;
        app.mPackage = "a";
        app.mTimeStamp = System.currentTimeMillis();
        events.add(app);
        Event app1 = new Event();
        app1.mEventType = Event.NOTIFICATION_INTERRUPTION;
        app1.mPackage = "com.android.settings";
        app1.mTimeStamp = System.currentTimeMillis();
        events.add(app1);
        Event app2 = new Event();
        app2.mEventType = Event.NOTIFICATION_INTERRUPTION;
        app2.mPackage = "pkg.class2";
        app2.mTimeStamp = System.currentTimeMillis() - 1000;
        events.add(app2);

        // app1, app2 are valid apps. app3 is invalid.
        when(mAppState.getEntry(app1.getPackage(), UserHandle.myUserId()))
        when(mAppState.getEntry(app.getPackageName(), UserHandle.myUserId()))
                .thenReturn(mAppEntry);
        when(mAppState.getEntry(app2.getPackage(), UserHandle.myUserId()))
        when(mAppState.getEntry(app1.getPackageName(), UserHandle.myUserId()))
                .thenReturn(mAppEntry);
        when(mAppState.getEntry(app3.getPackage(), UserHandle.myUserId()))
        when(mAppState.getEntry(app2.getPackageName(), UserHandle.myUserId()))
                .thenReturn(null);
        when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(
                new ResolveInfo());
        when(mBackend.getRecentApps()).thenReturn(apps);

        UsageEvents usageEvents = getUsageEvents(
                new String[] {app.getPackageName(), app1.getPackageName(), app2.getPackageName()},
                events);
        when(mIUsageStatsManager.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
                .thenReturn(usageEvents);

        mAppEntry.info = mApplicationInfo;

        mController.displayPreference(mScreen);
@@ -198,34 +216,37 @@ public class RecentNotifyingAppsPreferenceControllerTest {
    }

    @Test
    public void display_showRecentsWithInstantApp() {
        // Regular app.
        final List<NotifyingApp> apps = new ArrayList<>();
        final NotifyingApp app1 = new NotifyingApp().
                setLastNotified(System.currentTimeMillis())
                .setPackage("com.foo.bar");
        apps.add(app1);

        // Instant app.
        final NotifyingApp app2 = new NotifyingApp()
                .setLastNotified(System.currentTimeMillis() + 200)
                .setPackage("com.foo.barinstant");
        apps.add(app2);
    public void display_showRecentsWithInstantApp() throws Exception {
        List<Event> events = new ArrayList<>();
        Event app = new Event();
        app.mEventType = Event.NOTIFICATION_INTERRUPTION;
        app.mPackage = "com.foo.bar";
        app.mTimeStamp = System.currentTimeMillis();
        events.add(app);
        Event app1 = new Event();
        app1.mEventType = Event.NOTIFICATION_INTERRUPTION;
        app1.mPackage = "com.foo.barinstant";
        app1.mTimeStamp = System.currentTimeMillis() + 200;
        events.add(app1);
        UsageEvents usageEvents = getUsageEvents(
                new String[] {"com.foo.bar", "com.foo.barinstant"}, events);
        when(mIUsageStatsManager.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
                .thenReturn(usageEvents);

        ApplicationsState.AppEntry app1Entry = mock(ApplicationsState.AppEntry.class);
        ApplicationsState.AppEntry app2Entry = mock(ApplicationsState.AppEntry.class);
        app1Entry.info = mApplicationInfo;
        app2Entry.info = mApplicationInfo;

        when(mAppState.getEntry(app1.getPackage(), UserHandle.myUserId())).thenReturn(app1Entry);
        when(mAppState.getEntry(app2.getPackage(), UserHandle.myUserId())).thenReturn(app2Entry);
        when(mAppState.getEntry(
                app.getPackageName(), UserHandle.myUserId())).thenReturn(app1Entry);
        when(mAppState.getEntry(
                app1.getPackageName(), UserHandle.myUserId())).thenReturn(app2Entry);

        // Only the regular app app1 should have its intent resolve.
        when(mPackageManager.resolveActivity(argThat(intentMatcher(app1.getPackage())),
        when(mPackageManager.resolveActivity(argThat(intentMatcher(app.getPackageName())),
                anyInt())).thenReturn(new ResolveInfo());

        when(mBackend.getRecentApps()).thenReturn(apps);

        // Make sure app2 is considered an instant app.
        ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
                (InstantAppDataProvider) (ApplicationInfo info) -> {
@@ -241,23 +262,27 @@ public class RecentNotifyingAppsPreferenceControllerTest {
        ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
        verify(mCategory, times(2)).addPreference(prefCaptor.capture());
        List<Preference> prefs = prefCaptor.getAllValues();
        assertThat(prefs.get(1).getKey()).isEqualTo(app1.getPackage());
        assertThat(prefs.get(0).getKey()).isEqualTo(app2.getPackage());
        assertThat(prefs.get(1).getKey()).isEqualTo(app.getPackageName());
        assertThat(prefs.get(0).getKey()).isEqualTo(app1.getPackageName());
    }

    @Test
    public void display_showRecents_formatSummary() {
        final List<NotifyingApp> apps = new ArrayList<>();
        final NotifyingApp app1 = new NotifyingApp()
                .setLastNotified(System.currentTimeMillis())
                .setPackage("pkg.class");
        apps.add(app1);

        when(mAppState.getEntry(app1.getPackage(), UserHandle.myUserId()))
    public void display_showRecents_formatSummary() throws Exception {
        List<Event> events = new ArrayList<>();
        Event app = new Event();
        app.mEventType = Event.NOTIFICATION_INTERRUPTION;
        app.mPackage = "pkg.class";
        app.mTimeStamp = System.currentTimeMillis();
        events.add(app);
        UsageEvents usageEvents = getUsageEvents(new String[] {"pkg.class"}, events);
        when(mIUsageStatsManager.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
                .thenReturn(usageEvents);

        when(mAppState.getEntry(app.getPackageName(), UserHandle.myUserId()))
                .thenReturn(mAppEntry);
        when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(
                new ResolveInfo());
        when(mBackend.getRecentApps()).thenReturn(apps);

        mAppEntry.info = mApplicationInfo;

        mController.displayPreference(mScreen);
@@ -265,6 +290,66 @@ public class RecentNotifyingAppsPreferenceControllerTest {
        verify(mCategory).addPreference(argThat(summaryMatches("Just now")));
    }

    @Test
    public void reloadData() throws Exception {
        when(mUserManager.getProfileIdsWithDisabled(0)).thenReturn(new int[] {0, 10});

        mController = new RecentNotifyingAppsPreferenceController(
                mContext, mBackend, mIUsageStatsManager, mUserManager, mAppState, mHost);

        List<Event> events = new ArrayList<>();
        Event app = new Event();
        app.mEventType = Event.NOTIFICATION_INTERRUPTION;
        app.mPackage = "b";
        app.mTimeStamp = 1;
        events.add(app);
        Event app1 = new Event();
        app1.mEventType = Event.MAX_EVENT_TYPE;
        app1.mPackage = "com.foo.bar";
        app1.mTimeStamp = 10;
        events.add(app1);
        UsageEvents usageEvents = getUsageEvents(
                new String[] {"b", "com.foo.bar"}, events);
        when(mIUsageStatsManager.queryEventsForUser(anyLong(), anyLong(), eq(0), anyString()))
                .thenReturn(usageEvents);

        List<Event> events10 = new ArrayList<>();
        Event app10 = new Event();
        app10.mEventType = Event.NOTIFICATION_INTERRUPTION;
        app10.mPackage = "a";
        app10.mTimeStamp = 2;
        events10.add(app10);
        Event app10a = new Event();
        app10a.mEventType = Event.NOTIFICATION_INTERRUPTION;
        app10a.mPackage = "a";
        app10a.mTimeStamp = 20;
        events10.add(app10a);
        UsageEvents usageEvents10 = getUsageEvents(
                new String[] {"a"}, events10);
        when(mIUsageStatsManager.queryEventsForUser(anyLong(), anyLong(), eq(10), anyString()))
                .thenReturn(usageEvents10);

        mController.reloadData();

        assertThat(mController.mApps.size()).isEqualTo(2);
        boolean foundPkg0 = false;
        boolean foundPkg10 = false;
        for (NotifyingApp notifyingApp : mController.mApps) {
            if (notifyingApp.getLastNotified() == 20
                    && notifyingApp.getPackage().equals("a")
                    && notifyingApp.getUserId() == 10) {
                foundPkg10 = true;
            }
            if (notifyingApp.getLastNotified() == 1
                    && notifyingApp.getPackage().equals("b")
                    && notifyingApp.getUserId() == 0) {
                foundPkg0 = true;
            }
        }
        assertThat(foundPkg0).isTrue();
        assertThat(foundPkg10).isTrue();
    }

    private static ArgumentMatcher<Preference> summaryMatches(String expected) {
        return preference -> TextUtils.equals(expected, preference.getSummary());
    }
@@ -273,4 +358,13 @@ public class RecentNotifyingAppsPreferenceControllerTest {
    private static ArgumentMatcher<Intent> intentMatcher(String packageName) {
        return intent -> packageName.equals(intent.getPackage());
    }

    private UsageEvents getUsageEvents(String[] pkgs, List<Event> events) {
        UsageEvents usageEvents = new UsageEvents(events, pkgs);
        Parcel parcel = Parcel.obtain();
        parcel.setDataPosition(0);
        usageEvents.writeToParcel(parcel, 0);
        parcel.setDataPosition(0);
        return UsageEvents.CREATOR.createFromParcel(parcel);
    }
}