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

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

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 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.text.TextUtils;
20 import android.util.Log;
21 import android.util.Pair;
22 import android.util.SparseArray;
23
24 import com.google.android.apps.chrome.R;
25
26 import org.chromium.base.ApplicationStatus;
27 import org.chromium.base.ImportantFileWriterAndroid;
28 import org.chromium.chrome.browser.ApplicationLifetime;
29 import org.chromium.chrome.browser.ChromeMobileApplication;
30 import org.chromium.chrome.browser.IntentHandler;
31 import org.chromium.chrome.browser.Tab;
32 import org.chromium.chrome.browser.TabState;
33 import org.chromium.chrome.browser.UrlConstants;
34 import org.chromium.chrome.browser.UrlUtilities;
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.tabmodel.TabPersistentStore;
45 import org.chromium.chrome.browser.tabmodel.TabPersistentStore.OnTabStateReadCal lback;
46 import org.chromium.chrome.browser.tabmodel.document.ActivityDelegate;
47 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel;
48 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel.Entry;
49 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel.Initializa tionObserver;
50 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelImpl;
51 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelSelector;
52 import org.chromium.chrome.browser.tabmodel.document.OffTheRecordDocumentTabMode l;
53 import org.chromium.chrome.browser.tabmodel.document.StorageDelegate;
54
55 import java.io.File;
56 import java.io.IOException;
57 import java.util.ArrayList;
58 import java.util.List;
59
60 /**
61 * The class that carries out migration of tab states from/to document mode.
62 */
63 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
64 public class DocumentMigrationHelper {
65 private static final String TAG = "DocumentMigrationHelper";
66 private static final int[] ICON_TYPES = {FaviconHelper.FAVICON,
67 FaviconHelper.TOUCH_ICON | FaviconHelper.TOUCH_PRECOMPOSED_ICON};
68 private static final int DESIRED_ICON_SIZE_DP = 32;
69
70 public static final int FINALIZE_MODE_NO_ACTION = 0;
71 public static final int FINALIZE_MODE_FINISH_ACTIVITY = 1;
72 public static final int FINALIZE_MODE_RESTART_APP = 2;
73
74 private static class MigrationTabStateReadCallback implements OnTabStateRead Callback {
75 private int mSelectedTabId = Tab.INVALID_TAB_ID;
76
77 @Override
78 public void onDetailsRead(int index, int id, String url, boolean isStand ardActiveIndex,
79 boolean isIncognitoActiveIndex) {
80 Tab.incrementIdCounterTo(id + 1);
81 if (!isStandardActiveIndex) return;
82 // If the current tab read is the active standard tab, set the last used
83 // tab pref with the id, so that when document mode starts we show t hat
84 // tab first.
85 mSelectedTabId = id;
86 }
87
88 public int getSelectedTabId() {
89 return mSelectedTabId;
90 }
91 }
92
93 /**
94 * Stores a list of "tasks" that are meant to be returned by the ActivityMan ager for migration.
95 * The tasks are inserted manually during the migration from classic mode to document mode.
96 */
97 private static class MigrationActivityDelegate extends ActivityDelegate {
98 private final List<Entry> mEntries;
99 private final int mSelectedTabId;
100
101 private MigrationActivityDelegate(List<Entry> entries, int selectedTabId ) {
102 super(DocumentActivity.class, IncognitoDocumentActivity.class);
103 mEntries = entries;
104 mSelectedTabId = selectedTabId;
105 }
106
107 public int getSelectedTabId() {
108 return mSelectedTabId;
109 }
110
111 @Override
112 public boolean isValidActivity(boolean isIncognito, Intent intent) {
113 return true;
114 }
115
116 @Override
117 public List<Entry> getTasksFromRecents(boolean isIncognito) {
118 // We need to have our own list here, since these entries have not a ctually been
119 // created in Recents yet.
120 return mEntries;
121 }
122 }
123
124 private static class MigrationTabModel extends DocumentTabModelImpl {
125 private final SparseArray<String> mTitleList;
126
127 /**
128 * Constucts a {@link DocumentTabModel} to be used for migration.
129 * @param activityDelegate The delegate that has the tabs to be migrated .
130 * @param storageDelegate Delegate that interacts with the file system.
131 */
132 MigrationTabModel(MigrationActivityDelegate activityDelegate,
133 StorageDelegate storageDelegate) {
134 super(activityDelegate, storageDelegate, new TabDelegateImpl(), fals e,
135 Tab.INVALID_TAB_ID, ApplicationStatus.getApplicationContext( ));
136 startTabStateLoad();
137 mTitleList = new SparseArray<String>();
138 setLastShownId(activityDelegate.getSelectedTabId());
139 }
140
141 /**
142 * Returns the display title for the Document with the given ID.
143 * @param tabId The ID for the document to return the url for.
144 * @return The display title for the entry if it was found, null otherwi se.
145 */
146 public String getTitleForDocument(int tabId) {
147 String title = mTitleList.get(tabId);
148 return TextUtils.isEmpty(title) ? "" : title;
149 }
150
151 @Override
152 protected boolean shouldStartDeserialization(int currentState) {
153 return currentState == STATE_LOAD_TAB_STATE_BG_END;
154 }
155
156 @Override
157 protected void updateEntryInfoFromTabState(Entry entry, TabState tabStat e) {
158 super.updateEntryInfoFromTabState(entry, tabState);
159 mTitleList.put(entry.tabId, tabState.getDisplayTitleFromState());
160 }
161 }
162
163 /**
164 * Migrates all tab state to classic mode and creates a tab model file using the current
165 * {@link DocumentTabModel} instances.
166 * @param activity The activity to be finished after migration if necessary.
167 * @param finalizeMode The mode in which the migration should be finalized.
168 */
169 public static void migrateTabsFromDocumentToClassic(final Activity activity,
170 int finalizeMode) {
171 Context context = ApplicationStatus.getApplicationContext();
172 // Before migration we remove all incognito tabs and also remove the
173 // tabs that can not be reached through the {@link DocumentTabModel} ins tances.
174 List<Integer> tabIdsToRemove = new ArrayList<Integer>();
175
176 DocumentTabModelImpl normalTabModel = (DocumentTabModelImpl)
177 ChromeMobileApplication.getDocumentTabModelSelector().getModel(f alse);
178 OffTheRecordDocumentTabModel incognitoTabModel = (OffTheRecordDocumentTa bModel)
179 ChromeMobileApplication.getDocumentTabModelSelector().getModel(t rue);
180
181 // TODO(yusufo): Clean up this logic.
182 for (int i = 0; i < incognitoTabModel.getCount(); i++) {
183 tabIdsToRemove.add(incognitoTabModel.getTabAt(i).getId());
184 }
185
186 ActivityManager am =
187 (ActivityManager) context.getSystemService(Activity.ACTIVITY_SER VICE);
188 List<AppTask> taskList = am.getAppTasks();
189 for (int i = 0; i < taskList.size(); i++) {
190 Intent intent = DocumentUtils.getBaseIntentFromTask(taskList.get(i)) ;
191 int id = ActivityDelegate.getTabIdFromIntent(intent);
192 if (id == Tab.INVALID_TAB_ID) continue;
193 if (tabIdsToRemove.contains(id)) taskList.get(i).finishAndRemoveTask ();
194 }
195 incognitoTabModel.updateRecentlyClosed();
196
197 File migratedFolder = TabPersistentStore.getStateDirectory(context, 0);
198 String tabStatefileName = new File(migratedFolder,
199 TabPersistentStore.SAVED_STATE_FILE).getAbsolutePath();
200
201 // All the TabStates (incognito or not) live in the same directory.
202 File[] allTabs = normalTabModel.getStorageDelegate().getStateDirectory() .listFiles();
203 try {
204 for (int i = 0; i < allTabs.length; i++) {
205 String fileName = allTabs[i].getName();
206 Pair<Integer, Boolean> tabInfo = TabState.parseInfoFromFilename( fileName);
207 if (tabInfo == null) continue;
208 int tabId = tabInfo.first;
209
210 // Also remove the tab state file for the closed tabs.
211 boolean success;
212 if (!tabIdsToRemove.contains(tabId)) {
213 success = allTabs[i].renameTo(new File(migratedFolder, fileN ame));
214 } else {
215 success = allTabs[i].delete();
216 }
217
218 if (!success) Log.e(TAG, "Failed to move/delete file for tab ID: " + tabId);
219 }
220
221 if (normalTabModel.getCount() != 0) {
222 byte[] listData;
223 listData = TabPersistentStore.serializeTabLists(incognitoTabMode l, normalTabModel);
224 ImportantFileWriterAndroid.writeFileAtomically(tabStatefileName, listData);
225 }
226 } catch (IOException e) {
227 Log.e(TAG , "IO exception during tab migration, tab state might not restore correctly");
228 }
229 finalizeMigration(activity, finalizeMode);
230 }
231
232 /**
233 * Migrates all tab state to document mode and creates tasks for each curren tly open tab.
234 * @param activity Activity to be used while launching the tasks.
235 * @param finalizeMode The mode in which the migration should be finalized.
236 */
237 public static void migrateTabsFromClassicToDocument(
238 final Activity activity, final int finalizeMode) {
239 StorageDelegate storageDelegate = new StorageDelegate();
240 MigrationActivityDelegate activityDelegate =
241 createActivityDelegateWithTabsToMigrate(storageDelegate, activit y);
242 final MigrationTabModel normalTabModel =
243 new MigrationTabModel(activityDelegate, storageDelegate);
244
245 InitializationObserver observer = new InitializationObserver(normalTabMo del) {
246 @Override
247 protected void runImmediately() {
248 addAppTasksFromFiles(activity, normalTabModel, finalizeMode);
249 }
250
251 @Override
252 public boolean isSatisfied(int currentState) {
253 return currentState == DocumentTabModelImpl.STATE_DESERIALIZE_EN D;
254 }
255
256 @Override
257 public boolean isCanceled() {
258 return false;
259 }
260 };
261
262 observer.runWhenReady();
263 }
264
265 /**
266 * Migrate tabs saved in classic mode to document mode for an upgrade. This doesn't restart
267 * the app process but only finishes the {@link ChromeLauncherActivity} it w as being called
268 * with.
269 * @param activity The activity to use for carrying out and finalizing the m igration.
270 * @param finalizeMode The mode in which the migration should be finalized.
271 * @return Whether any tabs will be migrated.
272 */
273 public static boolean migrateTabsToDocumentForUpgrade(Activity activity,
274 int finalizeMode) {
275 ChromePreferenceManager.getInstance(activity).setAttemptedMigrationOnUpg rade();
276 File[] fileList = TabPersistentStore.getStateDirectory(activity, 0).list Files();
277 if (fileList == null || fileList.length == 0
278 || (fileList.length == 1
279 && fileList[0].getName().equals(TabPersistentStore.SAVED_STATE_F ILE))) {
280 return false;
281 }
282
283 migrateTabsFromClassicToDocument(activity, finalizeMode);
284 return true;
285 }
286
287 private static void finalizeMigration(Activity activity, final int mode) {
288 switch(mode) {
289 case FINALIZE_MODE_NO_ACTION:
290 return;
291 case FINALIZE_MODE_FINISH_ACTIVITY:
292 activity.finishAndRemoveTask();
293 return;
294 case FINALIZE_MODE_RESTART_APP:
295 ApplicationLifetime.terminate(true);
296 return;
297 default:
298 assert false;
299 }
300 }
301
302 private static MigrationActivityDelegate createActivityDelegateWithTabsToMig rate(
303 StorageDelegate storageDelegate, Activity activity) {
304 File migratedFolder = storageDelegate.getStateDirectory();
305 if (!migratedFolder.exists() && !migratedFolder.mkdir()) {
306 Log.e(TAG, "Failed to create folder: " + migratedFolder.getAbsoluteP ath());
307 }
308
309 // Create maps for all tabs that will be used during TabModel initializa tion.
310 final List<Entry> normalEntryMap = new ArrayList<Entry>();
311
312 int currentSelectorIndex = 0;
313 File currentFolder = TabPersistentStore.getStateDirectory(activity, curr entSelectorIndex);
314 MigrationTabStateReadCallback callback = new MigrationTabStateReadCallba ck();
315 while (currentFolder.listFiles() != null && currentFolder.listFiles().le ngth != 0) {
316 File[] allTabs = TabPersistentStore
317 .getStateDirectory(activity, currentSelectorIndex).listFiles ();
318 try {
319 TabPersistentStore.readSavedStateFile(currentFolder, callback);
320 } catch (IOException e) {
321 Log.e(TAG, "IO Exception while trying to get the last used tab i d");
322 }
323
324 for (int i = 0; i < allTabs.length; i++) {
325 // Move tab state file to the document side folder.
326 String fileName = allTabs[i].getName();
327 Pair<Integer, Boolean> tabInfo = TabState.parseInfoFromFilename( fileName);
328 if (tabInfo == null) continue;
329
330 boolean success;
331 if (tabInfo.second) {
332 success = allTabs[i].delete();
333 } else {
334 success = allTabs[i].renameTo(new File(migratedFolder, fileN ame));
335 normalEntryMap.add(new Entry(tabInfo.first, UrlConstants.NTP _URL));
336 }
337
338 if (!success) Log.e(TAG, "Failed to move/delete file: " + fileNa me);
339 }
340 currentSelectorIndex++;
341 currentFolder = TabPersistentStore.getStateDirectory(activity, curre ntSelectorIndex);
342 }
343
344 return new MigrationActivityDelegate(normalEntryMap, callback.getSelecte dTabId());
345 }
346
347 private static void addAppTasksFromFiles(final Activity activity,
348 final MigrationTabModel tabModel, final int finalizeMode) {
349 if (tabModel.getCount() == 0) {
350 finalizeMigration(activity, finalizeMode);
351 return;
352 }
353
354 final TabContentManager contentManager =
355 new TabContentManager(activity, new ContentOffsetProvider() {
356 @Override
357 public int getOverlayTranslateY() {
358 return 0;
359 }
360 }, DeviceClassManager.enableSnapshots());
361 FaviconHelper faviconHelper = new FaviconHelper();
362 for (int i = 0; i < tabModel.getCount(); i++) {
363 final int tabId = tabModel.getTabAt(i).getId();
364 String currentUrl = tabModel.getCurrentUrlForDocument(tabId);
365 String currentTitle = tabModel.getTitleForDocument(tabId);
366 final boolean finalizeWhenDone = i == tabModel.getCount() - 1;
367
368 // Use placeholders if we can't find anything for url and title.
369 if (TextUtils.isEmpty(currentUrl)) currentUrl = UrlConstants.NTP_URL ;
370 if (TextUtils.isEmpty(currentTitle)
371 && !NativePageFactory.isNativePageUrl(currentUrl, false)) {
372 currentTitle = UrlUtilities.getDomainAndRegistry(currentUrl, fal se);
373 }
374 final String url = currentUrl;
375 final String title = currentTitle;
376
377 faviconHelper.getLargestRawFaviconForUrl(
378 Profile.getLastUsedProfile().getOriginalProfile(),
379 url, ICON_TYPES, DESIRED_ICON_SIZE_DP,
380 new FaviconImageCallback() {
381 @Override
382 public void onFaviconAvailable(final Bitmap favicon, Str ing iconUrl) {
383 // Even if either the favicon or the thumbnail comes back null
384 // add the AppTask with the return values. The frame work handles a null
385 // favicon and addAppTask below handles null thumbna ils.
386 DecompressThumbnailCallback thumbnailCallback =
387 new DecompressThumbnailCallback() {
388 @Override
389 public void onFinishGetBitmap(Bitmap bitmap) {
390 if (!NativePageFactory.isNativePageUrl(url, false)
391 && !url.startsWith(UrlConstants.CHRO ME_SCHEME)) {
392 addAppTask(activity, tabId,
393 tabModel.getTabStateForDocument( tabId),
394 url, title, favicon, bitmap);
395 }
396 // TODO(yusufo) : Have a counter here to mak e sure all tabs
397 // before this one has been a dded.
398 if (finalizeWhenDone) {
399 finalizeMigration(activity, finalizeMode );
400 }
401 }
402 };
403 contentManager.getThumbnailForId(tabId, thumbnailCal lback);
404 }
405 });
406 }
407 }
408
409 private static void addAppTask(Activity activity, int tabId, TabState tabSta te,
410 String currentUrl, String title, Bitmap favicon, Bitmap bitmap) {
411 if (tabId == ActivityDelegate.getTabIdFromIntent(activity.getIntent())) return;
412 // Create intent and taskDescription.
413 Intent intent = new Intent(Intent.ACTION_VIEW,
414 DocumentTabModelSelector.createDocumentDataString(tabId, current Url));
415 intent.setClassName(activity, ChromeLauncherActivity.getDocumentClassNam e(false));
416 intent.putExtra(IntentHandler.EXTRA_PRESERVE_TASK, true);
417 ActivityManager am =
418 (ActivityManager) activity.getSystemService(Activity.ACTIVITY_SE RVICE);
419
420 Bitmap thumbnail = Bitmap.createBitmap(am.getAppTaskThumbnailSize().getW idth(),
421 am.getAppTaskThumbnailSize().getHeight(), Config.ARGB_8888);
422 Canvas canvas = new Canvas(thumbnail);
423 if (bitmap == null) {
424 canvas.drawColor(Color.WHITE);
425 } else {
426 float scale = Math.max(
427 (float) thumbnail.getWidth() / bitmap.getWidth(),
428 (float) thumbnail.getHeight() / bitmap.getHeight());
429 canvas.scale(scale, scale);
430 canvas.drawBitmap(bitmap, 0, 0, null);
431 }
432 TaskDescription taskDescription = new TaskDescription(title, favicon,
433 activity.getResources().getColor(R.color.default_primary_color)) ;
434 am.addAppTask(activity, intent, taskDescription, thumbnail);
435 Entry entry = new Entry(tabId, tabState);
436 DocumentTabModelImpl tabModel = (DocumentTabModelImpl) ChromeMobileAppli cation
437 .getDocumentTabModelSelector().getModel(false);
438 tabModel.addEntryForMigration(entry);
439 }
440
441
442 /**
443 * Migrates tabs with state to and from document mode.
444 * @param toDocumentMode Whether the user is opting out. If true the migrati on is from Document
445 * to Classic mode.
446 * @param activity The activity to use for launching intent if needed.
447 * @param terminate Whether the application process should be terminated aft er the migration.
448 */
449 public static void migrateTabs(boolean toDocumentMode, final Activity activi ty,
450 boolean terminate) {
451 int terminateMode = terminate ? FINALIZE_MODE_RESTART_APP : FINALIZE_MOD E_FINISH_ACTIVITY;
452 if (toDocumentMode) {
453 migrateTabsFromClassicToDocument(activity, terminateMode);
454 } else {
455 migrateTabsFromDocumentToClassic(activity, terminateMode);
456 }
457 }
458 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698