Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabTabPersistencePolicy.java

Issue 2296833002: Add tab persistence support for CustomTabs. (Closed)
Patch Set: Address twellington@ comments Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.customtabs;
6
7 import android.app.Activity;
8 import android.os.AsyncTask;
9 import android.os.StrictMode;
10 import android.util.Pair;
11 import android.util.SparseBooleanArray;
12
13 import org.chromium.base.ApplicationStatus;
14 import org.chromium.base.Callback;
15 import org.chromium.base.Log;
16 import org.chromium.base.StreamUtil;
17 import org.chromium.base.ThreadUtils;
18 import org.chromium.base.VisibleForTesting;
19 import org.chromium.chrome.browser.TabState;
20 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
21 import org.chromium.chrome.browser.tabmodel.TabModel;
22 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
23 import org.chromium.chrome.browser.tabmodel.TabPersistencePolicy;
24 import org.chromium.chrome.browser.tabmodel.TabPersistentStore;
25
26 import java.io.BufferedInputStream;
27 import java.io.DataInputStream;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.lang.ref.WeakReference;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.Comparator;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.concurrent.ExecutionException;
40 import java.util.concurrent.Executor;
41
42 import javax.annotation.Nullable;
43
44 /**
45 * Handles the Custom Tab specific behaviors of tab persistence.
46 */
47 public class CustomTabTabPersistencePolicy implements TabPersistencePolicy {
48
49 static final String SAVED_STATE_DIRECTORY = "custom_tabs";
50
51 /** Threshold where old state files should be deleted (30 days). */
52 @VisibleForTesting
gone 2016/08/31 17:59:16 You only need this annotation to prevent ProGuard
Ted C 2016/08/31 18:37:53 Done.
53 protected static final long STATE_EXPIRY_THRESHOLD = 30L * 24 * 60 * 60 * 10 00;
54 /** Maximum number of state files before we should start deleting old ones. */
55 @VisibleForTesting
56 protected static final int MAXIMUM_STATE_FILES = 30;
57
58 private static final String TAG = "tabmodel";
59
60 /** Prevents two state directories from getting created simultaneously. */
61 private static final Object DIR_CREATION_LOCK = new Object();
62 /**
63 * Prevents two clean up tasks from getting created simultaneously. Also pro tects against
64 * incorrectly interleaving create/run/cancel on the task.
65 */
66 private static final Object CLEAN_UP_TASK_LOCK = new Object();
67
68 private static File sStateDirectory;
69 private static AsyncTask<Void, Void, Void> sCleanupTask;
70
71 /**
72 * The folder where the state should be saved to.
73 * @return A file representing the directory that contains TabModelSelector states.
74 */
75 public static File getOrCreateCustomTabModeStateDirectory() {
76 synchronized (DIR_CREATION_LOCK) {
77 if (sStateDirectory == null) {
78 sStateDirectory = new File(
79 TabPersistentStore.getOrCreateBaseStateDirectory(), SAVE D_STATE_DIRECTORY);
80 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskRe ads();
81 StrictMode.allowThreadDiskWrites();
82 try {
83 if (!sStateDirectory.exists() && !sStateDirectory.mkdirs()) {
84 Log.e(TAG, "Failed to create state folder: " + sStateDir ectory);
85 }
86 } finally {
87 StrictMode.setThreadPolicy(oldPolicy);
88 }
89 }
90 }
91 return sStateDirectory;
92 }
93
94 private final int mTaskId;
95 private final boolean mShouldRestore;
96
97 private AsyncTask<Void, Void, Void> mInitializationTask;
98 private boolean mDestroyed;
99
100 /**
101 * Constructs a persistence policy for a given Custom Tab.
102 *
103 * @param taskId The task ID that the owning Custom Tab is in.
104 * @param shouldRestore Whether an attempt to restore tab state information should be done on
105 * startup.
106 */
107 public CustomTabTabPersistencePolicy(int taskId, boolean shouldRestore) {
108 mTaskId = taskId;
109 mShouldRestore = shouldRestore;
110 }
111
112 @Override
113 public File getOrCreateStateDirectory() {
114 return getOrCreateCustomTabModeStateDirectory();
115 }
116
117 @Override
118 public String getStateFileName() {
119 return TabPersistentStore.getStateFileName(Integer.toString(mTaskId));
120 }
121
122 @Override
123 @Nullable
124 public String getStateToBeMergedFileName() {
125 return null;
126 }
127
128 @Override
129 public boolean performInitialization(Executor executor) {
130 mInitializationTask = new AsyncTask<Void, Void, Void>() {
131 @Override
132 protected Void doInBackground(Void... params) {
133 File stateDir = getOrCreateStateDirectory();
134 File metadataFile = new File(stateDir, getStateFileName());
135 if (metadataFile.exists()) {
136 if (!mShouldRestore) {
137 if (!metadataFile.delete()) {
138 Log.e(TAG, "Failed to delete file: " + metadataFile) ;
139 }
140 } else {
141 if (!metadataFile.setLastModified(System.currentTimeMill is())) {
142 Log.e(TAG, "Unable to update last modified time: " + metadataFile);
143 }
144 }
145 }
146 return null;
147 }
148 }.executeOnExecutor(executor);
149
150 return true;
151 }
152
153 @Override
154 public void waitForInitializationToFinish() {
155 if (mInitializationTask == null) return;
156 try {
157 mInitializationTask.get();
158 } catch (InterruptedException | ExecutionException e) {
159 // Ignore and proceed.
160 }
161 }
162
163 @Override
164 public boolean isMergeInProgress() {
165 return false;
166 }
167
168 @Override
169 public void setMergeInProgress(boolean isStarted) {
170 assert false : "Merge not supported in Custom Tabs";
171 }
172
173 @Override
174 public void cancelCleanupInProgress() {
175 synchronized (CLEAN_UP_TASK_LOCK) {
176 if (sCleanupTask != null) sCleanupTask.cancel(true);
177 }
178 }
179
180 @Override
181 public void cleanupUnusedFiles(Callback<List<String>> filesToDelete) {
182 synchronized (CLEAN_UP_TASK_LOCK) {
183 if (sCleanupTask != null) sCleanupTask.cancel(true);
184 sCleanupTask = new CleanUpTabStateDataTask(filesToDelete);
185 sCleanupTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
186 }
187 }
188
189 @Override
190 public void setTabContentManager(TabContentManager cache) {
191 }
192
193 @Override
194 public void destroy() {
195 mDestroyed = true;
196 }
197
198 /**
199 * Given a list of metadata files, determine which are applicable for deleti on based on the
200 * deletion strategy of Custom Tabs.
201 *
202 * @param currentTimeMillis The current time in milliseconds
203 * ({@link System#currentTimeMillis()}.
204 * @param allMetadataFiles The complete list of all metadata files to check.
205 * @return The list of metadata files that are applicable for deletion.
206 */
207 protected static List<File> getMetadataFilesForDeletion(
208 long currentTimeMillis, List<File> allMetadataFiles) {
209 Collections.sort(allMetadataFiles, new Comparator<File>() {
210 @Override
211 public int compare(File lhs, File rhs) {
212 long lhsModifiedTime = lhs.lastModified();
213 long rhsModifiedTime = rhs.lastModified();
214
215 // Sort such that older files (those with an lower timestamp num ber) are at the
216 // end of the sorted listed.
217 //
218 // Copied from Long#compare, which is only available in API 19+
gone 2016/08/31 17:59:16 This keeps coming up... can we just add a function
Ted C 2016/08/31 18:37:53 Done.
219 return rhsModifiedTime < lhsModifiedTime
220 ? -1 : (lhsModifiedTime == rhsModifiedTime ? 0 : 1);
221 }
222 });
223
224 List<File> stateFilesApplicableForDeletion = new ArrayList<File>();
225 for (int i = 0; i < allMetadataFiles.size(); i++) {
226 File file = allMetadataFiles.get(i);
227 long fileAge = currentTimeMillis - file.lastModified();
228 if (i >= MAXIMUM_STATE_FILES || fileAge >= STATE_EXPIRY_THRESHOLD) {
229 stateFilesApplicableForDeletion.add(file);
230 }
231 }
232 return stateFilesApplicableForDeletion;
233 }
234
235 /**
236 * Get all current Tab IDs used by the specified activity.
237 *
238 * @param activity The activity whose tab IDs are to be collected from.
239 * @param tabIds Where the tab IDs should be added to.
240 */
241 private static void getAllTabIdsForActivity(CustomTabActivity activity, Set< Integer> tabIds) {
242 if (activity == null) return;
243 TabModelSelector selector = activity.getTabModelSelector();
244 if (selector == null) return;
245 List<TabModel> models = selector.getModels();
246 for (int i = 0; i < models.size(); i++) {
247 TabModel model = models.get(i);
248 for (int j = 0; j < model.getCount(); j++) {
249 tabIds.add(model.getTabAt(j).getId());
250 }
251 }
252 }
253
254 /**
255 * Gathers all of the tab IDs and task IDs for all currently live Custom Tab s.
256 *
257 * @param liveTabIds Where tab IDs will be added.
258 * @param liveTaskIds Where task IDs will be added.
259 */
260 protected static void getAllLiveTabAndTaskIds(
261 Set<Integer> liveTabIds, Set<Integer> liveTaskIds) {
262 ThreadUtils.assertOnUiThread();
263
264 List<WeakReference<Activity>> activities = ApplicationStatus.getRunningA ctivities();
265 for (int i = 0; i < activities.size(); i++) {
266 Activity activity = activities.get(i).get();
267 if (activity == null) continue;
268 if (!(activity instanceof CustomTabActivity)) continue;
269 getAllTabIdsForActivity((CustomTabActivity) activity, liveTabIds);
270 liveTaskIds.add(activity.getTaskId());
271 }
272 }
273
274 private class CleanUpTabStateDataTask extends AsyncTask<Void, Void, Void> {
275 private final Callback<List<String>> mFilesToDeleteCallback;
276
277 private Set<Integer> mUnreferencedTabIds;
278 private List<File> mDeletableMetadataFiles;
279 private Map<File, SparseBooleanArray> mTabIdsByMetadataFile;
280
281 CleanUpTabStateDataTask(Callback<List<String>> filesToDelete) {
282 mFilesToDeleteCallback = filesToDelete;
283 }
284
285 @Override
286 protected Void doInBackground(Void... voids) {
287 if (mDestroyed) return null;
288
289 mTabIdsByMetadataFile = new HashMap<>();
290 mUnreferencedTabIds = new HashSet<>();
291
292 File[] stateFiles = getOrCreateStateDirectory().listFiles();
293 if (stateFiles == null) return null;
294
295 Set<Integer> allTabIds = new HashSet<>();
296 Set<Integer> allReferencedTabIds = new HashSet<>();
297 List<File> metadataFiles = new ArrayList<>();
298 for (File file : stateFiles) {
299 if (TabPersistentStore.isStateFile(file.getName())) {
300 metadataFiles.add(file);
301
302 SparseBooleanArray tabIds = new SparseBooleanArray();
303 mTabIdsByMetadataFile.put(file, tabIds);
304 getTabsFromStateFile(tabIds, file);
305 for (int i = 0; i < tabIds.size(); i++) {
306 allReferencedTabIds.add(tabIds.keyAt(i));
307 }
308 continue;
309 }
310
311 Pair<Integer, Boolean> tabInfo = TabState.parseInfoFromFilename( file.getName());
312 if (tabInfo == null) continue;
313 allTabIds.add(tabInfo.first);
314 }
315
316 mUnreferencedTabIds.addAll(allTabIds);
317 mUnreferencedTabIds.removeAll(allReferencedTabIds);
318
319 mDeletableMetadataFiles = getMetadataFilesForDeletion(
320 System.currentTimeMillis(), metadataFiles);
321 return null;
322 }
323
324 @Override
325 protected void onPostExecute(Void unused) {
326 List<String> filesToDelete = new ArrayList<>();
327 if (mDestroyed) {
328 mFilesToDeleteCallback.onResult(filesToDelete);
329 return;
330 }
331
332 if (mUnreferencedTabIds.isEmpty() && mDeletableMetadataFiles.isEmpty ()) {
333 mFilesToDeleteCallback.onResult(filesToDelete);
334 return;
335 }
336
337 Set<Integer> liveTabIds = new HashSet<>();
338 Set<Integer> liveTaskIds = new HashSet<>();
339 getAllLiveTabAndTaskIds(liveTabIds, liveTaskIds);
340
341 for (Integer unreferencedTabId : mUnreferencedTabIds) {
342 // Ignore tabs that are referenced by live activities as they mi ght not have been
343 // able to write out their state yet.
344 if (liveTabIds.contains(unreferencedTabId)) continue;
345
346 // The tab state is not referenced by any current activities or any metadata files,
347 // so mark it for deletion.
348 filesToDelete.add(TabState.getTabStateFilename(unreferencedTabId , false));
349 }
350
351 for (int i = 0; i < mDeletableMetadataFiles.size(); i++) {
352 File metadataFile = mDeletableMetadataFiles.get(i);
353 String id = TabPersistentStore.getStateFileUniqueId(metadataFile .getName());
354 try {
355 int taskId = Integer.parseInt(id);
356
357 // Ignore the metadata file if it belongs to a currently liv e CustomTabActivity.
358 if (liveTaskIds.contains(taskId)) continue;
359
360 filesToDelete.add(metadataFile.getName());
361
362 SparseBooleanArray unusedTabIds = mTabIdsByMetadataFile.get( metadataFile);
363 if (unusedTabIds == null) continue;
364 for (int j = 0; j < unusedTabIds.size(); j++) {
365 filesToDelete.add(TabState.getTabStateFilename(
366 unusedTabIds.keyAt(j), false));
367 }
368 } catch (NumberFormatException ex) {
369 assert false : "Unexpected tab metadata file found: " + meta dataFile.getName();
370 continue;
371 }
372 }
373
374 mFilesToDeleteCallback.onResult(filesToDelete);
375 }
376
377 private void getTabsFromStateFile(SparseBooleanArray tabIds, File metada taFile) {
378 DataInputStream stream = null;
379 try {
380 stream = new DataInputStream(
381 new BufferedInputStream(new FileInputStream(metadataFile )));
382 TabPersistentStore.readSavedStateFile(stream, null, tabIds, fals e);
383 } catch (Exception e) {
384 Log.e(TAG, "Unable to read state for " + metadataFile.getName() + ": " + e);
385 } finally {
386 StreamUtil.closeQuietly(stream);
387 }
388 }
389 }
390 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698