Loading services/people/java/com/android/server/people/PeopleService.java +3 −0 Original line number Diff line number Diff line Loading @@ -188,6 +188,9 @@ public class PeopleService extends SystemService { ConversationStatus status) { handleIncomingUser(userId); checkCallerIsSameApp(packageName); if (status.getStartTimeMillis() > System.currentTimeMillis()) { throw new IllegalArgumentException("Start time must be in the past"); } mDataManager.addOrUpdateStatus(packageName, userId, conversationId, status); } Loading services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java 0 → 100644 +98 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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 android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.app.people.ConversationStatus; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.CancellationSignal; import android.os.SystemClock; import com.android.server.LocalServices; import com.android.server.notification.NotificationRecord; import com.android.server.people.PeopleServiceInternal; import java.util.concurrent.TimeUnit; /** * If a {@link ConversationStatus} is added to the system with an expiration time, remove that * status at that time */ public class ConversationStatusExpirationBroadcastReceiver extends BroadcastReceiver { static final String ACTION = "ConversationStatusExpiration"; static final String EXTRA_USER_ID = "userId"; static final int REQUEST_CODE = 10; static final String SCHEME = "expStatus"; void scheduleExpiration(Context context, @UserIdInt int userId, String pkg, String conversationId, ConversationStatus status) { final PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_CODE, new Intent(ACTION) .setData(new Uri.Builder().scheme(SCHEME) .appendPath(getKey(userId, pkg, conversationId, status)) .build()) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) .putExtra(EXTRA_USER_ID, userId), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); context.getSystemService(AlarmManager.class).setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, status.getEndTimeMillis(), pi); } private static String getKey(@UserIdInt int userId, String pkg, String conversationId, ConversationStatus status) { return userId + pkg + conversationId + status.getId(); } static IntentFilter getFilter() { IntentFilter conversationStatusFilter = new IntentFilter(ConversationStatusExpirationBroadcastReceiver.ACTION); conversationStatusFilter.addDataScheme( ConversationStatusExpirationBroadcastReceiver.SCHEME); return conversationStatusFilter; } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) { return; } if (ACTION.equals(action)) { new Thread(() -> { PeopleServiceInternal peopleServiceInternal = LocalServices.getService(PeopleServiceInternal.class); peopleServiceInternal.pruneDataForUser(intent.getIntExtra(EXTRA_USER_ID, ActivityManager.getCurrentUser()), new CancellationSignal()); }).start(); } } } services/people/java/com/android/server/people/data/DataManager.java +33 −0 Original line number Diff line number Diff line Loading @@ -124,6 +124,7 @@ public class DataManager { private PackageManagerInternal mPackageManagerInternal; private NotificationManagerInternal mNotificationManagerInternal; private UserManager mUserManager; private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver; public DataManager(Context context) { this(context, new Injector()); Loading @@ -145,6 +146,10 @@ public class DataManager { mShortcutServiceInternal.addShortcutChangeCallback(new ShortcutServiceCallback()); mStatusExpReceiver = new ConversationStatusExpirationBroadcastReceiver(); mContext.registerReceiver(mStatusExpReceiver, ConversationStatusExpirationBroadcastReceiver.getFilter()); IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver(); mContext.registerReceiver(shutdownBroadcastReceiver, shutdownIntentFilter); Loading Loading @@ -295,6 +300,27 @@ public class DataManager { }); } /** * Removes any status with an expiration time in the past. */ public void pruneExpiredConversationStatuses(@UserIdInt int callingUserId, long currentTimeMs) { forPackagesInProfile(callingUserId, packageData -> { final ConversationStore cs = packageData.getConversationStore(); packageData.forAllConversations(conversationInfo -> { ConversationInfo.Builder builder = new ConversationInfo.Builder(conversationInfo); List<ConversationStatus> newStatuses = new ArrayList<>(); for (ConversationStatus status : conversationInfo.getStatuses()) { if (status.getEndTimeMillis() < 0 || currentTimeMs < status.getEndTimeMillis()) { newStatuses.add(status); } } builder.setStatuses(newStatuses); cs.addOrUpdate(builder.build()); }); }); } /** * Returns the last notification interaction with the specified conversation. If the * conversation can't be found or no interactions have been recorded, returns 0L. Loading @@ -317,6 +343,12 @@ public class DataManager { ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify); builder.addOrUpdateStatus(status); cs.addOrUpdate(builder.build()); if (status.getEndTimeMillis() >= 0) { mStatusExpReceiver.scheduleExpiration( mContext, userId, packageName, conversationId, status); } } public void clearStatus(String packageName, int userId, String conversationId, Loading Loading @@ -458,6 +490,7 @@ public class DataManager { packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS); } packageData.pruneOrphanEvents(); pruneExpiredConversationStatuses(userId, System.currentTimeMillis()); pruneOldRecentConversations(userId, System.currentTimeMillis()); cleanupCachedShortcuts(userId, MAX_CACHED_RECENT_SHORTCUTS); }); Loading services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +62 −3 Original line number Diff line number Diff line Loading @@ -42,12 +42,14 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; Loading Loading @@ -141,6 +143,7 @@ public final class DataManagerTest { @Mock private JobScheduler mJobScheduler; @Mock private StatusBarNotification mStatusBarNotification; @Mock private Notification mNotification; @Mock private AlarmManager mAlarmManager; @Captor private ArgumentCaptor<ShortcutChangeCallback> mShortcutChangeCallbackCaptor; @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor; Loading @@ -152,7 +155,6 @@ public final class DataManagerTest { private DataManager mDataManager; private CancellationSignal mCancellationSignal; private ShortcutChangeCallback mShortcutChangeCallback; private BroadcastReceiver mShutdownBroadcastReceiver; private ShortcutInfo mShortcutInfo; private TestInjector mInjector; Loading Loading @@ -187,10 +189,15 @@ public final class DataManagerTest { Context originalContext = getInstrumentation().getTargetContext(); when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo()); when(mContext.getUser()).thenReturn(originalContext.getUser()); when(mContext.getPackageName()).thenReturn(originalContext.getPackageName()); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mContext.getSystemServiceName(UserManager.class)).thenReturn( Context.USER_SERVICE); when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager); when(mContext.getSystemServiceName(AlarmManager.class)).thenReturn( Context.ALARM_SERVICE); when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); Loading Loading @@ -246,8 +253,7 @@ public final class DataManagerTest { mShortcutChangeCallbackCaptor.capture()); mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue(); verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(), any()); mShutdownBroadcastReceiver = mBroadcastReceiverCaptor.getValue(); verify(mContext, times(2)).registerReceiver(any(), any()); } @After Loading Loading @@ -766,6 +772,36 @@ public final class DataManagerTest { assertTrue(activeTimeSlots.isEmpty()); } @Test public void testPruneExpiredConversationStatuses() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); mDataManager.addOrUpdateConversationInfo(shortcut); ConversationStatus cs1 = new ConversationStatus.Builder("cs1", 9) .setEndTimeMillis(System.currentTimeMillis()) .build(); ConversationStatus cs2 = new ConversationStatus.Builder("cs2", 10) .build(); ConversationStatus cs3 = new ConversationStatus.Builder("cs3", 1) .setEndTimeMillis(Long.MAX_VALUE) .build(); mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs1); mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs2); mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs3); mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal); assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) .doesNotContain(cs1); assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) .contains(cs2); assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) .contains(cs3); } @Test public void testDoNotUncacheShortcutWithActiveNotifications() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); Loading Loading @@ -976,6 +1012,29 @@ public final class DataManagerTest { .contains(cs); assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) .contains(cs2); verify(mAlarmManager, never()).setExactAndAllowWhileIdle(anyInt(), anyLong(), any()); } @Test public void testAddOrUpdateStatus_schedulesJob() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); mDataManager.addOrUpdateConversationInfo(shortcut); ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY) .setEndTimeMillis(1000) .build(); mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs); ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME) .setEndTimeMillis(3000) .build(); mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs2); verify(mAlarmManager, times(2)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any()); } @Test Loading Loading
services/people/java/com/android/server/people/PeopleService.java +3 −0 Original line number Diff line number Diff line Loading @@ -188,6 +188,9 @@ public class PeopleService extends SystemService { ConversationStatus status) { handleIncomingUser(userId); checkCallerIsSameApp(packageName); if (status.getStartTimeMillis() > System.currentTimeMillis()) { throw new IllegalArgumentException("Start time must be in the past"); } mDataManager.addOrUpdateStatus(packageName, userId, conversationId, status); } Loading
services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java 0 → 100644 +98 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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 android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.app.people.ConversationStatus; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.CancellationSignal; import android.os.SystemClock; import com.android.server.LocalServices; import com.android.server.notification.NotificationRecord; import com.android.server.people.PeopleServiceInternal; import java.util.concurrent.TimeUnit; /** * If a {@link ConversationStatus} is added to the system with an expiration time, remove that * status at that time */ public class ConversationStatusExpirationBroadcastReceiver extends BroadcastReceiver { static final String ACTION = "ConversationStatusExpiration"; static final String EXTRA_USER_ID = "userId"; static final int REQUEST_CODE = 10; static final String SCHEME = "expStatus"; void scheduleExpiration(Context context, @UserIdInt int userId, String pkg, String conversationId, ConversationStatus status) { final PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_CODE, new Intent(ACTION) .setData(new Uri.Builder().scheme(SCHEME) .appendPath(getKey(userId, pkg, conversationId, status)) .build()) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) .putExtra(EXTRA_USER_ID, userId), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); context.getSystemService(AlarmManager.class).setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, status.getEndTimeMillis(), pi); } private static String getKey(@UserIdInt int userId, String pkg, String conversationId, ConversationStatus status) { return userId + pkg + conversationId + status.getId(); } static IntentFilter getFilter() { IntentFilter conversationStatusFilter = new IntentFilter(ConversationStatusExpirationBroadcastReceiver.ACTION); conversationStatusFilter.addDataScheme( ConversationStatusExpirationBroadcastReceiver.SCHEME); return conversationStatusFilter; } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) { return; } if (ACTION.equals(action)) { new Thread(() -> { PeopleServiceInternal peopleServiceInternal = LocalServices.getService(PeopleServiceInternal.class); peopleServiceInternal.pruneDataForUser(intent.getIntExtra(EXTRA_USER_ID, ActivityManager.getCurrentUser()), new CancellationSignal()); }).start(); } } }
services/people/java/com/android/server/people/data/DataManager.java +33 −0 Original line number Diff line number Diff line Loading @@ -124,6 +124,7 @@ public class DataManager { private PackageManagerInternal mPackageManagerInternal; private NotificationManagerInternal mNotificationManagerInternal; private UserManager mUserManager; private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver; public DataManager(Context context) { this(context, new Injector()); Loading @@ -145,6 +146,10 @@ public class DataManager { mShortcutServiceInternal.addShortcutChangeCallback(new ShortcutServiceCallback()); mStatusExpReceiver = new ConversationStatusExpirationBroadcastReceiver(); mContext.registerReceiver(mStatusExpReceiver, ConversationStatusExpirationBroadcastReceiver.getFilter()); IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver(); mContext.registerReceiver(shutdownBroadcastReceiver, shutdownIntentFilter); Loading Loading @@ -295,6 +300,27 @@ public class DataManager { }); } /** * Removes any status with an expiration time in the past. */ public void pruneExpiredConversationStatuses(@UserIdInt int callingUserId, long currentTimeMs) { forPackagesInProfile(callingUserId, packageData -> { final ConversationStore cs = packageData.getConversationStore(); packageData.forAllConversations(conversationInfo -> { ConversationInfo.Builder builder = new ConversationInfo.Builder(conversationInfo); List<ConversationStatus> newStatuses = new ArrayList<>(); for (ConversationStatus status : conversationInfo.getStatuses()) { if (status.getEndTimeMillis() < 0 || currentTimeMs < status.getEndTimeMillis()) { newStatuses.add(status); } } builder.setStatuses(newStatuses); cs.addOrUpdate(builder.build()); }); }); } /** * Returns the last notification interaction with the specified conversation. If the * conversation can't be found or no interactions have been recorded, returns 0L. Loading @@ -317,6 +343,12 @@ public class DataManager { ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify); builder.addOrUpdateStatus(status); cs.addOrUpdate(builder.build()); if (status.getEndTimeMillis() >= 0) { mStatusExpReceiver.scheduleExpiration( mContext, userId, packageName, conversationId, status); } } public void clearStatus(String packageName, int userId, String conversationId, Loading Loading @@ -458,6 +490,7 @@ public class DataManager { packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS); } packageData.pruneOrphanEvents(); pruneExpiredConversationStatuses(userId, System.currentTimeMillis()); pruneOldRecentConversations(userId, System.currentTimeMillis()); cleanupCachedShortcuts(userId, MAX_CACHED_RECENT_SHORTCUTS); }); Loading
services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +62 −3 Original line number Diff line number Diff line Loading @@ -42,12 +42,14 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; Loading Loading @@ -141,6 +143,7 @@ public final class DataManagerTest { @Mock private JobScheduler mJobScheduler; @Mock private StatusBarNotification mStatusBarNotification; @Mock private Notification mNotification; @Mock private AlarmManager mAlarmManager; @Captor private ArgumentCaptor<ShortcutChangeCallback> mShortcutChangeCallbackCaptor; @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor; Loading @@ -152,7 +155,6 @@ public final class DataManagerTest { private DataManager mDataManager; private CancellationSignal mCancellationSignal; private ShortcutChangeCallback mShortcutChangeCallback; private BroadcastReceiver mShutdownBroadcastReceiver; private ShortcutInfo mShortcutInfo; private TestInjector mInjector; Loading Loading @@ -187,10 +189,15 @@ public final class DataManagerTest { Context originalContext = getInstrumentation().getTargetContext(); when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo()); when(mContext.getUser()).thenReturn(originalContext.getUser()); when(mContext.getPackageName()).thenReturn(originalContext.getPackageName()); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mContext.getSystemServiceName(UserManager.class)).thenReturn( Context.USER_SERVICE); when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager); when(mContext.getSystemServiceName(AlarmManager.class)).thenReturn( Context.ALARM_SERVICE); when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); Loading Loading @@ -246,8 +253,7 @@ public final class DataManagerTest { mShortcutChangeCallbackCaptor.capture()); mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue(); verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(), any()); mShutdownBroadcastReceiver = mBroadcastReceiverCaptor.getValue(); verify(mContext, times(2)).registerReceiver(any(), any()); } @After Loading Loading @@ -766,6 +772,36 @@ public final class DataManagerTest { assertTrue(activeTimeSlots.isEmpty()); } @Test public void testPruneExpiredConversationStatuses() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); mDataManager.addOrUpdateConversationInfo(shortcut); ConversationStatus cs1 = new ConversationStatus.Builder("cs1", 9) .setEndTimeMillis(System.currentTimeMillis()) .build(); ConversationStatus cs2 = new ConversationStatus.Builder("cs2", 10) .build(); ConversationStatus cs3 = new ConversationStatus.Builder("cs3", 1) .setEndTimeMillis(Long.MAX_VALUE) .build(); mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs1); mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs2); mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs3); mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal); assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) .doesNotContain(cs1); assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) .contains(cs2); assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) .contains(cs3); } @Test public void testDoNotUncacheShortcutWithActiveNotifications() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); Loading Loading @@ -976,6 +1012,29 @@ public final class DataManagerTest { .contains(cs); assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) .contains(cs2); verify(mAlarmManager, never()).setExactAndAllowWhileIdle(anyInt(), anyLong(), any()); } @Test public void testAddOrUpdateStatus_schedulesJob() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); mDataManager.addOrUpdateConversationInfo(shortcut); ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY) .setEndTimeMillis(1000) .build(); mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs); ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME) .setEndTimeMillis(3000) .build(); mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs2); verify(mAlarmManager, times(2)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any()); } @Test Loading