Merge "Handle start and end times of convo statuses"
This commit is contained in:
committed by
Android (Google) Code Review
commit
9032a61ea4
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
@@ -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);
|
||||
@@ -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.
|
||||
@@ -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,
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user