summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/AndroidManifest.xml8
-rw-r--r--packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleBackupFollowUpJob.java229
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/SharedPreferencesHelper.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java508
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java189
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java202
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java100
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java537
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java105
17 files changed, 2010 insertions, 44 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 604310a9e905..8963ddaece35 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -281,6 +281,8 @@
<!-- Permission for Smartspace. -->
<uses-permission android:name="android.permission.MANAGE_SMARTSPACE" />
+ <uses-permission android:name="android.permission.READ_PEOPLE_DATA" />
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
@@ -604,7 +606,8 @@
</activity>
<activity android:name=".people.widget.LaunchConversationActivity"
- android:windowDisablePreview="true" />
+ android:windowDisablePreview="true"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" />
<!-- People Space Widget -->
<receiver
@@ -630,6 +633,9 @@
android:permission="android.permission.GET_PEOPLE_TILE_PREVIEW">
</provider>
+ <service android:name=".people.PeopleBackupFollowUpJob"
+ android:permission="android.permission.BIND_JOB_SERVICE"/>
+
<!-- a gallery of delicious treats -->
<service
android:name=".DessertCaseDream"
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index fe31a7b75c06..c9e67715decb 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -29,6 +29,7 @@ import android.os.UserHandle
import android.util.Log
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.people.widget.PeopleBackupHelper
/**
* Helper for backing up elements in SystemUI
@@ -45,18 +46,29 @@ class BackupHelper : BackupAgentHelper() {
private const val TAG = "BackupHelper"
internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
+ private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
val controlsDataLock = Any()
const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
}
- override fun onCreate() {
+ override fun onCreate(userHandle: UserHandle, operationType: Int) {
super.onCreate()
// The map in mapOf is guaranteed to be order preserving
val controlsMap = mapOf(CONTROLS to getPPControlsFile(this))
NoOverwriteFileBackupHelper(controlsDataLock, this, controlsMap).also {
addHelper(NO_OVERWRITE_FILES_BACKUP_KEY, it)
}
+
+ // Conversations widgets backup only works for system user, because widgets' information is
+ // stored in system user's SharedPreferences files and we can't open those from other users.
+ if (!userHandle.isSystem) {
+ return
+ }
+
+ val keys = PeopleBackupHelper.getFilesToBackup()
+ addHelper(PEOPLE_TILES_BACKUP_KEY, PeopleBackupHelper(
+ this, userHandle, keys.toTypedArray()))
}
override fun onRestoreFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleBackupFollowUpJob.java b/packages/SystemUI/src/com/android/systemui/people/PeopleBackupFollowUpJob.java
new file mode 100644
index 000000000000..452484f599a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleBackupFollowUpJob.java
@@ -0,0 +1,229 @@
+/*
+ * 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.systemui.people;
+
+import static com.android.systemui.people.PeopleSpaceUtils.DEBUG;
+import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
+import static com.android.systemui.people.PeopleSpaceUtils.removeSharedPreferencesStorageForTile;
+import static com.android.systemui.people.widget.PeopleBackupHelper.isReadyForRestore;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.app.people.IPeopleManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.PersistableBundle;
+import android.os.ServiceManager;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.people.widget.PeopleBackupHelper;
+import com.android.systemui.people.widget.PeopleTileKey;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Follow-up job that runs after a Conversations widgets restore operation. Check if shortcuts that
+ * were not available before are now available. If any shortcut doesn't become available after
+ * 1 day, we clean up its storage.
+ */
+public class PeopleBackupFollowUpJob extends JobService {
+ private static final String TAG = "PeopleBackupFollowUpJob";
+ private static final String START_DATE = "start_date";
+
+ /** Follow-up job id. */
+ public static final int JOB_ID = 74823873;
+
+ private static final long JOB_PERIODIC_DURATION = Duration.ofHours(6).toMillis();
+ private static final long CLEAN_UP_STORAGE_AFTER_DURATION = Duration.ofHours(24).toMillis();
+
+ /** SharedPreferences file name for follow-up specific storage.*/
+ public static final String SHARED_FOLLOW_UP = "shared_follow_up";
+
+ private final Object mLock = new Object();
+ private Context mContext;
+ private PackageManager mPackageManager;
+ private IPeopleManager mIPeopleManager;
+ private JobScheduler mJobScheduler;
+
+ /** Schedules a PeopleBackupFollowUpJob every 2 hours. */
+ public static void scheduleJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putLong(START_DATE, System.currentTimeMillis());
+ JobInfo jobInfo = new JobInfo
+ .Builder(JOB_ID, new ComponentName(context, PeopleBackupFollowUpJob.class))
+ .setPeriodic(JOB_PERIODIC_DURATION)
+ .setExtras(bundle)
+ .build();
+ jobScheduler.schedule(jobInfo);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mContext = getApplicationContext();
+ mPackageManager = getApplicationContext().getPackageManager();
+ mIPeopleManager = IPeopleManager.Stub.asInterface(
+ ServiceManager.getService(Context.PEOPLE_SERVICE));
+ mJobScheduler = mContext.getSystemService(JobScheduler.class);
+
+ }
+
+ /** Sets necessary managers for testing. */
+ @VisibleForTesting
+ public void setManagers(Context context, PackageManager packageManager,
+ IPeopleManager iPeopleManager, JobScheduler jobScheduler) {
+ mContext = context;
+ mPackageManager = packageManager;
+ mIPeopleManager = iPeopleManager;
+ mJobScheduler = jobScheduler;
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (DEBUG) Log.d(TAG, "Starting job.");
+ synchronized (mLock) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = sp.edit();
+ SharedPreferences followUp = this.getSharedPreferences(
+ SHARED_FOLLOW_UP, Context.MODE_PRIVATE);
+ SharedPreferences.Editor followUpEditor = followUp.edit();
+
+ // Remove from SHARED_FOLLOW_UP storage all widgets that are now ready to be updated.
+ Map<String, Set<String>> remainingWidgets =
+ processFollowUpFile(followUp, followUpEditor);
+
+ // Check if all widgets were restored or if enough time elapsed to cancel the job.
+ long start = params.getExtras().getLong(START_DATE);
+ long now = System.currentTimeMillis();
+ if (shouldCancelJob(remainingWidgets, start, now)) {
+ cancelJobAndClearRemainingWidgets(remainingWidgets, followUpEditor, sp);
+ }
+
+ editor.apply();
+ followUpEditor.apply();
+ }
+
+ // Ensure all widgets modified from SHARED_FOLLOW_UP storage are now updated.
+ PeopleBackupHelper.updateWidgets(mContext);
+ return false;
+ }
+
+ /**
+ * Iterates through follow-up file entries and checks which shortcuts are now available.
+ * Returns a map of shortcuts that should be checked at a later time.
+ */
+ public Map<String, Set<String>> processFollowUpFile(SharedPreferences followUp,
+ SharedPreferences.Editor followUpEditor) {
+ Map<String, Set<String>> remainingWidgets = new HashMap<>();
+ Map<String, ?> all = followUp.getAll();
+ for (Map.Entry<String, ?> entry : all.entrySet()) {
+ String key = entry.getKey();
+
+ PeopleTileKey peopleTileKey = PeopleTileKey.fromString(key);
+ boolean restored = isReadyForRestore(mIPeopleManager, mPackageManager, peopleTileKey);
+ if (restored) {
+ if (DEBUG) Log.d(TAG, "Removing key from follow-up: " + key);
+ followUpEditor.remove(key);
+ continue;
+ }
+
+ if (DEBUG) Log.d(TAG, "Key should not be restored yet, try later: " + key);
+ try {
+ remainingWidgets.put(entry.getKey(), (Set<String>) entry.getValue());
+ } catch (Exception e) {
+ Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ }
+ }
+ return remainingWidgets;
+ }
+
+ /** Returns whether all shortcuts were restored or if enough time elapsed to cancel the job. */
+ public boolean shouldCancelJob(Map<String, Set<String>> remainingWidgets,
+ long start, long now) {
+ if (remainingWidgets.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "All widget storage was successfully restored.");
+ return true;
+ }
+
+ boolean oneDayHasPassed = (now - start) > CLEAN_UP_STORAGE_AFTER_DURATION;
+ if (oneDayHasPassed) {
+ if (DEBUG) {
+ Log.w(TAG, "One or more widgets were not properly restored, "
+ + "but cancelling job because it has been a day.");
+ }
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "There are still non-restored widgets, run job again.");
+ return false;
+ }
+
+ /** Cancels job and removes storage of any shortcut that was not restored. */
+ public void cancelJobAndClearRemainingWidgets(Map<String, Set<String>> remainingWidgets,
+ SharedPreferences.Editor followUpEditor, SharedPreferences sp) {
+ if (DEBUG) Log.d(TAG, "Cancelling follow up job.");
+ removeUnavailableShortcutsFromSharedStorage(remainingWidgets, sp);
+ followUpEditor.clear();
+ mJobScheduler.cancel(JOB_ID);
+ }
+
+ private void removeUnavailableShortcutsFromSharedStorage(Map<String,
+ Set<String>> remainingWidgets, SharedPreferences sp) {
+ for (Map.Entry<String, Set<String>> entry : remainingWidgets.entrySet()) {
+ PeopleTileKey peopleTileKey = PeopleTileKey.fromString(entry.getKey());
+ if (!PeopleTileKey.isValid(peopleTileKey)) {
+ Log.e(TAG, "Malformed peopleTileKey in follow-up file: " + entry.getKey());
+ continue;
+ }
+ Set<String> widgetIds;
+ try {
+ widgetIds = (Set<String>) entry.getValue();
+ } catch (Exception e) {
+ Log.e(TAG, "Malformed widget ids in follow-up file: " + e);
+ continue;
+ }
+ for (String id : widgetIds) {
+ int widgetId;
+ try {
+ widgetId = Integer.parseInt(id);
+ } catch (NumberFormatException ex) {
+ Log.e(TAG, "Malformed widget id in follow-up file: " + ex);
+ continue;
+ }
+
+ String contactUriString = sp.getString(String.valueOf(widgetId), EMPTY_STRING);
+ removeSharedPreferencesStorageForTile(
+ mContext, peopleTileKey, widgetId, contactUriString);
+ }
+ }
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 917a060f1f1d..dcab86bac406 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -25,6 +25,7 @@ import static com.android.systemui.people.NotificationHelper.shouldMatchNotifica
import android.annotation.Nullable;
import android.app.Notification;
+import android.app.backup.BackupManager;
import android.app.people.ConversationChannel;
import android.app.people.IPeopleManager;
import android.app.people.PeopleSpaceTile;
@@ -89,7 +90,7 @@ public class PeopleSpaceUtils {
/** Returns stored widgets for the conversation specified. */
public static Set<String> getStoredWidgetIds(SharedPreferences sp, PeopleTileKey key) {
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
return new HashSet<>();
}
return new HashSet<>(sp.getStringSet(key.toString(), new HashSet<>()));
@@ -97,19 +98,16 @@ public class PeopleSpaceUtils {
/** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */
public static void setSharedPreferencesStorageForTile(Context context, PeopleTileKey key,
- int appWidgetId, Uri contactUri) {
- if (!key.isValid()) {
+ int appWidgetId, Uri contactUri, BackupManager backupManager) {
+ if (!PeopleTileKey.isValid(key)) {
Log.e(TAG, "Not storing for invalid key");
return;
}
// Write relevant persisted storage.
SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId),
Context.MODE_PRIVATE);
- SharedPreferences.Editor widgetEditor = widgetSp.edit();
- widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, key.getPackageName());
- widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, key.getShortcutId());
- widgetEditor.putInt(PeopleSpaceUtils.USER_ID, key.getUserId());
- widgetEditor.apply();
+ SharedPreferencesHelper.setPeopleTileKey(widgetSp, key);
+
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sp.edit();
String contactUriString = contactUri == null ? EMPTY_STRING : contactUri.toString();
@@ -117,14 +115,18 @@ public class PeopleSpaceUtils {
// Don't overwrite existing widgets with the same key.
addAppWidgetIdForKey(sp, editor, appWidgetId, key.toString());
- addAppWidgetIdForKey(sp, editor, appWidgetId, contactUriString);
+ if (!TextUtils.isEmpty(contactUriString)) {
+ addAppWidgetIdForKey(sp, editor, appWidgetId, contactUriString);
+ }
editor.apply();
+ backupManager.dataChanged();
}
/** Removes stored data when tile is deleted. */
public static void removeSharedPreferencesStorageForTile(Context context, PeopleTileKey key,
int widgetId, String contactUriString) {
// Delete widgetId mapping to key.
+ if (DEBUG) Log.d(TAG, "Removing widget info from sharedPrefs");
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sp.edit();
editor.remove(String.valueOf(widgetId));
@@ -230,7 +232,7 @@ public class PeopleSpaceUtils {
*/
public static PeopleSpaceTile augmentTileFromNotification(Context context, PeopleSpaceTile tile,
PeopleTileKey key, NotificationEntry notificationEntry, int messagesCount,
- Optional<Integer> appWidgetId) {
+ Optional<Integer> appWidgetId, BackupManager backupManager) {
if (notificationEntry == null || notificationEntry.getSbn().getNotification() == null) {
if (DEBUG) Log.d(TAG, "Tile key: " + key.toString() + ". Notification is null");
return removeNotificationFields(tile);
@@ -246,7 +248,7 @@ public class PeopleSpaceUtils {
Uri contactUri = Uri.parse(uriFromNotification);
// Update storage.
setSharedPreferencesStorageForTile(context, new PeopleTileKey(tile), appWidgetId.get(),
- contactUri);
+ contactUri, backupManager);
// Update cached tile in-memory.
updatedTile.setContactUri(contactUri);
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 844a8c6b6583..730e85004936 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -330,11 +330,8 @@ public class PeopleTileViewHelper {
R.layout.people_tile_suppressed_layout);
}
Drawable appIcon = mContext.getDrawable(R.drawable.ic_conversation_icon);
- Bitmap appIconAsBitmap = convertDrawableToBitmap(appIcon);
- FastBitmapDrawable drawable = new FastBitmapDrawable(appIconAsBitmap);
- drawable.setIsDisabled(true);
- Bitmap convertedBitmap = convertDrawableToBitmap(drawable);
- views.setImageViewBitmap(R.id.icon, convertedBitmap);
+ Bitmap disabledBitmap = convertDrawableToDisabledBitmap(appIcon);
+ views.setImageViewBitmap(R.id.icon, disabledBitmap);
return views;
}
@@ -504,6 +501,11 @@ public class PeopleTileViewHelper {
}
private RemoteViews setLaunchIntents(RemoteViews views) {
+ if (!PeopleTileKey.isValid(mKey) || mTile == null) {
+ if (DEBUG) Log.d(TAG, "Skipping launch intent, Null tile or invalid key: " + mKey);
+ return views;
+ }
+
try {
Intent activityIntent = new Intent(mContext, LaunchConversationActivity.class);
activityIntent.addFlags(
@@ -1067,7 +1069,8 @@ public class PeopleTileViewHelper {
Icon icon = tile.getUserIcon();
if (icon == null) {
- return null;
+ Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge);
+ return convertDrawableToDisabledBitmap(placeholder);
}
PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
context.getPackageManager(),
@@ -1179,4 +1182,11 @@ public class PeopleTileViewHelper {
mAvatarSize = avatarSize;
}
}
+
+ private static Bitmap convertDrawableToDisabledBitmap(Drawable icon) {
+ Bitmap appIconAsBitmap = convertDrawableToBitmap(icon);
+ FastBitmapDrawable drawable = new FastBitmapDrawable(appIconAsBitmap);
+ drawable.setIsDisabled(true);
+ return convertDrawableToBitmap(drawable);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/SharedPreferencesHelper.java b/packages/SystemUI/src/com/android/systemui/people/SharedPreferencesHelper.java
new file mode 100644
index 000000000000..aef08fb421d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/SharedPreferencesHelper.java
@@ -0,0 +1,59 @@
+/*
+ * 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.systemui.people;
+
+import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
+import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
+
+import android.content.SharedPreferences;
+
+import com.android.systemui.people.widget.PeopleTileKey;
+
+/** Helper class for Conversations widgets SharedPreferences storage. */
+public class SharedPreferencesHelper {
+ /** Clears all storage from {@code sp}. */
+ public static void clear(SharedPreferences sp) {
+ SharedPreferences.Editor editor = sp.edit();
+ editor.clear();
+ editor.apply();
+ }
+
+ /** Sets {@code sp}'s storage to identify a {@link PeopleTileKey}. */
+ public static void setPeopleTileKey(SharedPreferences sp, PeopleTileKey key) {
+ setPeopleTileKey(sp, key.getShortcutId(), key.getUserId(), key.getPackageName());
+ }
+
+ /** Sets {@code sp}'s storage to identify a {@link PeopleTileKey}. */
+ public static void setPeopleTileKey(SharedPreferences sp, String shortcutId, int userId,
+ String packageName) {
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putString(SHORTCUT_ID, shortcutId);
+ editor.putInt(USER_ID, userId);
+ editor.putString(PACKAGE_NAME, packageName);
+ editor.apply();
+ }
+
+ /** Returns a {@link PeopleTileKey} based on storage from {@code sp}. */
+ public static PeopleTileKey getPeopleTileKey(SharedPreferences sp) {
+ String shortcutId = sp.getString(SHORTCUT_ID, null);
+ String packageName = sp.getString(PACKAGE_NAME, null);
+ int userId = sp.getInt(USER_ID, INVALID_USER_ID);
+ return new PeopleTileKey(shortcutId, userId, packageName);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
index b031637e4016..79318d69837d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -152,7 +152,7 @@ public class LaunchConversationActivity extends Activity {
launcherApps.startShortcut(
packageName, tileId, null, null, userHandle);
} catch (Exception e) {
- Log.e(TAG, "Exception:" + e);
+ Log.e(TAG, "Exception launching shortcut:" + e);
}
} else {
if (DEBUG) Log.d(TAG, "Trying to launch conversation with null shortcutInfo.");
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
new file mode 100644
index 000000000000..d8c96dd182b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
@@ -0,0 +1,508 @@
+/*
+ * 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.systemui.people.widget;
+
+import static com.android.systemui.people.PeopleBackupFollowUpJob.SHARED_FOLLOW_UP;
+import static com.android.systemui.people.PeopleSpaceUtils.DEBUG;
+import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
+
+import android.app.backup.BackupDataInputStream;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.SharedPreferencesBackupHelper;
+import android.app.people.IPeopleManager;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.people.PeopleBackupFollowUpJob;
+import com.android.systemui.people.SharedPreferencesHelper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Helper class to backup and restore Conversations widgets storage.
+ * It is used by SystemUI's BackupHelper agent.
+ * TODO(b/192334798): Lock access to storage using PeopleSpaceWidgetManager's lock.
+ */
+public class PeopleBackupHelper extends SharedPreferencesBackupHelper {
+ private static final String TAG = "PeopleBackupHelper";
+
+ public static final String ADD_USER_ID_TO_URI = "add_user_id_to_uri_";
+ public static final String SHARED_BACKUP = "shared_backup";
+
+ private final Context mContext;
+ private final UserHandle mUserHandle;
+ private final PackageManager mPackageManager;
+ private final IPeopleManager mIPeopleManager;
+ private final AppWidgetManager mAppWidgetManager;
+
+ /**
+ * Types of entries stored in the default SharedPreferences file for Conversation widgets.
+ * Widget ID corresponds to a pair [widgetId, contactURI].
+ * PeopleTileKey corresponds to a pair [PeopleTileKey, {widgetIds}].
+ * Contact URI corresponds to a pair [Contact URI, {widgetIds}].
+ */
+ enum SharedFileEntryType {
+ UNKNOWN,
+ WIDGET_ID,
+ PEOPLE_TILE_KEY,
+ CONTACT_URI
+ }
+
+ /**
+ * Returns the file names that should be backed up and restored by SharedPreferencesBackupHelper
+ * infrastructure.
+ */
+ public static List<String> getFilesToBackup() {
+ return Collections.singletonList(SHARED_BACKUP);
+ }
+
+ public PeopleBackupHelper(Context context, UserHandle userHandle,
+ String[] sharedPreferencesKey) {
+ super(context, sharedPreferencesKey);
+ mContext = context;
+ mUserHandle = userHandle;
+ mPackageManager = context.getPackageManager();
+ mIPeopleManager = IPeopleManager.Stub.asInterface(
+ ServiceManager.getService(Context.PEOPLE_SERVICE));
+ mAppWidgetManager = AppWidgetManager.getInstance(context);
+ }
+
+ @VisibleForTesting
+ public PeopleBackupHelper(Context context, UserHandle userHandle,
+ String[] sharedPreferencesKey, PackageManager packageManager,
+ IPeopleManager peopleManager) {
+ super(context, sharedPreferencesKey);
+ mContext = context;
+ mUserHandle = userHandle;
+ mPackageManager = packageManager;
+ mIPeopleManager = peopleManager;
+ mAppWidgetManager = AppWidgetManager.getInstance(context);
+ }
+
+ /**
+ * Reads values from default storage, backs them up appropriately to a specified backup file,
+ * and calls super's performBackup, which backs up the values of the backup file.
+ */
+ @Override
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ if (DEBUG) Log.d(TAG, "Backing up conversation widgets, writing to: " + SHARED_BACKUP);
+ // Open default value for readings values.
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ if (sp.getAll().isEmpty()) {
+ if (DEBUG) Log.d(TAG, "No information to be backed up, finishing.");
+ return;
+ }
+
+ // Open backup file for writing.
+ SharedPreferences backupSp = mContext.getSharedPreferences(
+ SHARED_BACKUP, Context.MODE_PRIVATE);
+ SharedPreferences.Editor backupEditor = backupSp.edit();
+ backupEditor.clear();
+
+ // Fetch Conversations widgets corresponding to this user.
+ List<String> existingWidgets = getExistingWidgetsForUser(mUserHandle.getIdentifier());
+ if (existingWidgets.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "No existing Conversations widgets, returning.");
+ return;
+ }
+
+ // Writes each entry to backup file.
+ sp.getAll().entrySet().forEach(entry -> backupKey(entry, backupEditor, existingWidgets));
+ backupEditor.apply();
+
+ super.performBackup(oldState, data, newState);
+ }
+
+ /**
+ * Restores backed up values to backup file via super's restoreEntity, then transfers them
+ * back to regular storage. Restore operations for each users are done in sequence, so we can
+ * safely use the same backup file names.
+ */
+ @Override
+ public void restoreEntity(BackupDataInputStream data) {
+ if (DEBUG) Log.d(TAG, "Restoring Conversation widgets.");
+ super.restoreEntity(data);
+
+ // Open backup file for reading values.
+ SharedPreferences backupSp = mContext.getSharedPreferences(
+ SHARED_BACKUP, Context.MODE_PRIVATE);
+
+ // Open default file and follow-up file for writing.
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ SharedPreferences.Editor editor = sp.edit();
+ SharedPreferences followUp = mContext.getSharedPreferences(
+ SHARED_FOLLOW_UP, Context.MODE_PRIVATE);
+ SharedPreferences.Editor followUpEditor = followUp.edit();
+
+ // Writes each entry back to default value.
+ boolean shouldScheduleJob = false;
+ for (Map.Entry<String, ?> entry : backupSp.getAll().entrySet()) {
+ boolean restored = restoreKey(entry, editor, followUpEditor, backupSp);
+ if (!restored) {
+ shouldScheduleJob = true;
+ }
+ }
+
+ editor.apply();
+ followUpEditor.apply();
+ SharedPreferencesHelper.clear(backupSp);
+
+ // If any of the widgets is not yet available, schedule a follow-up job to check later.
+ if (shouldScheduleJob) {
+ if (DEBUG) Log.d(TAG, "At least one shortcut is not available, scheduling follow-up.");
+ PeopleBackupFollowUpJob.scheduleJob(mContext);
+ }
+
+ updateWidgets(mContext);
+ }
+
+ /** Backs up an entry from default file to backup file. */
+ public void backupKey(Map.Entry<String, ?> entry, SharedPreferences.Editor backupEditor,
+ List<String> existingWidgets) {
+ String key = entry.getKey();
+ if (TextUtils.isEmpty(key)) {
+ return;
+ }
+
+ SharedFileEntryType entryType = getEntryType(entry);
+ switch(entryType) {
+ case WIDGET_ID:
+ backupWidgetIdKey(key, String.valueOf(entry.getValue()), backupEditor,
+ existingWidgets);
+ break;
+ case PEOPLE_TILE_KEY:
+ backupPeopleTileKey(key, (Set<String>) entry.getValue(), backupEditor,
+ existingWidgets);
+ break;
+ case CONTACT_URI:
+ backupContactUriKey(key, (Set<String>) entry.getValue(), backupEditor);
+ break;
+ case UNKNOWN:
+ default:
+ Log.w(TAG, "Key not identified, skipping: " + key);
+ }
+ }
+
+ /**
+ * Tries to restore an entry from backup file to default file.
+ * Returns true if restore is finished, false if it needs to be checked later.
+ */
+ boolean restoreKey(Map.Entry<String, ?> entry, SharedPreferences.Editor editor,
+ SharedPreferences.Editor followUpEditor, SharedPreferences backupSp) {
+ String key = entry.getKey();
+ SharedFileEntryType keyType = getEntryType(entry);
+ int storedUserId = backupSp.getInt(ADD_USER_ID_TO_URI + key, INVALID_USER_ID);
+ switch (keyType) {
+ case WIDGET_ID:
+ restoreWidgetIdKey(key, String.valueOf(entry.getValue()), editor, storedUserId);
+ return true;
+ case PEOPLE_TILE_KEY:
+ return restorePeopleTileKeyAndCorrespondingWidgetFile(
+ key, (Set<String>) entry.getValue(), editor, followUpEditor);
+ case CONTACT_URI:
+ restoreContactUriKey(key, (Set<String>) entry.getValue(), editor, storedUserId);
+ return true;
+ case UNKNOWN:
+ default:
+ Log.e(TAG, "Key not identified, skipping:" + key);
+ return true;
+ }
+ }
+
+ /**
+ * Backs up a [widgetId, contactURI] pair, if widget id corresponds to current user.
+ * If contact URI has a user id, stores it so it can be re-added on restore.
+ */
+ private void backupWidgetIdKey(String key, String uriString, SharedPreferences.Editor editor,
+ List<String> existingWidgets) {
+ if (!existingWidgets.contains(key)) {
+ if (DEBUG) Log.d(TAG, "Widget: " + key + " does't correspond to this user, skipping.");
+ return;
+ }
+ Uri uri = Uri.parse(uriString);
+ if (ContentProvider.uriHasUserId(uri)) {
+ if (DEBUG) Log.d(TAG, "Contact URI value has user ID, removing from: " + uri);
+ int userId = ContentProvider.getUserIdFromUri(uri);
+ editor.putInt(ADD_USER_ID_TO_URI + key, userId);
+ uri = ContentProvider.getUriWithoutUserId(uri);
+ }
+ if (DEBUG) Log.d(TAG, "Backing up widgetId key: " + key + " . Value: " + uri.toString());
+ editor.putString(key, uri.toString());
+ }
+
+ /** Restores a [widgetId, contactURI] pair, and a potential {@code storedUserId}. */
+ private void restoreWidgetIdKey(String key, String uriString, SharedPreferences.Editor editor,
+ int storedUserId) {
+ Uri uri = Uri.parse(uriString);
+ if (storedUserId != INVALID_USER_ID) {
+ uri = ContentProvider.createContentUriForUser(uri, UserHandle.of(storedUserId));
+ if (DEBUG) Log.d(TAG, "UserId was removed from URI on back up, re-adding as:" + uri);
+
+ }
+ if (DEBUG) Log.d(TAG, "Restoring widgetId key: " + key + " . Value: " + uri.toString());
+ editor.putString(key, uri.toString());
+ }
+
+ /**
+ * Backs up a [PeopleTileKey, {widgetIds}] pair, if PeopleTileKey's user is the same as current
+ * user, stripping out the user id.
+ */
+ private void backupPeopleTileKey(String key, Set<String> widgetIds,
+ SharedPreferences.Editor editor, List<String> existingWidgets) {
+ PeopleTileKey peopleTileKey = PeopleTileKey.fromString(key);
+ if (peopleTileKey.getUserId() != mUserHandle.getIdentifier()) {
+ if (DEBUG) Log.d(TAG, "PeopleTileKey corresponds to different user, skipping backup.");
+ return;
+ }
+
+ Set<String> filteredWidgets = widgetIds.stream()
+ .filter(id -> existingWidgets.contains(id))
+ .collect(Collectors.toSet());
+ if (filteredWidgets.isEmpty()) {
+ return;
+ }
+
+ peopleTileKey.setUserId(INVALID_USER_ID);
+ if (DEBUG) {
+ Log.d(TAG, "Backing up PeopleTileKey key: " + peopleTileKey.toString() + ". Value: "
+ + filteredWidgets);
+ }
+ editor.putStringSet(peopleTileKey.toString(), filteredWidgets);
+ }
+
+ /**
+ * Restores a [PeopleTileKey, {widgetIds}] pair, restoring the user id. Checks if the
+ * corresponding shortcut exists, and if not, we should schedule a follow up to check later.
+ * Also restores corresponding [widgetId, PeopleTileKey], which is not backed up since the
+ * information can be inferred from this.
+ * Returns true if restore is finished, false if we should check if shortcut is available later.
+ */
+ private boolean restorePeopleTileKeyAndCorrespondingWidgetFile(String key,
+ Set<String> widgetIds, SharedPreferences.Editor editor,
+ SharedPreferences.Editor followUpEditor) {
+ PeopleTileKey peopleTileKey = PeopleTileKey.fromString(key);
+ // Should never happen, as type of key has been checked.
+ if (peopleTileKey == null) {
+ if (DEBUG) Log.d(TAG, "PeopleTileKey key to be restored is null, skipping.");
+ return true;
+ }
+
+ peopleTileKey.setUserId(mUserHandle.getIdentifier());
+ if (!PeopleTileKey.isValid(peopleTileKey)) {
+ if (DEBUG) Log.d(TAG, "PeopleTileKey key to be restored is not valid, skipping.");
+ return true;
+ }
+
+ boolean restored = isReadyForRestore(
+ mIPeopleManager, mPackageManager, peopleTileKey);
+ if (!restored) {
+ if (DEBUG) Log.d(TAG, "Adding key to follow-up storage: " + peopleTileKey.toString());
+ // Follow-up file stores shortcuts that need to be checked later, and possibly wiped
+ // from our storage.
+ followUpEditor.putStringSet(peopleTileKey.toString(), widgetIds);
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Restoring PeopleTileKey key: " + peopleTileKey.toString() + " . Value: "
+ + widgetIds);
+ }
+ editor.putStringSet(peopleTileKey.toString(), widgetIds);
+ restoreWidgetIdFiles(mContext, widgetIds, peopleTileKey);
+ return restored;
+ }
+
+ /**
+ * Backs up a [contactURI, {widgetIds}] pair. If contactURI contains a userId, we back up
+ * this entry in the corresponding user. If it doesn't, we back it up as user 0.
+ * If contact URI has a user id, stores it so it can be re-added on restore.
+ * We do not take existing widgets for this user into consideration.
+ */
+ private void backupContactUriKey(String key, Set<String> widgetIds,
+ SharedPreferences.Editor editor) {
+ Uri uri = Uri.parse(String.valueOf(key));
+ if (ContentProvider.uriHasUserId(uri)) {
+ int userId = ContentProvider.getUserIdFromUri(uri);
+ if (DEBUG) Log.d(TAG, "Contact URI has user Id: " + userId);
+ if (userId == mUserHandle.getIdentifier()) {
+ uri = ContentProvider.getUriWithoutUserId(uri);
+ if (DEBUG) {
+ Log.d(TAG, "Backing up contactURI key: " + uri.toString() + " . Value: "
+ + widgetIds);
+ }
+ editor.putInt(ADD_USER_ID_TO_URI + uri.toString(), userId);
+ editor.putStringSet(uri.toString(), widgetIds);
+ } else {
+ if (DEBUG) Log.d(TAG, "ContactURI corresponds to different user, skipping.");
+ }
+ } else if (mUserHandle.isSystem()) {
+ if (DEBUG) {
+ Log.d(TAG, "Backing up contactURI key: " + uri.toString() + " . Value: "
+ + widgetIds);
+ }
+ editor.putStringSet(uri.toString(), widgetIds);
+ }
+ }
+
+ /** Restores a [contactURI, {widgetIds}] pair, and a potential {@code storedUserId}. */
+ private void restoreContactUriKey(String key, Set<String> widgetIds,
+ SharedPreferences.Editor editor, int storedUserId) {
+ Uri uri = Uri.parse(key);
+ if (storedUserId != INVALID_USER_ID) {
+ uri = ContentProvider.createContentUriForUser(uri, UserHandle.of(storedUserId));
+ if (DEBUG) Log.d(TAG, "UserId was removed from URI on back up, re-adding as:" + uri);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Restoring contactURI key: " + uri.toString() + " . Value: " + widgetIds);
+ }
+ editor.putStringSet(uri.toString(), widgetIds);
+ }
+
+ /** Restores the widget-specific files that contain PeopleTileKey information. */
+ public static void restoreWidgetIdFiles(Context context, Set<String> widgetIds,
+ PeopleTileKey key) {
+ for (String id : widgetIds) {
+ if (DEBUG) Log.d(TAG, "Restoring widget Id file: " + id + " . Value: " + key);
+ SharedPreferences dest = context.getSharedPreferences(id, Context.MODE_PRIVATE);
+ SharedPreferencesHelper.setPeopleTileKey(dest, key);
+ }
+ }
+
+ private List<String> getExistingWidgetsForUser(int userId) {
+ List<String> existingWidgets = new ArrayList<>();
+ int[] ids = mAppWidgetManager.getAppWidgetIds(
+ new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
+ for (int id : ids) {
+ String idString = String.valueOf(id);
+ SharedPreferences sp = mContext.getSharedPreferences(idString, Context.MODE_PRIVATE);
+ if (sp.getInt(USER_ID, INVALID_USER_ID) == userId) {
+ existingWidgets.add(idString);
+ }
+ }
+ if (DEBUG) Log.d(TAG, "Existing widgets: " + existingWidgets);
+ return existingWidgets;
+ }
+
+ /**
+ * Returns whether {@code key} corresponds to a shortcut that is ready for restore, either
+ * because it is available or because it never will be. If not ready, we schedule a job to check
+ * again later.
+ */
+ public static boolean isReadyForRestore(IPeopleManager peopleManager,
+ PackageManager packageManager, PeopleTileKey key) {
+ if (DEBUG) Log.d(TAG, "Checking if we should schedule a follow up job : " + key);
+ if (!PeopleTileKey.isValid(key)) {
+ if (DEBUG) Log.d(TAG, "Key is invalid, should not follow up.");
+ return true;
+ }
+
+ try {
+ PackageInfo info = packageManager.getPackageInfoAsUser(
+ key.getPackageName(), 0, key.getUserId());
+ } catch (PackageManager.NameNotFoundException e) {
+ if (DEBUG) Log.d(TAG, "Package is not installed, should follow up.");
+ return false;
+ }
+
+ try {
+ boolean isConversation = peopleManager.isConversation(
+ key.getPackageName(), key.getUserId(), key.getShortcutId());
+ if (DEBUG) {
+ Log.d(TAG, "Checked if shortcut exists, should follow up: " + !isConversation);
+ }
+ return isConversation;
+ } catch (Exception e) {
+ if (DEBUG) Log.d(TAG, "Error checking if backed up info is a shortcut.");
+ return false;
+ }
+ }
+
+ /** Parses default file {@code entry} to determine the entry's type.*/
+ public static SharedFileEntryType getEntryType(Map.Entry<String, ?> entry) {
+ String key = entry.getKey();
+ if (key == null) {
+ return SharedFileEntryType.UNKNOWN;
+ }
+
+ try {
+ int id = Integer.parseInt(key);
+ try {
+ String contactUri = (String) entry.getValue();
+ } catch (Exception e) {
+ Log.w(TAG, "Malformed value, skipping:" + entry.getValue());
+ return SharedFileEntryType.UNKNOWN;
+ }
+ return SharedFileEntryType.WIDGET_ID;
+ } catch (NumberFormatException ignored) { }
+
+ try {
+ Set<String> widgetIds = (Set<String>) entry.getValue();
+ } catch (Exception e) {
+ Log.w(TAG, "Malformed value, skipping:" + entry.getValue());
+ return SharedFileEntryType.UNKNOWN;
+ }
+
+ PeopleTileKey peopleTileKey = PeopleTileKey.fromString(key);
+ if (peopleTileKey != null) {
+ return SharedFileEntryType.PEOPLE_TILE_KEY;
+ }
+
+ try {
+ Uri uri = Uri.parse(key);
+ return SharedFileEntryType.CONTACT_URI;
+ } catch (Exception e) {
+ return SharedFileEntryType.UNKNOWN;
+ }
+ }
+
+ /** Sends a broadcast to update the existing Conversation widgets. */
+ public static void updateWidgets(Context context) {
+ int[] widgetIds = AppWidgetManager.getInstance(context)
+ .getAppWidgetIds(new ComponentName(context, PeopleSpaceWidgetProvider.class));
+ if (DEBUG) {
+ for (int id : widgetIds) {
+ Log.d(TAG, "Calling update to widget: " + id);
+ }
+ }
+ if (widgetIds != null && widgetIds.length != 0) {
+ Intent intent = new Intent(context, PeopleSpaceWidgetProvider.class);
+ intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds);
+ context.sendBroadcast(intent);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 62a0df270698..72cddd0b4b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -22,6 +22,7 @@ import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.content.Intent.ACTION_BOOT_COMPLETED;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
@@ -29,6 +30,7 @@ import static com.android.systemui.people.NotificationHelper.getContactUri;
import static com.android.systemui.people.NotificationHelper.getHighestPriorityNotification;
import static com.android.systemui.people.NotificationHelper.shouldFilterOut;
import static com.android.systemui.people.NotificationHelper.shouldMatchNotificationByUri;
+import static com.android.systemui.people.PeopleBackupFollowUpJob.SHARED_FOLLOW_UP;
import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
@@ -38,6 +40,7 @@ import static com.android.systemui.people.PeopleSpaceUtils.augmentTileFromNotifi
import static com.android.systemui.people.PeopleSpaceUtils.getMessagesCount;
import static com.android.systemui.people.PeopleSpaceUtils.getNotificationsByUri;
import static com.android.systemui.people.PeopleSpaceUtils.removeNotificationFields;
+import static com.android.systemui.people.widget.PeopleBackupHelper.getEntryType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -46,6 +49,8 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
+import android.app.backup.BackupManager;
+import android.app.job.JobScheduler;
import android.app.people.ConversationChannel;
import android.app.people.IPeopleManager;
import android.app.people.PeopleManager;
@@ -84,8 +89,10 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.people.NotificationHelper;
+import com.android.systemui.people.PeopleBackupFollowUpJob;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.people.PeopleTileViewHelper;
+import com.android.systemui.people.SharedPreferencesHelper;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -93,6 +100,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.wm.shell.bubbles.Bubbles;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -126,6 +134,7 @@ public class PeopleSpaceWidgetManager {
private Optional<Bubbles> mBubblesOptional;
private UserManager mUserManager;
private PeopleSpaceWidgetManager mManager;
+ private BackupManager mBackupManager;
public UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
private NotificationManager mNotificationManager;
private BroadcastDispatcher mBroadcastDispatcher;
@@ -164,6 +173,7 @@ public class PeopleSpaceWidgetManager {
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
mBubblesOptional = bubblesOptional;
mUserManager = userManager;
+ mBackupManager = new BackupManager(context);
mNotificationManager = notificationManager;
mManager = this;
mBroadcastDispatcher = broadcastDispatcher;
@@ -189,6 +199,7 @@ public class PeopleSpaceWidgetManager {
null /* executor */, UserHandle.ALL);
IntentFilter perAppFilter = new IntentFilter(ACTION_PACKAGE_REMOVED);
+ perAppFilter.addAction(ACTION_PACKAGE_ADDED);
perAppFilter.addDataScheme("package");
// BroadcastDispatcher doesn't allow data schemes.
mContext.registerReceiver(mBaseBroadcastReceiver, perAppFilter);
@@ -224,7 +235,7 @@ public class PeopleSpaceWidgetManager {
AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager,
PeopleManager peopleManager, LauncherApps launcherApps,
NotificationEntryManager notificationEntryManager, PackageManager packageManager,
- Optional<Bubbles> bubblesOptional, UserManager userManager,
+ Optional<Bubbles> bubblesOptional, UserManager userManager, BackupManager backupManager,
INotificationManager iNotificationManager, NotificationManager notificationManager,
@Background Executor executor) {
mContext = context;
@@ -236,6 +247,7 @@ public class PeopleSpaceWidgetManager {
mPackageManager = packageManager;
mBubblesOptional = bubblesOptional;
mUserManager = userManager;
+ mBackupManager = backupManager;
mINotificationManager = iNotificationManager;
mNotificationManager = notificationManager;
mManager = this;
@@ -257,8 +269,6 @@ public class PeopleSpaceWidgetManager {
if (DEBUG) Log.d(TAG, "no widgets to update");
return;
}
-
- if (DEBUG) Log.d(TAG, "updating " + widgetIds.length + " widgets: " + widgetIds);
synchronized (mLock) {
updateSingleConversationWidgets(widgetIds);
}
@@ -274,6 +284,7 @@ public class PeopleSpaceWidgetManager {
public void updateSingleConversationWidgets(int[] appWidgetIds) {
Map<Integer, PeopleSpaceTile> widgetIdToTile = new HashMap<>();
for (int appWidgetId : appWidgetIds) {
+ if (DEBUG) Log.d(TAG, "Updating widget: " + appWidgetId);
PeopleSpaceTile tile = getTileForExistingWidget(appWidgetId);
if (tile == null) {
Log.e(TAG, "Matching conversation not found for shortcut ID");
@@ -293,7 +304,8 @@ public class PeopleSpaceWidgetManager {
private void updateAppWidgetViews(int appWidgetId, PeopleSpaceTile tile, Bundle options) {
PeopleTileKey key = getKeyFromStorageByWidgetId(appWidgetId);
if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + " for: " + key.toString());
- if (!key.isValid()) {
+
+ if (!PeopleTileKey.isValid(key)) {
Log.e(TAG, "Cannot update invalid widget");
return;
}
@@ -301,6 +313,7 @@ public class PeopleSpaceWidgetManager {
options, key);
// Tell the AppWidgetManager to perform an update on the current app widget.
+ if (DEBUG) Log.d(TAG, "Calling update widget for widgetId: " + appWidgetId);
mAppWidgetManager.updateAppWidget(appWidgetId, views);
}
@@ -314,6 +327,9 @@ public class PeopleSpaceWidgetManager {
/** Updates tile in app widget options and the current view. */
public void updateAppWidgetOptionsAndView(int appWidgetId, PeopleSpaceTile tile) {
+ if (tile == null) {
+ if (DEBUG) Log.w(TAG, "Storing null tile");
+ }
synchronized (mTiles) {
mTiles.put(appWidgetId, tile);
}
@@ -368,7 +384,7 @@ public class PeopleSpaceWidgetManager {
@Nullable
public PeopleSpaceTile getTileFromPersistentStorage(PeopleTileKey key, int appWidgetId) throws
PackageManager.NameNotFoundException {
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
Log.e(TAG, "PeopleTileKey invalid: " + key.toString());
return null;
}
@@ -430,7 +446,8 @@ public class PeopleSpaceWidgetManager {
try {
PeopleTileKey key = new PeopleTileKey(
sbn.getShortcutId(), sbn.getUser().getIdentifier(), sbn.getPackageName());
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
+ Log.d(TAG, "Sbn doesn't contain valid PeopleTileKey: " + key.toString());
return;
}
int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
@@ -561,7 +578,7 @@ public class PeopleSpaceWidgetManager {
if (DEBUG) Log.d(TAG, "Augmenting tile from notification, key: " + key.toString());
return augmentTileFromNotification(mContext, tile, key, highestPriority, messagesCount,
- appWidgetId);
+ appWidgetId, mBackupManager);
}
/** Returns an augmented tile for an existing widget. */
@@ -588,7 +605,7 @@ public class PeopleSpaceWidgetManager {
/** Returns stored widgets for the conversation specified. */
public Set<String> getMatchingKeyWidgetIds(PeopleTileKey key) {
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
return new HashSet<>();
}
return new HashSet<>(mSharedPrefs.getStringSet(key.toString(), new HashSet<>()));
@@ -776,7 +793,7 @@ public class PeopleSpaceWidgetManager {
// PeopleTileKey arguments.
if (DEBUG) Log.d(TAG, "onAppWidgetOptionsChanged called for widget: " + appWidgetId);
PeopleTileKey optionsKey = AppWidgetOptionsHelper.getPeopleTileKeyFromBundle(newOptions);
- if (optionsKey.isValid()) {
+ if (PeopleTileKey.isValid(optionsKey)) {
if (DEBUG) {
Log.d(TAG, "PeopleTileKey was present in Options, shortcutId: "
+ optionsKey.getShortcutId());
@@ -808,7 +825,7 @@ public class PeopleSpaceWidgetManager {
existingKeyIfStored = getKeyFromStorageByWidgetId(appWidgetId);
}
// Delete previous storage if the widget already existed and is just reconfigured.
- if (existingKeyIfStored.isValid()) {
+ if (PeopleTileKey.isValid(existingKeyIfStored)) {
if (DEBUG) Log.d(TAG, "Remove previous storage for widget: " + appWidgetId);
deleteWidgets(new int[]{appWidgetId});
} else {
@@ -820,7 +837,7 @@ public class PeopleSpaceWidgetManager {
synchronized (mLock) {
if (DEBUG) Log.d(TAG, "Add storage for : " + key.toString());
PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId,
- tile.getContactUri());
+ tile.getContactUri(), mBackupManager);
}
if (DEBUG) Log.d(TAG, "Ensure listener is registered for widget: " + appWidgetId);
registerConversationListenerIfNeeded(appWidgetId, key);
@@ -838,7 +855,7 @@ public class PeopleSpaceWidgetManager {
/** Registers a conversation listener for {@code appWidgetId} if not already registered. */
public void registerConversationListenerIfNeeded(int widgetId, PeopleTileKey key) {
// Retrieve storage needed for registration.
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
if (DEBUG) Log.w(TAG, "Could not register listener for widget: " + widgetId);
return;
}
@@ -887,7 +904,7 @@ public class PeopleSpaceWidgetManager {
widgetSp.getString(SHORTCUT_ID, null),
widgetSp.getInt(USER_ID, INVALID_USER_ID),
widgetSp.getString(PACKAGE_NAME, null));
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
if (DEBUG) Log.e(TAG, "Could not delete " + widgetId);
return;
}
@@ -1053,6 +1070,7 @@ public class PeopleSpaceWidgetManager {
return;
}
for (int appWidgetId : appWidgetIds) {
+ if (DEBUG) Log.d(TAG, "Updating widget from broadcast, widget id: " + appWidgetId);
PeopleSpaceTile existingTile = null;
PeopleSpaceTile updatedTile = null;
try {
@@ -1060,7 +1078,7 @@ public class PeopleSpaceWidgetManager {
existingTile = getTileForExistingWidgetThrowing(appWidgetId);
if (existingTile == null) {
Log.e(TAG, "Matching conversation not found for shortcut ID");
- return;
+ continue;
}
updatedTile = getTileWithCurrentState(existingTile, entryPoint);
updateAppWidgetOptionsAndView(appWidgetId, updatedTile);
@@ -1068,6 +1086,14 @@ public class PeopleSpaceWidgetManager {
} catch (PackageManager.NameNotFoundException e) {
// Delete data for uninstalled widgets.
Log.e(TAG, "Package no longer found for tile: " + e);
+ JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
+ if (jobScheduler != null
+ && jobScheduler.getPendingJob(PeopleBackupFollowUpJob.JOB_ID) != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Device was recently restored, wait before deleting storage.");
+ }
+ continue;
+ }
synchronized (mLock) {
updateAppWidgetOptionsAndView(appWidgetId, updatedTile);
}
@@ -1185,4 +1211,139 @@ public class PeopleSpaceWidgetManager {
return PeopleSpaceTile.BLOCK_CONVERSATIONS;
}
}
+
+ /**
+ * Modifies widgets storage after a restore operation, since widget ids get remapped on restore.
+ * This is guaranteed to run after the PeopleBackupHelper restore operation.
+ */
+ public void remapWidgets(int[] oldWidgetIds, int[] newWidgetIds) {
+ if (DEBUG) {
+ Log.d(TAG, "Remapping widgets, old: " + Arrays.toString(oldWidgetIds) + ". new: "
+ + Arrays.toString(newWidgetIds));
+ }
+
+ Map<String, String> widgets = new HashMap<>();
+ for (int i = 0; i < oldWidgetIds.length; i++) {
+ widgets.put(String.valueOf(oldWidgetIds[i]), String.valueOf(newWidgetIds[i]));
+ }
+
+ remapWidgetFiles(widgets);
+ remapSharedFile(widgets);
+ remapFollowupFile(widgets);
+
+ int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
+ new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
+ Bundle b = new Bundle();
+ b.putBoolean(AppWidgetManager.OPTION_APPWIDGET_RESTORE_COMPLETED, true);
+ for (int id : widgetIds) {
+ if (DEBUG) Log.d(TAG, "Setting widget as restored, widget id:" + id);
+ mAppWidgetManager.updateAppWidgetOptions(id, b);
+ }
+
+ updateWidgets(widgetIds);
+ }
+
+ /** Remaps widget ids in widget specific files. */
+ public void remapWidgetFiles(Map<String, String> widgets) {
+ if (DEBUG) Log.d(TAG, "Remapping widget files");
+ for (Map.Entry<String, String> entry : widgets.entrySet()) {
+ String from = String.valueOf(entry.getKey());
+ String to = String.valueOf(entry.getValue());
+
+ SharedPreferences src = mContext.getSharedPreferences(from, Context.MODE_PRIVATE);
+ PeopleTileKey key = SharedPreferencesHelper.getPeopleTileKey(src);
+ if (PeopleTileKey.isValid(key)) {
+ if (DEBUG) {
+ Log.d(TAG, "Moving PeopleTileKey: " + key.toString() + " from file: "
+ + from + ", to file: " + to);
+ }
+ SharedPreferences dest = mContext.getSharedPreferences(to, Context.MODE_PRIVATE);
+ SharedPreferencesHelper.setPeopleTileKey(dest, key);
+ SharedPreferencesHelper.clear(src);
+ }
+ }
+ }
+
+ /** Remaps widget ids in default shared storage. */
+ public void remapSharedFile(Map<String, String> widgets) {
+ if (DEBUG) Log.d(TAG, "Remapping shared file");
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ SharedPreferences.Editor editor = sp.edit();
+ Map<String, ?> all = sp.getAll();
+ for (Map.Entry<String, ?> entry : all.entrySet()) {
+ String key = entry.getKey();
+ PeopleBackupHelper.SharedFileEntryType keyType = getEntryType(entry);
+ if (DEBUG) Log.d(TAG, "Remapping key:" + key);
+ switch (keyType) {
+ case WIDGET_ID:
+ String newId = widgets.get(key);
+ if (TextUtils.isEmpty(newId)) {
+ Log.w(TAG, "Key is widget id without matching new id, skipping: " + key);
+ break;
+ }
+ if (DEBUG) Log.d(TAG, "Key is widget id: " + key + ", replace with: " + newId);
+ try {
+ editor.putString(newId, (String) entry.getValue());
+ } catch (Exception e) {
+ Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ }
+ editor.remove(key);
+ break;
+ case PEOPLE_TILE_KEY:
+ case CONTACT_URI:
+ Set<String> oldWidgetIds;
+ try {
+ oldWidgetIds = (Set<String>) entry.getValue();
+ } catch (Exception e) {
+ Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ editor.remove(key);
+ break;
+ }
+ Set<String> newWidgets = getNewWidgets(oldWidgetIds, widgets);
+ if (DEBUG) {
+ Log.d(TAG, "Key is PeopleTileKey or contact URI: " + key
+ + ", replace values with new ids: " + newWidgets);
+ }
+ editor.putStringSet(key, newWidgets);
+ break;
+ case UNKNOWN:
+ Log.e(TAG, "Key not identified:" + key);
+ }
+ }
+ editor.apply();
+ }
+
+ /** Remaps widget ids in follow-up job file. */
+ public void remapFollowupFile(Map<String, String> widgets) {
+ if (DEBUG) Log.d(TAG, "Remapping follow up file");
+ SharedPreferences followUp = mContext.getSharedPreferences(
+ SHARED_FOLLOW_UP, Context.MODE_PRIVATE);
+ SharedPreferences.Editor followUpEditor = followUp.edit();
+ Map<String, ?> followUpAll = followUp.getAll();
+ for (Map.Entry<String, ?> entry : followUpAll.entrySet()) {
+ String key = entry.getKey();
+ Set<String> oldWidgetIds;
+ try {
+ oldWidgetIds = (Set<String>) entry.getValue();
+ } catch (Exception e) {
+ Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ followUpEditor.remove(key);
+ continue;
+ }
+ Set<String> newWidgets = getNewWidgets(oldWidgetIds, widgets);
+ if (DEBUG) {
+ Log.d(TAG, "Follow up key: " + key + ", replace with new ids: " + newWidgets);
+ }
+ followUpEditor.putStringSet(key, newWidgets);
+ }
+ followUpEditor.apply();
+ }
+
+ private Set<String> getNewWidgets(Set<String> oldWidgets, Map<String, String> widgetsMapping) {
+ return oldWidgets
+ .stream()
+ .map(widgetsMapping::get)
+ .filter(id -> !TextUtils.isEmpty(id))
+ .collect(Collectors.toSet());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java
index a28da43a80b6..c4be197504be 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java
@@ -75,7 +75,7 @@ public class PeopleSpaceWidgetPinnedReceiver extends BroadcastReceiver {
String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, INVALID_USER_ID);
PeopleTileKey key = new PeopleTileKey(shortcutId, userId, packageName);
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
if (DEBUG) Log.w(TAG, "Skipping: key is not valid: " + key.toString());
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index 3522b76e6460..36939b735a07 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -70,6 +70,13 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider {
mPeopleSpaceWidgetManager.deleteWidgets(appWidgetIds);
}
+ @Override
+ public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
+ super.onRestored(context, oldWidgetIds, newWidgetIds);
+ ensurePeopleSpaceWidgetManagerInitialized();
+ mPeopleSpaceWidgetManager.remapWidgets(oldWidgetIds, newWidgetIds);
+ }
+
private void ensurePeopleSpaceWidgetManagerInitialized() {
mPeopleSpaceWidgetManager.init();
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java
index 319df85b4872..6e6ca254dee0 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java
@@ -25,6 +25,8 @@ import android.text.TextUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/** Class that encapsulates fields identifying a Conversation. */
public class PeopleTileKey {
@@ -32,6 +34,8 @@ public class PeopleTileKey {
private int mUserId;
private String mPackageName;
+ private static final Pattern KEY_PATTERN = Pattern.compile("(.+)/(-?\\d+)/(\\p{L}.*)");
+
public PeopleTileKey(String shortcutId, int userId, String packageName) {
mShortcutId = shortcutId;
mUserId = userId;
@@ -66,8 +70,12 @@ public class PeopleTileKey {
return mPackageName;
}
+ public void setUserId(int userId) {
+ mUserId = userId;
+ }
+
/** Returns whether PeopleTileKey is valid/well-formed. */
- public boolean isValid() {
+ private boolean validate() {
return !TextUtils.isEmpty(mShortcutId) && !TextUtils.isEmpty(mPackageName) && mUserId >= 0;
}
@@ -88,10 +96,31 @@ public class PeopleTileKey {
*/
@Override
public String toString() {
- if (!isValid()) return EMPTY_STRING;
return mShortcutId + "/" + mUserId + "/" + mPackageName;
}
+ /** Parses {@code key} into a {@link PeopleTileKey}. */
+ public static PeopleTileKey fromString(String key) {
+ if (key == null) {
+ return null;
+ }
+ Matcher m = KEY_PATTERN.matcher(key);
+ if (m.find()) {
+ try {
+ int userId = Integer.parseInt(m.group(2));
+ return new PeopleTileKey(m.group(1), userId, m.group(3));
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ /** Returns whether {@code key} is a valid {@link PeopleTileKey}. */
+ public static boolean isValid(PeopleTileKey key) {
+ return key != null && key.validate();
+ }
+
@Override
public boolean equals(Object other) {
if (this == other) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java
new file mode 100644
index 000000000000..00e012e5d30c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.systemui.people;
+
+import static com.android.systemui.people.PeopleBackupFollowUpJob.SHARED_FOLLOW_UP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobScheduler;
+import android.app.people.IPeopleManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.widget.PeopleTileKey;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class PeopleBackupFollowUpJobTest extends SysuiTestCase {
+ private static final String SHORTCUT_ID_1 = "101";
+ private static final String PACKAGE_NAME_1 = "package_name";
+ private static final int USER_ID_1 = 0;
+
+ private static final PeopleTileKey PEOPLE_TILE_KEY =
+ new PeopleTileKey(SHORTCUT_ID_1, USER_ID_1, PACKAGE_NAME_1);
+
+ private static final String WIDGET_ID_STRING = "3";
+ private static final String SECOND_WIDGET_ID_STRING = "12";
+ private static final Set<String> WIDGET_IDS = new HashSet<>(
+ Arrays.asList(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING));
+
+ private static final Uri URI = Uri.parse("fake_uri");
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private PackageInfo mPackageInfo;
+ @Mock
+ private IPeopleManager mIPeopleManager;
+ @Mock
+ private JobScheduler mJobScheduler;
+
+ private final SharedPreferences mSp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ private final SharedPreferences.Editor mEditor = mSp.edit();
+ private final SharedPreferences mFollowUpSp = mContext.getSharedPreferences(
+ SHARED_FOLLOW_UP, Context.MODE_PRIVATE);
+ private final SharedPreferences.Editor mFollowUpEditor = mFollowUpSp.edit();
+ private final SharedPreferences mWidgetIdSp = mContext.getSharedPreferences(
+ WIDGET_ID_STRING, Context.MODE_PRIVATE);
+ private final SharedPreferences mSecondWidgetIdSp = mContext.getSharedPreferences(
+ SECOND_WIDGET_ID_STRING, Context.MODE_PRIVATE);
+
+ private PeopleBackupFollowUpJob mPeopleBackupFollowUpJob;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mPackageManager.getPackageInfoAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(mPackageInfo);
+ when(mIPeopleManager.isConversation(any(), anyInt(), any())).thenReturn(true);
+
+ mPeopleBackupFollowUpJob = new PeopleBackupFollowUpJob();
+ mPeopleBackupFollowUpJob.setManagers(
+ mContext, mPackageManager, mIPeopleManager, mJobScheduler);
+ }
+
+ @After
+ public void tearDown() {
+ mEditor.clear().commit();
+ mFollowUpEditor.clear().commit();
+ mWidgetIdSp.edit().clear().commit();
+ mSecondWidgetIdSp.edit().clear().commit();
+ }
+
+ @Test
+ public void testProcessFollowUpFile_shouldFollowUp() throws RemoteException {
+ when(mIPeopleManager.isConversation(any(), anyInt(), any())).thenReturn(false);
+ mFollowUpEditor.putStringSet(PEOPLE_TILE_KEY.toString(), WIDGET_IDS);
+ mFollowUpEditor.apply();
+
+ Map<String, Set<String>> remainingWidgets =
+ mPeopleBackupFollowUpJob.processFollowUpFile(mFollowUpSp, mFollowUpEditor);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(remainingWidgets.size()).isEqualTo(1);
+ assertThat(remainingWidgets.get(PEOPLE_TILE_KEY.toString()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ assertThat(mFollowUpSp.getStringSet(PEOPLE_TILE_KEY.toString(), new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ }
+
+ @Test
+ public void testProcessFollowUpFile_shouldRestore() {
+ mFollowUpEditor.putStringSet(PEOPLE_TILE_KEY.toString(), WIDGET_IDS);
+ mFollowUpEditor.apply();
+
+ Map<String, Set<String>> remainingWidgets =
+ mPeopleBackupFollowUpJob.processFollowUpFile(mFollowUpSp, mFollowUpEditor);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(remainingWidgets).isEmpty();
+ assertThat(mFollowUpSp.getStringSet(PEOPLE_TILE_KEY.toString(), new HashSet<>())).isEmpty();
+ }
+
+ @Test
+ public void testShouldCancelJob_noRemainingWidgets_shouldCancel() {
+ assertThat(mPeopleBackupFollowUpJob.shouldCancelJob(
+ new HashMap<>(), 10, Duration.ofMinutes(1).toMillis())).isTrue();
+ }
+
+ @Test
+ public void testShouldCancelJob_noRemainingWidgets_longTimeElapsed_shouldCancel() {
+ assertThat(mPeopleBackupFollowUpJob.shouldCancelJob(
+ new HashMap<>(), 10, Duration.ofHours(25).toMillis())).isTrue();
+ }
+
+ @Test
+ public void testShouldCancelJob_remainingWidgets_shortTimeElapsed_shouldNotCancel() {
+ Map<String, Set<String>> remainingWidgets = new HashMap<>();
+ remainingWidgets.put(PEOPLE_TILE_KEY.toString(), WIDGET_IDS);
+ assertThat(mPeopleBackupFollowUpJob.shouldCancelJob(remainingWidgets, 10, 1000)).isFalse();
+ }
+
+ @Test
+ public void testShouldCancelJob_remainingWidgets_longTimeElapsed_shouldCancel() {
+ Map<String, Set<String>> remainingWidgets = new HashMap<>();
+ remainingWidgets.put(PEOPLE_TILE_KEY.toString(), WIDGET_IDS);
+ assertThat(mPeopleBackupFollowUpJob.shouldCancelJob(
+ remainingWidgets, 10, 1000 * 60 * 60 * 25)).isTrue();
+ }
+
+ @Test
+ public void testCancelJobAndClearRemainingWidgets() {
+ SharedPreferencesHelper.setPeopleTileKey(mWidgetIdSp, PEOPLE_TILE_KEY);
+ SharedPreferencesHelper.setPeopleTileKey(mSecondWidgetIdSp, PEOPLE_TILE_KEY);
+ mEditor.putStringSet(URI.toString(), WIDGET_IDS);
+ mEditor.putString(WIDGET_ID_STRING, URI.toString());
+ mEditor.putString(SECOND_WIDGET_ID_STRING, URI.toString());
+ mEditor.apply();
+ Map<String, Set<String>> remainingWidgets = new HashMap<>();
+ remainingWidgets.put(PEOPLE_TILE_KEY.toString(), WIDGET_IDS);
+
+ mPeopleBackupFollowUpJob.cancelJobAndClearRemainingWidgets(
+ remainingWidgets, mFollowUpEditor, mSp);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ verify(mJobScheduler, times(1)).cancel(anyInt());
+ assertThat(mFollowUpSp.getAll()).isEmpty();
+ assertThat(mWidgetIdSp.getAll()).isEmpty();
+ assertThat(mSecondWidgetIdSp.getAll()).isEmpty();
+ assertThat(mSp.getStringSet(PEOPLE_TILE_KEY.toString(), new HashSet<>())).isEmpty();
+ assertThat(mSp.getStringSet(URI.toString(), new HashSet<>())).isEmpty();
+ assertThat(mSp.getString(WIDGET_ID_STRING, null)).isNull();
+ assertThat(mSp.getString(SECOND_WIDGET_ID_STRING, null)).isNull();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index 33c7a571ce27..fba19861b006 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -33,6 +33,7 @@ import static org.mockito.Mockito.when;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.Person;
+import android.app.backup.BackupManager;
import android.app.people.IPeopleManager;
import android.app.people.PeopleSpaceTile;
import android.appwidget.AppWidgetManager;
@@ -198,6 +199,8 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
private NotificationEntryManager mNotificationEntryManager;
@Mock
private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
+ @Mock
+ private BackupManager mBackupManager;
private Bundle mOptions;
@@ -252,7 +255,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
PeopleTileKey key = new PeopleTileKey(tile);
PeopleSpaceTile actual = PeopleSpaceUtils
.augmentTileFromNotification(mContext, tile, key, mNotificationEntry1, 0,
- Optional.empty());
+ Optional.empty(), mBackupManager);
assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
assertThat(actual.getNotificationSender()).isEqualTo(null);
@@ -292,7 +295,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
PeopleTileKey key = new PeopleTileKey(tile);
PeopleSpaceTile actual = PeopleSpaceUtils
.augmentTileFromNotification(mContext, tile, key, notificationEntry, 0,
- Optional.empty());
+ Optional.empty(), mBackupManager);
assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
assertThat(actual.getNotificationSender().toString()).isEqualTo("name");
@@ -325,7 +328,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
PeopleTileKey key = new PeopleTileKey(tile);
PeopleSpaceTile actual = PeopleSpaceUtils
.augmentTileFromNotification(mContext, tile, key, notificationEntry, 0,
- Optional.empty());
+ Optional.empty(), mBackupManager);
assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_1);
assertThat(actual.getNotificationDataUri()).isEqualTo(URI);
@@ -358,7 +361,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
PeopleTileKey key = new PeopleTileKey(tile);
PeopleSpaceTile actual = PeopleSpaceUtils
.augmentTileFromNotification(mContext, tile, key, notificationEntry, 0,
- Optional.empty());
+ Optional.empty(), mBackupManager);
assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_1);
assertThat(actual.getNotificationDataUri()).isNull();
@@ -376,7 +379,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
PeopleTileKey key = new PeopleTileKey(tile);
PeopleSpaceTile actual = PeopleSpaceUtils
.augmentTileFromNotification(mContext, tile, key, mNotificationEntry3, 0,
- Optional.empty());
+ Optional.empty(), mBackupManager);
assertThat(actual.getNotificationContent()).isEqualTo(null);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java
new file mode 100644
index 000000000000..7cd5e22a9b97
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.systemui.people;
+
+import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
+import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.widget.PeopleTileKey;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SharedPreferencesHelperTest extends SysuiTestCase {
+ private static final String SHORTCUT_ID_1 = "101";
+ private static final String PACKAGE_NAME_1 = "package_name";
+ private static final int USER_ID_1 = 0;
+
+ private static final PeopleTileKey PEOPLE_TILE_KEY =
+ new PeopleTileKey(SHORTCUT_ID_1, USER_ID_1, PACKAGE_NAME_1);
+
+ private static final int WIDGET_ID = 1;
+
+ private void setStorageForTile(PeopleTileKey peopleTileKey, int widgetId) {
+ SharedPreferences widgetSp = mContext.getSharedPreferences(
+ String.valueOf(widgetId),
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor widgetEditor = widgetSp.edit();
+ widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, peopleTileKey.getPackageName());
+ widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, peopleTileKey.getShortcutId());
+ widgetEditor.putInt(PeopleSpaceUtils.USER_ID, peopleTileKey.getUserId());
+ widgetEditor.apply();
+ }
+
+ @Test
+ public void testGetPeopleTileKey() {
+ setStorageForTile(PEOPLE_TILE_KEY, WIDGET_ID);
+
+ SharedPreferences sp = mContext.getSharedPreferences(
+ String.valueOf(WIDGET_ID),
+ Context.MODE_PRIVATE);
+ PeopleTileKey actual = SharedPreferencesHelper.getPeopleTileKey(sp);
+
+ assertThat(actual.getPackageName()).isEqualTo(PACKAGE_NAME_1);
+ assertThat(actual.getShortcutId()).isEqualTo(SHORTCUT_ID_1);
+ assertThat(actual.getUserId()).isEqualTo(USER_ID_1);
+ }
+
+ @Test
+ public void testSetPeopleTileKey() {
+ SharedPreferences sp = mContext.getSharedPreferences(
+ String.valueOf(WIDGET_ID),
+ Context.MODE_PRIVATE);
+ SharedPreferencesHelper.setPeopleTileKey(sp, PEOPLE_TILE_KEY);
+
+ assertThat(sp.getString(SHORTCUT_ID, null)).isEqualTo(SHORTCUT_ID_1);
+ assertThat(sp.getString(PACKAGE_NAME, null)).isEqualTo(PACKAGE_NAME_1);
+ assertThat(sp.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(USER_ID_1);
+ }
+
+ @Test
+ public void testClear() {
+ setStorageForTile(PEOPLE_TILE_KEY, WIDGET_ID);
+
+ SharedPreferences sp = mContext.getSharedPreferences(
+ String.valueOf(WIDGET_ID),
+ Context.MODE_PRIVATE);
+ SharedPreferencesHelper.clear(sp);
+
+ assertThat(sp.getString(SHORTCUT_ID, null)).isEqualTo(null);
+ assertThat(sp.getString(PACKAGE_NAME, null)).isEqualTo(null);
+ assertThat(sp.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(INVALID_USER_ID);
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java
new file mode 100644
index 000000000000..5d526e102b91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java
@@ -0,0 +1,537 @@
+/*
+ * 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.systemui.people.widget;
+
+import static com.android.systemui.people.PeopleBackupFollowUpJob.SHARED_FOLLOW_UP;
+import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
+import static com.android.systemui.people.widget.PeopleBackupHelper.ADD_USER_ID_TO_URI;
+import static com.android.systemui.people.widget.PeopleBackupHelper.SHARED_BACKUP;
+import static com.android.systemui.people.widget.PeopleBackupHelper.SharedFileEntryType;
+import static com.android.systemui.people.widget.PeopleBackupHelper.getEntryType;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.app.people.IPeopleManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.preference.PreferenceManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.SharedPreferencesHelper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class PeopleBackupHelperTest extends SysuiTestCase {
+ private static final String SHORTCUT_ID_1 = "101";
+ private static final String PACKAGE_NAME_1 = "package_name";
+ private static final int USER_ID_0 = 0;
+ private static final int USER_ID_10 = 10;
+
+ private static final PeopleTileKey PEOPLE_TILE_KEY =
+ new PeopleTileKey(SHORTCUT_ID_1, USER_ID_0, PACKAGE_NAME_1);
+ private static final PeopleTileKey OTHER_PEOPLE_TILE_KEY =
+ new PeopleTileKey(SHORTCUT_ID_1, USER_ID_10, PACKAGE_NAME_1);
+ private static final PeopleTileKey INVALID_USER_ID_PEOPLE_TILE_KEY =
+ new PeopleTileKey(SHORTCUT_ID_1, INVALID_USER_ID, PACKAGE_NAME_1);
+
+ private static final String WIDGET_ID_STRING = "3";
+ private static final String SECOND_WIDGET_ID_STRING = "12";
+ private static final String OTHER_WIDGET_ID_STRING = "7";
+ private static final Set<String> WIDGET_IDS = new HashSet<>(
+ Arrays.asList(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING));
+
+ private static final String URI_STRING = "content://mms";
+ private static final String URI_WITH_USER_ID_0 = "content://0@mms";
+ private static final String URI_WITH_USER_ID_10 = "content://10@mms";
+
+ private final SharedPreferences mBackupSp = mContext.getSharedPreferences(
+ SHARED_BACKUP, Context.MODE_PRIVATE);
+ private final SharedPreferences.Editor mBackupEditor = mBackupSp.edit();
+ private final SharedPreferences mSp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ private final SharedPreferences.Editor mEditor = mSp.edit();
+ private final SharedPreferences mFollowUpSp = mContext.getSharedPreferences(
+ SHARED_FOLLOW_UP, Context.MODE_PRIVATE);
+ private final SharedPreferences.Editor mFollowUpEditor = mFollowUpSp.edit();
+ private final SharedPreferences mWidgetIdSp = mContext.getSharedPreferences(
+ WIDGET_ID_STRING, Context.MODE_PRIVATE);
+ private final SharedPreferences mSecondWidgetIdSp = mContext.getSharedPreferences(
+ SECOND_WIDGET_ID_STRING, Context.MODE_PRIVATE);
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private PackageInfo mPackageInfo;
+ @Mock
+ private IPeopleManager mIPeopleManager;
+
+ private PeopleBackupHelper mHelper;
+ private PeopleBackupHelper mOtherHelper;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mHelper = new PeopleBackupHelper(mContext,
+ UserHandle.of(0), new String[]{SHARED_BACKUP}, mPackageManager, mIPeopleManager);
+ mOtherHelper = new PeopleBackupHelper(mContext,
+ UserHandle.of(10), new String[]{SHARED_BACKUP}, mPackageManager, mIPeopleManager);
+
+ when(mPackageManager.getPackageInfoAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(mPackageInfo);
+ when(mIPeopleManager.isConversation(any(), anyInt(), any())).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() {
+ mBackupEditor.clear().commit();
+ mEditor.clear().commit();
+ mFollowUpEditor.clear().commit();
+ mWidgetIdSp.edit().clear().commit();
+ mSecondWidgetIdSp.edit().clear().commit();
+ }
+
+ @Test
+ public void testGetKeyType_widgetId() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(WIDGET_ID_STRING, "contact");
+ assertThat(getEntryType(entry)).isEqualTo(SharedFileEntryType.WIDGET_ID);
+ }
+
+ @Test
+ public void testGetKeyType_widgetId_twoDigits() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ SECOND_WIDGET_ID_STRING, URI_STRING);
+ assertThat(getEntryType(entry)).isEqualTo(SharedFileEntryType.WIDGET_ID);
+ }
+
+ @Test
+ public void testGetKeyType_peopleTileKey_valid() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ "shortcut_id/12/com.android.systemui", WIDGET_IDS);
+ assertThat(getEntryType(entry)).isEqualTo(SharedFileEntryType.PEOPLE_TILE_KEY);
+ }
+
+ @Test
+ public void testGetKeyType_peopleTileKey_validWithSlashes() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ "shortcut_id/with/slashes/12/com.android.systemui2", WIDGET_IDS);
+ assertThat(getEntryType(entry)).isEqualTo(SharedFileEntryType.PEOPLE_TILE_KEY);
+ }
+
+ @Test
+ public void testGetKeyType_peopleTileKey_negativeNumber() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ "shortcut_id/with/slashes/-1/com.android.systemui2", WIDGET_IDS);
+ assertThat(getEntryType(entry)).isEqualTo(SharedFileEntryType.PEOPLE_TILE_KEY);
+ }
+
+ @Test
+ public void testGetKeyType_contactUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ "shortcut_id/1f/com.android.systemui2", WIDGET_IDS);
+ assertThat(getEntryType(entry)).isEqualTo(SharedFileEntryType.CONTACT_URI);
+ }
+
+ @Test
+ public void testGetKeyType_contactUri_valid() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ "http://content.fake", WIDGET_IDS);
+ assertThat(getEntryType(entry)).isEqualTo(SharedFileEntryType.CONTACT_URI);
+ }
+
+ @Test
+ public void testGetKeyType_contactUri_invalidPackageName() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ "shortcut_id/with/slashes/12/2r/com.android.systemui2", WIDGET_IDS);
+ assertThat(getEntryType(entry)).isEqualTo(SharedFileEntryType.CONTACT_URI);
+ }
+
+ @Test
+ public void testGetKeyType_unknown_unexpectedValueForPeopleTileKey() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ "shortcut_id/12/com.android.systemui", URI_STRING);
+ assertThat(getEntryType(entry)).isEqualTo(SharedFileEntryType.UNKNOWN);
+ }
+
+ @Test
+ public void testGetKeyType_unknown_unexpectedValueForContactUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ URI_STRING, "12");
+ assertThat(getEntryType(entry)).isEqualTo(SharedFileEntryType.UNKNOWN);
+ }
+
+ @Test
+ public void testGetKeyType_unknown() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ null, WIDGET_IDS);
+ assertThat(getEntryType(entry)).isEqualTo(SharedFileEntryType.UNKNOWN);
+ }
+
+ @Test
+ public void testBackupKey_widgetIdKey_containsWidget_noUserIdInUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(WIDGET_ID_STRING, URI_STRING);
+
+ mHelper.backupKey(entry, mBackupEditor, Collections.singletonList(WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getString(WIDGET_ID_STRING, null)).isEqualTo(URI_STRING);
+ }
+
+ @Test
+ public void testBackupKey_widgetIdKey_doesNotContainWidget_noUserIdInUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(WIDGET_ID_STRING, URI_STRING);
+
+ mHelper.backupKey(entry, mBackupEditor, Collections.singletonList(OTHER_WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getString(WIDGET_ID_STRING, null)).isNull();
+ }
+
+ @Test
+ public void testBackupKey_widgetIdKey_containsOneWidget_differentUserIdInUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(WIDGET_ID_STRING,
+ URI_WITH_USER_ID_10);
+
+ mHelper.backupKey(entry, mBackupEditor, Collections.singletonList(WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getString(WIDGET_ID_STRING, null)).isEqualTo(URI_STRING);
+ assertThat(mBackupSp.getInt(ADD_USER_ID_TO_URI + WIDGET_ID_STRING, INVALID_USER_ID))
+ .isEqualTo(USER_ID_10);
+ }
+
+ @Test
+ public void testBackupKey_widgetIdKey_containsWidget_SameUserIdInUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ WIDGET_ID_STRING, URI_WITH_USER_ID_10);
+
+ mOtherHelper.backupKey(entry, mBackupEditor, Collections.singletonList(WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getString(WIDGET_ID_STRING, null)).isEqualTo(URI_STRING);
+ assertThat(mBackupSp.getInt(ADD_USER_ID_TO_URI + WIDGET_ID_STRING, INVALID_USER_ID))
+ .isEqualTo(USER_ID_10);
+ }
+
+ @Test
+ public void testBackupKey_contactUriKey_ignoresExistingWidgets() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(URI_STRING, WIDGET_IDS);
+
+ mHelper.backupKey(entry, mBackupEditor, Collections.singletonList(WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getStringSet(URI_STRING, new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ }
+
+ @Test
+ public void testBackupKey_contactUriKey_ignoresExistingWidgets_otherWidget() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(URI_STRING, WIDGET_IDS);
+
+ mHelper.backupKey(entry, mBackupEditor, Collections.singletonList(WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getStringSet(URI_STRING, new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ }
+
+ @Test
+ public void testBackupKey_contactUriKey_noUserId_otherUser_doesntBackup() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(URI_STRING, WIDGET_IDS);
+
+ mOtherHelper.backupKey(entry, mBackupEditor, Collections.singletonList(WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getStringSet(URI_STRING, new HashSet<>())).isEmpty();
+ }
+
+ @Test
+ public void testBackupKey_contactUriKey_sameUserId() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(URI_WITH_USER_ID_10, WIDGET_IDS);
+
+ mOtherHelper.backupKey(entry, mBackupEditor, Collections.singletonList(WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getStringSet(URI_STRING, new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ assertThat(mBackupSp.getInt(ADD_USER_ID_TO_URI + URI_STRING, INVALID_USER_ID))
+ .isEqualTo(USER_ID_10);
+ }
+
+ @Test
+ public void testBackupKey_contactUriKey_differentUserId_runningAsUser0() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(URI_WITH_USER_ID_10, WIDGET_IDS);
+
+ mHelper.backupKey(entry, mBackupEditor, Collections.singletonList(WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getStringSet(URI_STRING, new HashSet<>())).isEmpty();
+ assertThat(mBackupSp.getInt(ADD_USER_ID_TO_URI + URI_STRING, INVALID_USER_ID))
+ .isEqualTo(INVALID_USER_ID);
+ }
+
+ @Test
+ public void testBackupKey_contactUriKey_differentUserId_runningAsUser10() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(URI_WITH_USER_ID_0, WIDGET_IDS);
+
+ mOtherHelper.backupKey(entry, mBackupEditor, Collections.singletonList(WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getStringSet(URI_STRING, new HashSet<>())).isEmpty();
+ assertThat(mBackupSp.getInt(ADD_USER_ID_TO_URI + URI_STRING, INVALID_USER_ID))
+ .isEqualTo(INVALID_USER_ID);
+ }
+
+ @Test
+ public void testBackupKey_peopleTileKey_containsWidget() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ PEOPLE_TILE_KEY.toString(), WIDGET_IDS);
+
+ mHelper.backupKey(entry, mBackupEditor, Collections.singletonList(WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getStringSet(
+ INVALID_USER_ID_PEOPLE_TILE_KEY.toString(), new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING);
+ }
+
+ @Test
+ public void testBackupKey_peopleTileKey_containsBothWidgets() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ PEOPLE_TILE_KEY.toString(), WIDGET_IDS);
+
+ mHelper.backupKey(entry, mBackupEditor,
+ Arrays.asList(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(
+ mBackupSp.getStringSet(INVALID_USER_ID_PEOPLE_TILE_KEY.toString(), new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ }
+
+ @Test
+ public void testBackupKey_peopleTileKey_doesNotContainWidget() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ PEOPLE_TILE_KEY.toString(), WIDGET_IDS);
+
+ mHelper.backupKey(entry, mBackupEditor, Collections.singletonList(OTHER_WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getStringSet(
+ INVALID_USER_ID_PEOPLE_TILE_KEY.toString(), new HashSet<>())).isEmpty();
+ }
+
+ @Test
+ public void testBackupKey_peopleTileKey_differentUserId() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ OTHER_PEOPLE_TILE_KEY.toString(), WIDGET_IDS);
+
+ mHelper.backupKey(entry, mBackupEditor, Collections.singletonList(WIDGET_ID_STRING));
+ mBackupEditor.apply();
+
+ assertThat(mBackupSp.getStringSet(
+ INVALID_USER_ID_PEOPLE_TILE_KEY.toString(), new HashSet<>())).isEmpty();
+ }
+
+ @Test
+ public void testRestoreKey_widgetIdKey_noUserIdInUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(WIDGET_ID_STRING, URI_STRING);
+
+ boolean restored = mHelper.restoreKey(entry, mEditor, mFollowUpEditor, mBackupSp);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(restored).isTrue();
+ assertThat(mSp.getString(WIDGET_ID_STRING, null)).isEqualTo(URI_STRING);
+ assertThat(mFollowUpSp.getAll()).isEmpty();
+ }
+
+ @Test
+ public void testRestoreKey_widgetIdKey_sameUserInUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(WIDGET_ID_STRING, URI_STRING);
+ mBackupEditor.putInt(ADD_USER_ID_TO_URI + WIDGET_ID_STRING, USER_ID_0);
+ mBackupEditor.apply();
+
+ boolean restored = mHelper.restoreKey(entry, mEditor, mFollowUpEditor, mBackupSp);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(restored).isTrue();
+ assertThat(mSp.getString(WIDGET_ID_STRING, null)).isEqualTo(URI_WITH_USER_ID_0);
+ assertThat(mFollowUpSp.getAll()).isEmpty();
+ }
+
+ @Test
+ public void testRestoreKey_widgetIdKey_differentUserInUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(WIDGET_ID_STRING, URI_STRING);
+ mBackupEditor.putInt(ADD_USER_ID_TO_URI + WIDGET_ID_STRING, USER_ID_10);
+ mBackupEditor.apply();
+
+ boolean restored = mHelper.restoreKey(entry, mEditor, mFollowUpEditor, mBackupSp);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(restored).isTrue();
+ assertThat(mSp.getString(WIDGET_ID_STRING, null)).isEqualTo(URI_WITH_USER_ID_10);
+ assertThat(mFollowUpSp.getAll()).isEmpty();
+ }
+
+ @Test
+ public void testRestoreKey_widgetIdKey_nonSystemUser_differentUser() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(WIDGET_ID_STRING, URI_STRING);
+ mBackupEditor.putInt(ADD_USER_ID_TO_URI + WIDGET_ID_STRING, USER_ID_0);
+ mBackupEditor.apply();
+
+ boolean restored = mOtherHelper.restoreKey(entry, mEditor, mFollowUpEditor, mBackupSp);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(restored).isTrue();
+ assertThat(mSp.getString(WIDGET_ID_STRING, null)).isEqualTo(URI_WITH_USER_ID_0);
+ assertThat(mFollowUpSp.getAll()).isEmpty();
+ }
+
+ @Test
+ public void testRestoreKey_contactUriKey_noUserIdInUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(URI_STRING, WIDGET_IDS);
+
+ boolean restored = mHelper.restoreKey(entry, mEditor, mFollowUpEditor, mBackupSp);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(restored).isTrue();
+ assertThat(mSp.getStringSet(URI_STRING, new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ assertThat(mFollowUpSp.getAll()).isEmpty();
+ }
+
+ @Test
+ public void testRestoreKey_contactUriKey_sameUserInUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(URI_STRING, WIDGET_IDS);
+ mBackupEditor.putInt(ADD_USER_ID_TO_URI + URI_STRING, USER_ID_0);
+ mBackupEditor.apply();
+
+ boolean restored = mHelper.restoreKey(entry, mEditor, mFollowUpEditor, mBackupSp);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(restored).isTrue();
+ assertThat(mSp.getStringSet(URI_WITH_USER_ID_0, new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ assertThat(mSp.getStringSet(URI_STRING, new HashSet<>())).isEmpty();
+ assertThat(mFollowUpSp.getAll()).isEmpty();
+ }
+
+ @Test
+ public void testRestoreKey_contactUriKey_differentUserInUri() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(URI_STRING, WIDGET_IDS);
+ mBackupEditor.putInt(ADD_USER_ID_TO_URI + URI_STRING, USER_ID_10);
+ mBackupEditor.apply();
+
+ boolean restored = mHelper.restoreKey(entry, mEditor, mFollowUpEditor, mBackupSp);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(restored).isTrue();
+ assertThat(mSp.getStringSet(URI_WITH_USER_ID_10, new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ assertThat(mSp.getStringSet(URI_STRING, new HashSet<>())).isEmpty();
+ assertThat(mFollowUpSp.getAll()).isEmpty();
+ }
+
+ @Test
+ public void testRestoreKey_contactUriKey_nonSystemUser_differentUser() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(URI_STRING, WIDGET_IDS);
+ mBackupEditor.putInt(ADD_USER_ID_TO_URI + URI_STRING, USER_ID_0);
+ mBackupEditor.apply();
+
+ boolean restored = mOtherHelper.restoreKey(entry, mEditor, mFollowUpEditor, mBackupSp);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(restored).isTrue();
+ assertThat(mSp.getStringSet(URI_WITH_USER_ID_0, new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ assertThat(mSp.getStringSet(URI_STRING, new HashSet<>())).isEmpty();
+ assertThat(mFollowUpSp.getAll()).isEmpty();
+ }
+
+ @Test
+ public void testRestoreKey_peopleTileKey_shouldNotFollowUp() {
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ INVALID_USER_ID_PEOPLE_TILE_KEY.toString(), WIDGET_IDS);
+
+ boolean restored = mHelper.restoreKey(entry, mEditor, mFollowUpEditor, mBackupSp);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(restored).isTrue();
+ assertThat(mSp.getStringSet(PEOPLE_TILE_KEY.toString(), new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ assertThat(SharedPreferencesHelper.getPeopleTileKey(mWidgetIdSp))
+ .isEqualTo(PEOPLE_TILE_KEY);
+ assertThat(SharedPreferencesHelper.getPeopleTileKey(mSecondWidgetIdSp))
+ .isEqualTo(PEOPLE_TILE_KEY);
+ assertThat(mFollowUpSp.getAll()).isEmpty();
+ }
+
+ @Test
+ public void testRestoreKey_peopleTileKey_shortcutNotYetRestored_shouldFollowUpBoth()
+ throws RemoteException {
+ when(mIPeopleManager.isConversation(any(), anyInt(), any())).thenReturn(false);
+
+ Map.Entry<String, ?> entry = new AbstractMap.SimpleEntry<>(
+ INVALID_USER_ID_PEOPLE_TILE_KEY.toString(), WIDGET_IDS);
+
+ boolean restored = mHelper.restoreKey(entry, mEditor, mFollowUpEditor, mBackupSp);
+ mEditor.apply();
+ mFollowUpEditor.apply();
+
+ assertThat(restored).isFalse();
+ assertThat(mSp.getStringSet(PEOPLE_TILE_KEY.toString(), new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ assertThat(SharedPreferencesHelper.getPeopleTileKey(mWidgetIdSp))
+ .isEqualTo(PEOPLE_TILE_KEY);
+ assertThat(SharedPreferencesHelper.getPeopleTileKey(mSecondWidgetIdSp))
+ .isEqualTo(PEOPLE_TILE_KEY);
+
+ assertThat(mFollowUpSp.getStringSet(PEOPLE_TILE_KEY.toString(), new HashSet<>()))
+ .containsExactly(WIDGET_ID_STRING, SECOND_WIDGET_ID_STRING);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index c48f26b8c853..ddad7581899b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -73,6 +73,7 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Person;
+import android.app.backup.BackupManager;
import android.app.people.ConversationChannel;
import android.app.people.ConversationStatus;
import android.app.people.IPeopleManager;
@@ -102,7 +103,9 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.PeopleBackupFollowUpJob;
import com.android.systemui.people.PeopleSpaceUtils;
+import com.android.systemui.people.SharedPreferencesHelper;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.SbnBuilder;
@@ -151,6 +154,11 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
private static final int WIDGET_ID_WITH_KEY_IN_OPTIONS = 4;
private static final int WIDGET_ID_WITH_SAME_URI = 5;
private static final int WIDGET_ID_WITH_DIFFERENT_URI = 6;
+ private static final int WIDGET_ID_8 = 8;
+ private static final int WIDGET_ID_9 = 9;
+ private static final int WIDGET_ID_11 = 11;
+ private static final int WIDGET_ID_14 = 14;
+ private static final int WIDGET_ID_15 = 15;
private static final String SHORTCUT_ID = "101";
private static final String OTHER_SHORTCUT_ID = "102";
private static final String NOTIFICATION_KEY = "0|com.android.systemui.tests|0|null|0";
@@ -195,6 +203,14 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
| SUPPRESSED_EFFECT_NOTIFICATION_LIST;
private static final long SBN_POST_TIME = 567L;
+ private static final Map<String, String> WIDGETS_MAPPING = Map.of(
+ String.valueOf(WIDGET_ID_8), String.valueOf(WIDGET_ID_WITH_SHORTCUT),
+ String.valueOf(WIDGET_ID_9), String.valueOf(WIDGET_ID_WITHOUT_SHORTCUT),
+ String.valueOf(WIDGET_ID_11), String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS),
+ String.valueOf(WIDGET_ID_14), String.valueOf(WIDGET_ID_WITH_SAME_URI),
+ String.valueOf(WIDGET_ID_15), String.valueOf(WIDGET_ID_WITH_DIFFERENT_URI)
+ );
+
private ShortcutInfo mShortcutInfo;
private NotificationEntry mNotificationEntry;
@@ -228,6 +244,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
private NotificationManager.Policy mNotificationPolicy;
@Mock
private Bubbles mBubbles;
+ @Mock
+ private BackupManager mBackupManager;
@Captor
private ArgumentCaptor<NotificationHandler> mListenerCaptor;
@@ -246,8 +264,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
mManager = new PeopleSpaceWidgetManager(mContext, mAppWidgetManager, mIPeopleManager,
mPeopleManager, mLauncherApps, mNotificationEntryManager, mPackageManager,
- Optional.of(mBubbles), mUserManager, mINotificationManager, mNotificationManager,
- mFakeExecutor);
+ Optional.of(mBubbles), mUserManager, mBackupManager, mINotificationManager,
+ mNotificationManager, mFakeExecutor);
mManager.attach(mListenerService);
verify(mListenerService).addNotificationHandler(mListenerCaptor.capture());
@@ -1410,6 +1428,89 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONVERSATIONS);
}
+ @Test
+ public void testRemapWidgetFiles() {
+ setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_8, URI);
+ setStorageForTile(OTHER_SHORTCUT_ID, TEST_PACKAGE_B, WIDGET_ID_11, URI);
+
+ mManager.remapWidgetFiles(WIDGETS_MAPPING);
+
+ SharedPreferences sp1 = mContext.getSharedPreferences(
+ String.valueOf(WIDGET_ID_WITH_SHORTCUT), Context.MODE_PRIVATE);
+ PeopleTileKey key1 = SharedPreferencesHelper.getPeopleTileKey(sp1);
+ assertThat(key1.getShortcutId()).isEqualTo(SHORTCUT_ID);
+ assertThat(key1.getPackageName()).isEqualTo(TEST_PACKAGE_A);
+
+ SharedPreferences sp4 = mContext.getSharedPreferences(
+ String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS), Context.MODE_PRIVATE);
+ PeopleTileKey key4 = SharedPreferencesHelper.getPeopleTileKey(sp4);
+ assertThat(key4.getShortcutId()).isEqualTo(OTHER_SHORTCUT_ID);
+ assertThat(key4.getPackageName()).isEqualTo(TEST_PACKAGE_B);
+
+ SharedPreferences sp8 = mContext.getSharedPreferences(
+ String.valueOf(WIDGET_ID_8), Context.MODE_PRIVATE);
+ PeopleTileKey key8 = SharedPreferencesHelper.getPeopleTileKey(sp8);
+ assertThat(key8.getShortcutId()).isNull();
+ assertThat(key8.getPackageName()).isNull();
+
+ SharedPreferences sp11 = mContext.getSharedPreferences(
+ String.valueOf(WIDGET_ID_11), Context.MODE_PRIVATE);
+ PeopleTileKey key11 = SharedPreferencesHelper.getPeopleTileKey(sp11);
+ assertThat(key11.getShortcutId()).isNull();
+ assertThat(key11.getPackageName()).isNull();
+ }
+
+ @Test
+ public void testRemapSharedFile() {
+ setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_8, URI);
+ setStorageForTile(OTHER_SHORTCUT_ID, TEST_PACKAGE_B, WIDGET_ID_11, URI);
+
+ mManager.remapSharedFile(WIDGETS_MAPPING);
+
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+
+ assertThat(sp.getString(String.valueOf(WIDGET_ID_8), null)).isNull();
+ assertThat(sp.getString(String.valueOf(WIDGET_ID_11), null)).isNull();
+ assertThat(sp.getString(String.valueOf(WIDGET_ID_WITH_SHORTCUT), null))
+ .isEqualTo(URI.toString());
+ assertThat(sp.getString(String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS), null))
+ .isEqualTo(URI.toString());
+
+ assertThat(sp.getStringSet(URI.toString(), new HashSet<>())).containsExactly(
+ String.valueOf(WIDGET_ID_WITH_SHORTCUT),
+ String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS));
+
+ PeopleTileKey key8 = new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A);
+ assertThat(sp.getStringSet(key8.toString(), new HashSet<>())).containsExactly(
+ String.valueOf(WIDGET_ID_WITH_SHORTCUT));
+
+ PeopleTileKey key11 = new PeopleTileKey(OTHER_SHORTCUT_ID, 0, TEST_PACKAGE_B);
+ assertThat(sp.getStringSet(key11.toString(), new HashSet<>())).containsExactly(
+ String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS));
+ }
+
+ @Test
+ public void testRemapFollowupFile() {
+ PeopleTileKey key8 = new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A);
+ PeopleTileKey key11 = new PeopleTileKey(OTHER_SHORTCUT_ID, 0, TEST_PACKAGE_B);
+ Set<String> set8 = new HashSet<>(Collections.singleton(String.valueOf(WIDGET_ID_8)));
+ Set<String> set11 = new HashSet<>(Collections.singleton(String.valueOf(WIDGET_ID_11)));
+
+ SharedPreferences followUp = mContext.getSharedPreferences(
+ PeopleBackupFollowUpJob.SHARED_FOLLOW_UP, Context.MODE_PRIVATE);
+ SharedPreferences.Editor followUpEditor = followUp.edit();
+ followUpEditor.putStringSet(key8.toString(), set8);
+ followUpEditor.putStringSet(key11.toString(), set11);
+ followUpEditor.apply();
+
+ mManager.remapFollowupFile(WIDGETS_MAPPING);
+
+ assertThat(followUp.getStringSet(key8.toString(), new HashSet<>())).containsExactly(
+ String.valueOf(WIDGET_ID_WITH_SHORTCUT));
+ assertThat(followUp.getStringSet(key11.toString(), new HashSet<>())).containsExactly(
+ String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS));
+ }
+
private void setFinalField(String fieldName, int value) {
try {
Field field = NotificationManager.Policy.class.getDeclaredField(fieldName);