summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src/com/android/systemui/people/PeopleBackupFollowUpJob.java
blob: 3bc1f30ea3212848dfe34111e82a8b655ce4e6be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
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(48).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;
    }
}