Loading src/com/android/settings/notification/ConfigureNotificationSettings.java +6 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading src/com/android/settings/notification/NotificationBackend.java +0 −9 Original line number Diff line number Diff line Loading @@ -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()); Loading src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java +71 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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". Loading @@ -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); Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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; } Loading tests/robotests/src/com/android/settings/notification/RecentNotifyingAppsPreferenceControllerTest.java +150 −56 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading Loading @@ -94,6 +100,8 @@ public class RecentNotifyingAppsPreferenceControllerTest { private Fragment mHost; @Mock private FragmentActivity mActivity; @Mock private IUsageStatsManager mIUsageStatsManager; private Context mContext; private RecentNotifyingAppsPreferenceController mController; Loading @@ -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)) Loading Loading @@ -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); Loading @@ -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); Loading @@ -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) -> { Loading @@ -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); Loading @@ -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()); } Loading @@ -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); } } Loading
src/com/android/settings/notification/ConfigureNotificationSettings.java +6 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading
src/com/android/settings/notification/NotificationBackend.java +0 −9 Original line number Diff line number Diff line Loading @@ -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()); Loading
src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java +71 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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". Loading @@ -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); Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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; } Loading
tests/robotests/src/com/android/settings/notification/RecentNotifyingAppsPreferenceControllerTest.java +150 −56 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading Loading @@ -94,6 +100,8 @@ public class RecentNotifyingAppsPreferenceControllerTest { private Fragment mHost; @Mock private FragmentActivity mActivity; @Mock private IUsageStatsManager mIUsageStatsManager; private Context mContext; private RecentNotifyingAppsPreferenceController mController; Loading @@ -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)) Loading Loading @@ -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); Loading @@ -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); Loading @@ -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) -> { Loading @@ -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); Loading @@ -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()); } Loading @@ -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); } }