Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3e358e3d authored by Flavio Fiszman's avatar Flavio Fiszman Committed by Automerger Merge Worker
Browse files

Merge "Conversations Widget - Backup and Restore" into sc-dev am: ade3d606

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14703538

Change-Id: If38f9c271ddc50930d5500abb4ebb16c1323356d
parents df59b443 ade3d606
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -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"
+13 −1
Original line number Diff line number Diff line
@@ -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() {
+229 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.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;
    }
}
+13 −11
Original line number Diff line number Diff line
@@ -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());
        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);
        }
+16 −6
Original line number Diff line number Diff line
@@ -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);
    }
}
Loading