Conversations Widget - Backup and Restore

Change-Id: I46a8359ffd4b462add31f5cecce70ff05870db6f
Bug: 183084949
Test: PeopleBackupHelperTest, PeopleSpaceWidgetManagerTest..
This commit is contained in:
Flavio Fiszman
2021-05-21 15:38:10 +01:00
parent 0dd908063e
commit e54f26157f
17 changed files with 2010 additions and 44 deletions

View File

@@ -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"

View File

@@ -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() {

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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.");

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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) {

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);