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

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/document/DocumentMigrationHelper.java

Issue 1982043002: 👀 Cleanup DocumentMode migration helpers. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 7 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 2015 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.document;
6
7 import android.annotation.TargetApi;
8 import android.app.Activity;
9 import android.app.ActivityManager;
10 import android.app.ActivityManager.AppTask;
11 import android.app.ActivityManager.TaskDescription;
12 import android.content.Context;
13 import android.content.Intent;
14 import android.graphics.Bitmap;
15 import android.graphics.Bitmap.Config;
16 import android.graphics.Canvas;
17 import android.graphics.Color;
18 import android.os.Build;
19 import android.os.StrictMode;
20 import android.text.TextUtils;
21 import android.util.Pair;
22 import android.util.SparseArray;
23
24 import org.chromium.base.ApiCompatibilityUtils;
25 import org.chromium.base.ContextUtils;
26 import org.chromium.base.ImportantFileWriterAndroid;
27 import org.chromium.base.Log;
28 import org.chromium.chrome.R;
29 import org.chromium.chrome.browser.ActivityTaskDescriptionIconGenerator;
30 import org.chromium.chrome.browser.ApplicationLifetime;
31 import org.chromium.chrome.browser.ChromeApplication;
32 import org.chromium.chrome.browser.IntentHandler;
33 import org.chromium.chrome.browser.TabState;
34 import org.chromium.chrome.browser.UrlConstants;
35 import org.chromium.chrome.browser.compositor.layouts.content.ContentOffsetProvi der;
36 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
37 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager. DecompressThumbnailCallback;
38 import org.chromium.chrome.browser.device.DeviceClassManager;
39 import org.chromium.chrome.browser.favicon.FaviconHelper;
40 import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback;
41 import org.chromium.chrome.browser.ntp.NativePageFactory;
42 import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
43 import org.chromium.chrome.browser.profiles.Profile;
44 import org.chromium.chrome.browser.tab.Tab;
45 import org.chromium.chrome.browser.tab.TabIdManager;
46 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
47 import org.chromium.chrome.browser.tabmodel.TabPersistentStore;
48 import org.chromium.chrome.browser.tabmodel.TabPersistentStore.OnTabStateReadCal lback;
49 import org.chromium.chrome.browser.tabmodel.document.ActivityDelegate;
50 import org.chromium.chrome.browser.tabmodel.document.ActivityDelegateImpl;
51 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel;
52 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel.Entry;
53 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel.Initializa tionObserver;
54 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelImpl;
55 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelSelector;
56 import org.chromium.chrome.browser.tabmodel.document.OffTheRecordDocumentTabMode l;
57 import org.chromium.chrome.browser.tabmodel.document.StorageDelegate;
58 import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
59 import org.chromium.chrome.browser.util.UrlUtilities;
60
61 import java.io.File;
62 import java.io.IOException;
63 import java.util.ArrayList;
64 import java.util.List;
65 import java.util.concurrent.atomic.AtomicInteger;
66
67 /**
68 * The class that carries out migration of tab states from/to document mode.
69 */
70 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
71 public class DocumentMigrationHelper {
72 private static final String TAG = "DocumentMigration";
73
74 public static final int FINALIZE_MODE_NO_ACTION = 0;
75 public static final int FINALIZE_MODE_FINISH_ACTIVITY = 1;
76 public static final int FINALIZE_MODE_RESTART_APP = 2;
77
78 private static final List<MigrationImageCallback> sMigrationCallbacks =
79 new ArrayList<MigrationImageCallback>();
80
81 private static class MigrationTabStateReadCallback implements OnTabStateRead Callback {
82 private int mSelectedTabId = Tab.INVALID_TAB_ID;
83
84 @Override
85 public void onDetailsRead(int index, int id, String url, Boolean isIncog nito,
86 boolean isStandardActiveIndex, boolean isIncognitoActiveIndex) {
87 TabIdManager.getInstance().incrementIdCounterTo(id + 1);
88 if (!isStandardActiveIndex) return;
89 // If the current tab read is the active standard tab, set the last used
90 // tab pref with the id, so that when document mode starts we show t hat
91 // tab first.
92 mSelectedTabId = id;
93 }
94
95 public int getSelectedTabId() {
96 return mSelectedTabId;
97 }
98 }
99
100 /**
101 * Stores a list of "tasks" that are meant to be returned by the ActivityMan ager for migration.
102 * The tasks are inserted manually during the migration from classic mode to document mode.
103 */
104 private static class MigrationActivityDelegate extends ActivityDelegateImpl {
105 private final List<Entry> mEntries;
106 private final int mSelectedTabId;
107
108 private MigrationActivityDelegate(List<Entry> entries, int selectedTabId ) {
109 super(DocumentActivity.class, IncognitoDocumentActivity.class);
110 mEntries = entries;
111 mSelectedTabId = selectedTabId;
112 }
113
114 public int getSelectedTabId() {
115 return mSelectedTabId;
116 }
117
118 @Override
119 public boolean isValidActivity(boolean isIncognito, Intent intent) {
120 return true;
121 }
122
123 @Override
124 public List<Entry> getTasksFromRecents(boolean isIncognito) {
125 // We need to have our own list here, since these entries have not a ctually been
126 // created in Recents yet.
127 return mEntries;
128 }
129 }
130
131 private static class MigrationTabCreatorManager implements TabCreatorManager {
132 TabDelegate mRegularTabCreator = new TabDelegate(false);
133 TabDelegate mIncognitoTabCreator = new TabDelegate(true);
134
135 @Override
136 public TabDelegate getTabCreator(boolean incognito) {
137 return incognito ? mIncognitoTabCreator : mRegularTabCreator;
138 }
139 }
140
141 private static class MigrationTabModel extends DocumentTabModelImpl {
142 private final SparseArray<String> mTitleList;
143
144 /**
145 * Constucts a {@link DocumentTabModel} to be used for migration.
146 * @param activityDelegate The delegate that has the tabs to be migrated .
147 * @param storageDelegate Delegate that interacts with the file system.
148 */
149 MigrationTabModel(MigrationActivityDelegate activityDelegate,
150 StorageDelegate storageDelegate) {
151 super(activityDelegate, storageDelegate, new MigrationTabCreatorMana ger(), false,
152 Tab.INVALID_TAB_ID, ContextUtils.getApplicationContext());
153 startTabStateLoad();
154 mTitleList = new SparseArray<String>();
155 setLastShownId(activityDelegate.getSelectedTabId());
156 }
157
158 /**
159 * Returns the display title for the Document with the given ID.
160 * @param tabId The ID for the document to return the url for.
161 * @return The display title for the entry if it was found, null otherwi se.
162 */
163 public String getTitleForDocument(int tabId) {
164 String title = mTitleList.get(tabId);
165 return TextUtils.isEmpty(title) ? "" : title;
166 }
167
168 @Override
169 protected boolean shouldStartDeserialization(int currentState) {
170 return currentState == STATE_LOAD_TAB_STATE_BG_END;
171 }
172
173 @Override
174 protected void updateEntryInfoFromTabState(Entry entry, TabState tabStat e) {
175 super.updateEntryInfoFromTabState(entry, tabState);
176 mTitleList.put(entry.tabId, tabState.getDisplayTitleFromState());
177 }
178 }
179
180 private static class MigrationImageCallback
181 implements FaviconImageCallback, DecompressThumbnailCallback {
182 private final AtomicInteger mCompletedCount;
183 private final Activity mActivity;
184 private final TabContentManager mTabContentManager;
185 private final MigrationTabModel mTabModel;
186 private final int mTabId;
187 private final String mUrl;
188 private final String mTitle;
189 private final int mFinalizeMode;
190
191 private Bitmap mIcon;
192
193 public MigrationImageCallback(final AtomicInteger completedCount, final Activity activity,
194 final TabContentManager tabContentManager, final MigrationTabMod el tabModel,
195 final int tabId, final String url, final String title, final int finalizeMode) {
196 mCompletedCount = completedCount;
197 mActivity = activity;
198 mTabContentManager = tabContentManager;
199 mTabModel = tabModel;
200 mTabId = tabId;
201 mUrl = url;
202 mTitle = title;
203 mFinalizeMode = finalizeMode;
204 }
205
206 @Override
207 public void onFaviconAvailable(final Bitmap favicon, String iconUrl) {
208 mIcon = new ActivityTaskDescriptionIconGenerator(mActivity).getBitma p(mUrl, favicon);
209 mTabContentManager.getThumbnailForId(mTabId, this);
210 }
211
212 @Override
213 public void onFinishGetBitmap(Bitmap bitmap) {
214 // Even if the favicon or thumbnail are null, we still add the AppTa sk: the framework
215 // handles null favicons and addAppTask() below handles null thumbna ils.
216 if (!NativePageFactory.isNativePageUrl(mUrl, false)
217 && !mUrl.startsWith(UrlConstants.CHROME_SCHEME)) {
218 addAppTask(mActivity, mTabId, mTabModel.getTabStateForDocument(m TabId), mUrl,
219 mTitle, mIcon, bitmap);
220 }
221
222 if (mCompletedCount.incrementAndGet() == mTabModel.getCount()) {
223 mTabContentManager.destroy();
224 finalizeMigration(mActivity, mFinalizeMode);
225 }
226 }
227 }
228
229 /**
230 * Migrates all tab state to classic mode and creates a tab model file using the current
231 * {@link DocumentTabModel} instances.
232 * @param activity The activity to be finished after migration if necessary.
233 * @param finalizeMode The mode in which the migration should be finalized.
234 */
235 public static void migrateTabsFromDocumentToClassic(final Activity activity,
236 int finalizeMode) {
237 Context context = ContextUtils.getApplicationContext();
238 // Before migration we remove all incognito tabs and also remove the
239 // tabs that can not be reached through the {@link DocumentTabModel} ins tances.
240 List<Integer> tabIdsToRemove = new ArrayList<Integer>();
241
242 DocumentTabModelImpl normalTabModel = (DocumentTabModelImpl)
243 ChromeApplication.getDocumentTabModelSelector().getModel(false);
244 OffTheRecordDocumentTabModel incognitoTabModel = (OffTheRecordDocumentTa bModel)
245 ChromeApplication.getDocumentTabModelSelector().getModel(true);
246
247 // TODO(yusufo): Clean up this logic.
248 for (int i = 0; i < incognitoTabModel.getCount(); i++) {
249 tabIdsToRemove.add(incognitoTabModel.getTabAt(i).getId());
250 }
251
252 ActivityManager am =
253 (ActivityManager) context.getSystemService(Activity.ACTIVITY_SER VICE);
254 List<AppTask> taskList = am.getAppTasks();
255 for (int i = 0; i < taskList.size(); i++) {
256 Intent intent = DocumentUtils.getBaseIntentFromTask(taskList.get(i)) ;
257 int id = ActivityDelegate.getTabIdFromIntent(intent);
258 if (id == Tab.INVALID_TAB_ID) continue;
259 if (tabIdsToRemove.contains(id)) taskList.get(i).finishAndRemoveTask ();
260 }
261 incognitoTabModel.updateRecentlyClosed();
262
263 File migratedFolder = TabPersistentStore.getStateDirectory(context, 0);
264 String tabStatefileName = new File(migratedFolder,
265 TabPersistentStore.SAVED_STATE_FILE).getAbsolutePath();
266
267 // All the TabStates (incognito or not) live in the same directory.
268 File[] allTabs = normalTabModel.getStorageDelegate().getStateDirectory() .listFiles();
269 try {
270 if (allTabs != null) {
271 for (int i = 0; i < allTabs.length; i++) {
272 String fileName = allTabs[i].getName();
273 Pair<Integer, Boolean> tabInfo = TabState.parseInfoFromFilen ame(fileName);
274 if (tabInfo == null) continue;
275 int tabId = tabInfo.first;
276
277 // Also remove the tab state file for the closed tabs.
278 boolean success;
279 if (!tabIdsToRemove.contains(tabId)) {
280 success = allTabs[i].renameTo(new File(migratedFolder, f ileName));
281 } else {
282 success = allTabs[i].delete();
283 }
284
285 if (!success) Log.e(TAG, "Failed to move/delete file for tab ID: " + tabId);
286 }
287 }
288
289 if (normalTabModel.getCount() != 0) {
290 byte[] listData;
291 listData = TabPersistentStore.serializeTabModelSelector(
292 ChromeApplication.getDocumentTabModelSelector(), null);
293 ImportantFileWriterAndroid.writeFileAtomically(tabStatefileName, listData);
294 }
295 } catch (IOException e) {
296 Log.e(TAG , "IO exception during tab migration, tab state might not restore correctly");
297 }
298 finalizeMigration(activity, finalizeMode);
299 }
300
301 /**
302 * Migrates all tab state to document mode and creates tasks for each curren tly open tab.
303 * @param activity Activity to be used while launching the tasks.
304 * @param finalizeMode The mode in which the migration should be finalized.
305 */
306 public static void migrateTabsFromClassicToDocument(
307 final Activity activity, final int finalizeMode) {
308 StorageDelegate storageDelegate = new StorageDelegate();
309 MigrationActivityDelegate activityDelegate =
310 createActivityDelegateWithTabsToMigrate(storageDelegate, activit y);
311 final MigrationTabModel normalTabModel =
312 new MigrationTabModel(activityDelegate, storageDelegate);
313
314 InitializationObserver observer = new InitializationObserver(normalTabMo del) {
315 @Override
316 protected void runImmediately() {
317 addAppTasksFromFiles(activity, normalTabModel, finalizeMode);
318 }
319
320 @Override
321 public boolean isSatisfied(int currentState) {
322 return currentState == DocumentTabModelImpl.STATE_DESERIALIZE_EN D;
323 }
324
325 @Override
326 public boolean isCanceled() {
327 return false;
328 }
329 };
330
331 observer.runWhenReady();
332 }
333
334 /**
335 * Migrate tabs saved in classic mode to document mode for an upgrade. This doesn't restart
336 * the app process but only finishes the {@link ChromeLauncherActivity} it w as being called
337 * with.
338 * @param activity The activity to use for carrying out and finalizing the m igration.
339 * @param finalizeMode The mode in which the migration should be finalized.
340 * @return Whether any tabs will be migrated.
341 */
342 public static boolean migrateTabsToDocumentForUpgrade(Activity activity,
343 int finalizeMode) {
344 // Allow StrictMode violations here as state file read/write is on the c ritical path. This
345 // is also a one-time migration, so optimizing further does not improve daily experience.
346 // See http://crbug.com/493157 for more context.
347 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
348 StrictMode.allowThreadDiskWrites();
349 try {
350 ChromePreferenceManager.getInstance(activity).setAttemptedMigrationO nUpgrade();
351
352 File[] fileList = TabPersistentStore.getStateDirectory(activity, 0). listFiles();
353 if (fileList == null || fileList.length == 0
354 || (fileList.length == 1
355 && fileList[0].getName().equals(TabPersistentStore.SAVED_STA TE_FILE))) {
356 return false;
357 }
358
359 migrateTabsFromClassicToDocument(activity, finalizeMode);
360 return true;
361 } finally {
362 StrictMode.setThreadPolicy(oldPolicy);
363 }
364 }
365
366 private static void finalizeMigration(Activity activity, final int mode) {
367 sMigrationCallbacks.clear();
368
369 switch(mode) {
370 case FINALIZE_MODE_NO_ACTION:
371 return;
372 case FINALIZE_MODE_FINISH_ACTIVITY:
373 activity.finishAndRemoveTask();
374 return;
375 case FINALIZE_MODE_RESTART_APP:
376 ApplicationLifetime.terminate(true);
377 return;
378 default:
379 assert false;
380 }
381 }
382
383 private static MigrationActivityDelegate createActivityDelegateWithTabsToMig rate(
384 StorageDelegate storageDelegate, Activity activity) {
385 File migratedFolder = storageDelegate.getStateDirectory();
386 if (!migratedFolder.exists() && !migratedFolder.mkdir()) {
387 Log.e(TAG, "Failed to create folder: " + migratedFolder.getAbsoluteP ath());
388 }
389
390 // Create maps for all tabs that will be used during TabModel initializa tion.
391 final List<Entry> normalEntryMap = new ArrayList<Entry>();
392
393 File currentFolder = null;
394 MigrationTabStateReadCallback callback = new MigrationTabStateReadCallba ck();
395 for (int currentSelectorIndex = 0;
396 (currentFolder = getNextNonEmptyFolder(activity, currentSelector Index)) != null;
397 ++currentSelectorIndex) {
398 File[] allTabs = TabPersistentStore
399 .getStateDirectory(activity, currentSelectorIndex).listFiles ();
400 try {
401 TabPersistentStore.readSavedStateFile(currentFolder, callback);
402 } catch (IOException e) {
403 Log.e(TAG, "IO Exception while trying to get the last used tab i d");
404 }
405
406 if (allTabs != null) {
407 for (int i = 0; i < allTabs.length; i++) {
408 // Move tab state file to the document side folder.
409 String fileName = allTabs[i].getName();
410 Pair<Integer, Boolean> tabInfo = TabState.parseInfoFromFilen ame(fileName);
411 if (tabInfo == null) continue;
412
413 boolean success;
414 if (tabInfo.second) {
415 success = allTabs[i].delete();
416 } else {
417 success = allTabs[i].renameTo(new File(migratedFolder, f ileName));
418 normalEntryMap.add(new Entry(tabInfo.first, UrlConstants .NTP_URL));
419 }
420
421 if (!success) Log.e(TAG, "Failed to move/delete file: " + fi leName);
422 }
423 }
424 }
425
426 return new MigrationActivityDelegate(normalEntryMap, callback.getSelecte dTabId());
427 }
428
429 private static File getNextNonEmptyFolder(Activity activity, int currentSele ctorIndex) {
430 File folder = TabPersistentStore.getStateDirectory(activity, currentSele ctorIndex);
431 File[] files = folder.listFiles();
432 return (files == null || files.length == 0) ? null : folder;
433 }
434
435 private static void addAppTasksFromFiles(final Activity activity,
436 final MigrationTabModel tabModel, final int finalizeMode) {
437 assert sMigrationCallbacks.size() == 0;
438
439 if (tabModel.getCount() == 0) {
440 finalizeMigration(activity, finalizeMode);
441 return;
442 }
443
444 final AtomicInteger completedCount = new AtomicInteger();
445 final TabContentManager contentManager =
446 new TabContentManager(activity, new ContentOffsetProvider() {
447 @Override
448 public int getOverlayTranslateY() {
449 return 0;
450 }
451 }, DeviceClassManager.enableSnapshots());
452 FaviconHelper faviconHelper = new FaviconHelper();
453 for (int i = 0; i < tabModel.getCount(); i++) {
454 final int tabId = tabModel.getTabAt(i).getId();
455 String currentUrl = tabModel.getCurrentUrlForDocument(tabId);
456 String currentTitle = tabModel.getTitleForDocument(tabId);
457
458 // Use placeholders if we can't find anything for url and title.
459 if (TextUtils.isEmpty(currentUrl)) currentUrl = UrlConstants.NTP_URL ;
460 if (TextUtils.isEmpty(currentTitle)
461 && !NativePageFactory.isNativePageUrl(currentUrl, false)) {
462 currentTitle = UrlUtilities.getDomainAndRegistry(currentUrl, fal se);
463 }
464 final String url = currentUrl;
465 final String title = currentTitle;
466
467 MigrationImageCallback imageCallback = new MigrationImageCallback(co mpletedCount,
468 activity, contentManager, tabModel, tabId, url, title, final izeMode);
469 faviconHelper.getLocalFaviconImageForURL(
470 Profile.getLastUsedProfile().getOriginalProfile(),
471 url, 0, imageCallback);
472 sMigrationCallbacks.add(imageCallback);
473 }
474 }
475
476 private static void addAppTask(Activity activity, int tabId, TabState tabSta te,
477 String currentUrl, String title, Bitmap favicon, Bitmap bitmap) {
478 if (tabId == ActivityDelegate.getTabIdFromIntent(activity.getIntent())) return;
479 // Create intent and taskDescription.
480 Intent intent = new Intent(Intent.ACTION_VIEW,
481 DocumentTabModelSelector.createDocumentDataString(tabId, current Url));
482 intent.setClassName(activity, ChromeLauncherActivity.getDocumentClassNam e(false));
483 intent.putExtra(IntentHandler.EXTRA_PRESERVE_TASK, true);
484 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
485 ActivityManager am =
486 (ActivityManager) activity.getSystemService(Activity.ACTIVITY_SE RVICE);
487
488 Bitmap thumbnail = Bitmap.createBitmap(am.getAppTaskThumbnailSize().getW idth(),
489 am.getAppTaskThumbnailSize().getHeight(), Config.ARGB_8888);
490 Canvas canvas = new Canvas(thumbnail);
491 if (bitmap == null) {
492 canvas.drawColor(Color.WHITE);
493 } else {
494 float scale = Math.max(
495 (float) thumbnail.getWidth() / bitmap.getWidth(),
496 (float) thumbnail.getHeight() / bitmap.getHeight());
497 canvas.scale(scale, scale);
498 canvas.drawBitmap(bitmap, 0, 0, null);
499 }
500 TaskDescription taskDescription = new TaskDescription(title, favicon,
501 ApiCompatibilityUtils.getColor(activity.getResources(),
502 R.color.default_primary_color));
503 am.addAppTask(activity, intent, taskDescription, thumbnail);
504 Entry entry = new Entry(tabId, tabState);
505 DocumentTabModelImpl tabModel = (DocumentTabModelImpl) ChromeApplication
506 .getDocumentTabModelSelector().getModel(false);
507 tabModel.addEntryForMigration(entry);
508 }
509
510
511 /**
512 * Migrates tabs with state to and from document mode.
513 * @param toDocumentMode Whether the user is opting out. If true the migrati on is from Document
514 * to Classic mode.
515 * @param activity The activity to use for launching intent if needed.
516 * @param terminate Whether the application process should be terminated aft er the migration.
517 */
518 public static void migrateTabs(boolean toDocumentMode, final Activity activi ty,
519 boolean terminate) {
520 // Allowing StrictMode violations as above. See http://crbug.com/493157 for context.
521 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
522 StrictMode.allowThreadDiskWrites();
523 try {
524 int terminateMode =
525 terminate ? FINALIZE_MODE_RESTART_APP : FINALIZE_MODE_FINISH _ACTIVITY;
526 if (toDocumentMode) {
527 migrateTabsFromClassicToDocument(activity, terminateMode);
528 } else {
529 migrateTabsFromDocumentToClassic(activity, terminateMode);
530 }
531 } finally {
532 StrictMode.setThreadPolicy(oldPolicy);
533 }
534 }
535 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698