Merge "Conversations Widget - Backup and Restore" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
ade3d60635
@@ -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"
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user