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

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/tabmodel/DocumentModeAssassin.java

Issue 1838333003: Add fallback for DocumentModeAssassin (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Stupid proguard Created 4 years, 8 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
1 // Copyright 2016 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.chrome.browser.tabmodel; 5 package org.chromium.chrome.browser.tabmodel;
6 6
7 import android.annotation.TargetApi; 7 import android.annotation.TargetApi;
8 import android.content.Context; 8 import android.content.Context;
9 import android.content.SharedPreferences; 9 import android.content.SharedPreferences;
10 import android.os.AsyncTask; 10 import android.os.AsyncTask;
11 import android.os.Build; 11 import android.os.Build;
12 import android.preference.PreferenceManager;
12 import android.util.Pair; 13 import android.util.Pair;
13 14
14 import org.chromium.base.ApplicationStatus; 15 import org.chromium.base.ApplicationStatus;
15 import org.chromium.base.CommandLine; 16 import org.chromium.base.CommandLine;
16 import org.chromium.base.FileUtils; 17 import org.chromium.base.FileUtils;
17 import org.chromium.base.Log; 18 import org.chromium.base.Log;
18 import org.chromium.base.ObserverList; 19 import org.chromium.base.ObserverList;
19 import org.chromium.base.StreamUtil; 20 import org.chromium.base.StreamUtil;
20 import org.chromium.base.ThreadUtils; 21 import org.chromium.base.ThreadUtils;
21 import org.chromium.base.VisibleForTesting; 22 import org.chromium.base.VisibleForTesting;
22 import org.chromium.chrome.browser.ChromeApplication; 23 import org.chromium.chrome.browser.ChromeApplication;
23 import org.chromium.chrome.browser.ChromeSwitches; 24 import org.chromium.chrome.browser.ChromeSwitches;
24 import org.chromium.chrome.browser.TabState; 25 import org.chromium.chrome.browser.TabState;
25 import org.chromium.chrome.browser.document.DocumentActivity; 26 import org.chromium.chrome.browser.document.DocumentActivity;
26 import org.chromium.chrome.browser.document.DocumentUtils; 27 import org.chromium.chrome.browser.document.DocumentUtils;
27 import org.chromium.chrome.browser.document.IncognitoDocumentActivity; 28 import org.chromium.chrome.browser.document.IncognitoDocumentActivity;
29 import org.chromium.chrome.browser.document.IncognitoNotificationManager;
28 import org.chromium.chrome.browser.preferences.DocumentModeManager; 30 import org.chromium.chrome.browser.preferences.DocumentModeManager;
29 import org.chromium.chrome.browser.tab.Tab; 31 import org.chromium.chrome.browser.tab.Tab;
30 import org.chromium.chrome.browser.tabmodel.TabPersistentStore.TabModelMetadata; 32 import org.chromium.chrome.browser.tabmodel.TabPersistentStore.TabModelMetadata;
31 import org.chromium.chrome.browser.tabmodel.document.ActivityDelegate; 33 import org.chromium.chrome.browser.tabmodel.document.ActivityDelegate;
32 import org.chromium.chrome.browser.tabmodel.document.ActivityDelegateImpl; 34 import org.chromium.chrome.browser.tabmodel.document.ActivityDelegateImpl;
33 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel; 35 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel;
34 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelImpl; 36 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelImpl;
35 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelSelector;
36 import org.chromium.chrome.browser.tabmodel.document.StorageDelegate; 37 import org.chromium.chrome.browser.tabmodel.document.StorageDelegate;
37 import org.chromium.chrome.browser.util.FeatureUtilities; 38 import org.chromium.chrome.browser.util.FeatureUtilities;
38 39
39 import java.io.File; 40 import java.io.File;
40 import java.io.FileInputStream; 41 import java.io.FileInputStream;
41 import java.io.FileOutputStream; 42 import java.io.FileOutputStream;
42 import java.io.IOException; 43 import java.io.IOException;
43 import java.nio.channels.FileChannel; 44 import java.nio.channels.FileChannel;
44 import java.util.HashSet; 45 import java.util.HashSet;
45 import java.util.Set; 46 import java.util.Set;
46 47
47 import javax.annotation.Nullable;
48
49 /** 48 /**
50 * Divorces Chrome's tabs from Android's Overview menu. Assumes native librarie s are unavailable. 49 * Divorces Chrome's tabs from Android's Overview menu. Assumes native librarie s are unavailable.
51 * 50 *
52 * Migration from document mode to tabbed mode occurs in two main phases: 51 * Migration from document mode to tabbed mode occurs in two main phases:
53 * 52 *
54 * 1) NON-DESTRUCTIVE MIGRATION: 53 * 1) NON-DESTRUCTIVE MIGRATION:
55 * TabState files for the normal DocumentTabModel are copied from the documen t mode directories 54 * TabState files for the normal DocumentTabModel are copied from the documen t mode directories
56 * into the tabbed mode directory. Incognito tabs are silently dropped, as w ith the previous 55 * into the tabbed mode directory. Incognito tabs are silently dropped, as w ith the previous
57 * migration pathway. 56 * migration pathway.
58 * 57 *
59 * TODO(dfalcantara): Check what happens on other launchers.
60 *
61 * Once all TabState files are copied, a TabModel metadata file is written ou t for the tabbed 58 * Once all TabState files are copied, a TabModel metadata file is written ou t for the tabbed
62 * mode {@link TabModelImpl} to read out. Because the native library is not available, the file 59 * mode {@link TabModelImpl} to read out. Because the native library is not available, the file
63 * will be incomplete but usable; it will be corrected by the TabModelImpl wh en it loads it and 60 * will be incomplete but usable; it will be corrected by the TabModelImpl wh en it loads it and
64 * all of the TabState files up. See {@link #writeTabModelMetadata} for deta ils. 61 * all of the TabState files up. See {@link #writeTabModelMetadata} for deta ils.
65 * 62 *
66 * 2) CLEANUP OF ALL DOCUMENT-RELATED THINGS: 63 * 2) CLEANUP OF ALL DOCUMENT-RELATED THINGS:
67 * DocumentActivity tasks in Android's Recents are removed, TabState files in the document mode 64 * DocumentActivity tasks in Android's Recents are removed, TabState files in the document mode
68 * directory are deleted, and document mode preferences are cleared. 65 * directory are deleted, and document mode preferences are cleared.
69 * 66 *
70 * TODO(dfalcantara): Add histograms for tracking migration progress. 67 * TODO(dfalcantara): Add histograms for tracking migration progress?
71 *
72 * TODO(dfalcantara): Potential pitfalls that need to be accounted for:
73 * - Consistently crashing during migration means you can never open Chrome un til you clear data.
74 * - Successfully migrating, but crashing while deleting things and closing of f tasks.
75 * - Failing to copy all the TabState files over during migration because of a lack of space.
76 */ 68 */
77 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 69 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
78 public class DocumentModeAssassin { 70 public class DocumentModeAssassin {
79 /** Alerted about progress along the migration pipeline. */ 71 /** Alerted about progress along the migration pipeline. */
80 public static class DocumentModeAssassinObserver { 72 public static class DocumentModeAssassinObserver {
81 /** 73 /**
82 * Called on the UI thread when the DocumentModeAssassin has progressed along its pipeline, 74 * Called on the UI thread when the DocumentModeAssassin has progressed along its pipeline,
83 * and when the DocumentModeAssasssinObserver is first added to the Docu mentModeAssassin. 75 * and when the DocumentModeAssasssinObserver is first added to the Docu mentModeAssassin.
84 * 76 *
85 * @param newStage New stage of the pipeline. 77 * @param newStage New stage of the pipeline.
(...skipping 16 matching lines...) Expand all
102 static final int STAGE_INITIALIZED = 1; 94 static final int STAGE_INITIALIZED = 1;
103 static final int STAGE_COPY_TAB_STATES_STARTED = 2; 95 static final int STAGE_COPY_TAB_STATES_STARTED = 2;
104 static final int STAGE_COPY_TAB_STATES_DONE = 3; 96 static final int STAGE_COPY_TAB_STATES_DONE = 3;
105 static final int STAGE_WRITE_TABMODEL_METADATA_STARTED = 4; 97 static final int STAGE_WRITE_TABMODEL_METADATA_STARTED = 4;
106 static final int STAGE_WRITE_TABMODEL_METADATA_DONE = 5; 98 static final int STAGE_WRITE_TABMODEL_METADATA_DONE = 5;
107 static final int STAGE_CHANGE_SETTINGS_STARTED = 6; 99 static final int STAGE_CHANGE_SETTINGS_STARTED = 6;
108 static final int STAGE_CHANGE_SETTINGS_DONE = 7; 100 static final int STAGE_CHANGE_SETTINGS_DONE = 7;
109 static final int STAGE_DELETION_STARTED = 8; 101 static final int STAGE_DELETION_STARTED = 8;
110 public static final int STAGE_DONE = 9; 102 public static final int STAGE_DONE = 9;
111 103
104 static final String PREF_NUM_MIGRATION_ATTEMPTS =
105 "org.chromium.chrome.browser.tabmodel.NUM_MIGRATION_ATTEMPTS";
106 static final int MAX_MIGRATION_ATTEMPTS_BEFORE_FAILURE = 3;
112 private static final String TAG = "DocumentModeAssassin"; 107 private static final String TAG = "DocumentModeAssassin";
113 108
114 /** Which TabModelSelectorImpl to copy files into during migration. */ 109 /** Which TabModelSelectorImpl to copy files into during migration. */
115 private static final int TAB_MODEL_INDEX = 0; 110 private static final int TAB_MODEL_INDEX = 0;
116 111
117 /** Creates and holds the Singleton. */ 112 /** Creates and holds the Singleton. */
118 private static class LazyHolder { 113 private static class LazyHolder {
119 private static final DocumentModeAssassin INSTANCE = new DocumentModeAss assin(); 114 private static final DocumentModeAssassin INSTANCE = new DocumentModeAss assin();
120 } 115 }
121 116
122 /** Returns the Singleton instance. */ 117 /** Returns the Singleton instance. */
123 public static DocumentModeAssassin getInstance() { 118 public static DocumentModeAssassin getInstance() {
124 return LazyHolder.INSTANCE; 119 return LazyHolder.INSTANCE;
125 } 120 }
126 121
127 /** IDs of Tabs that have had their TabState files copied between directorie s successfully. */ 122 /** IDs of Tabs that have had their TabState files copied between directorie s successfully. */
128 private final Set<Integer> mMigratedTabIds = new HashSet<Integer>(); 123 private final Set<Integer> mMigratedTabIds = new HashSet<Integer>();
129 124
130 /** Observers of the migration pipeline. */ 125 /** Observers of the migration pipeline. */
131 private final ObserverList<DocumentModeAssassinObserver> mObservers = 126 private final ObserverList<DocumentModeAssassinObserver> mObservers =
132 new ObserverList<DocumentModeAssassinObserver>(); 127 new ObserverList<DocumentModeAssassinObserver>();
133 128
134 /** Current stage of the migration. */ 129 /** Current stage of the migration. */
135 private int mStage = STAGE_UNINITIALIZED; 130 private int mStage = STAGE_UNINITIALIZED;
136 131
137 /** Whether or not startStage is allowed to progress along the migration pip eline. */ 132 /** Whether or not startStage is allowed to progress along the migration pip eline. */
138 private boolean mIsPipelineActive; 133 private boolean mIsPipelineActive;
139 134
140 /** Returns whether or not a migration to tabbed mode from document mode is necessary. */
141 public static boolean isMigrationNecessary() {
142 return CommandLine.getInstance().hasSwitch(ChromeSwitches.ENABLE_FORCED_ MIGRATION)
143 && FeatureUtilities.isDocumentMode(ApplicationStatus.getApplicat ionContext());
144 }
145
146 /** Migrates the user from document mode to tabbed mode if necessary. */ 135 /** Migrates the user from document mode to tabbed mode if necessary. */
147 @VisibleForTesting 136 @VisibleForTesting
148 public void migrateFromDocumentToTabbedMode() { 137 public final void migrateFromDocumentToTabbedMode() {
149 ThreadUtils.assertOnUiThread(); 138 ThreadUtils.assertOnUiThread();
150 139
140 // Migration is already underway.
141 if (mStage != STAGE_UNINITIALIZED) return;
142
143 // If migration isn't necessary, don't do anything.
151 if (!isMigrationNecessary()) { 144 if (!isMigrationNecessary()) {
152 // Don't kick off anything if we don't need to.
153 setStage(STAGE_UNINITIALIZED, STAGE_DONE); 145 setStage(STAGE_UNINITIALIZED, STAGE_DONE);
154 return; 146 return;
155 } else if (mStage != STAGE_UNINITIALIZED) {
156 // Migration is already underway.
157 return;
158 } 147 }
159 148
160 // TODO(dfalcantara): Add a pathway to catch repeated migration failures . 149 // If we've crashed or failed too many times, send them to tabbed mode w ithout their data.
150 // - Any incorrect or invalid files in the tabbed mode directory will be wiped out by the
151 // TabPersistentStore when the ChromeTabbedActivity starts.
152 //
153 // - If it crashes in the step after being migrated, then the user will simply be left
154 // with a bunch of inaccessible document mode data instead of being st uck trying to
155 // migrate, which is a lesser evil. This case will be caught by the c heck above to see if
156 // migration is even necessary.
157 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( getContext());
158 int numMigrationAttempts = prefs.getInt(PREF_NUM_MIGRATION_ATTEMPTS, 0);
159 if (numMigrationAttempts >= MAX_MIGRATION_ATTEMPTS_BEFORE_FAILURE) {
160 Log.e(TAG, "Too many failures. Migrating user to tabbed mode withou t data.");
161 setStage(STAGE_UNINITIALIZED, STAGE_WRITE_TABMODEL_METADATA_DONE);
162 return;
163 }
161 164
165 // Kick off the migration pipeline.
166 // Using apply() instead of commit() seems to save the preference just f ine, even if Chrome
167 // crashes immediately afterward.
168 SharedPreferences.Editor editor = prefs.edit();
169 editor.putInt(PREF_NUM_MIGRATION_ATTEMPTS, numMigrationAttempts + 1);
170 editor.apply();
162 setStage(STAGE_UNINITIALIZED, STAGE_INITIALIZED); 171 setStage(STAGE_UNINITIALIZED, STAGE_INITIALIZED);
163 } 172 }
164 173
165 /** 174 /**
166 * Makes copies of {@link TabState} files in the document mode directory and places them in the 175 * Makes copies of {@link TabState} files in the document mode directory and places them in the
167 * tabbed mode directory. Only non-Incognito tabs are transferred. 176 * tabbed mode directory. Only non-Incognito tabs are transferred.
168 * 177 *
169 * TODO(dfalcantara): Prevent migrating chrome:// pages? 178 * If the user is out of space on their device, this plows through the migra tion pathway.
179 * TODO(dfalcantara): Should we do something about this? A user can have at most 16 tabs in
180 * Android's Recents menu.
170 * 181 *
171 * @param selectedTabId ID of the last viewed non-Incognito tab. 182 * @param selectedTabId ID of the last viewed non-Incognito tab.
172 * @param context Context to use when accessing directorie s.
173 * @param documentDirectoryOverride Overrides the default location for where document mode's
174 * TabState files are expected to be.
175 * @param tabbedDirectoryOverride Overrides the default location for where tabbed mode's
176 * TabState files are expected to be.
177 */ 183 */
178 void copyTabStateFiles(final int selectedTabId, final Context context, 184 final void copyTabStateFiles(final int selectedTabId) {
179 @Nullable final File documentDirectoryOverride,
180 @Nullable final File tabbedDirectoryOverride) {
181 ThreadUtils.assertOnUiThread(); 185 ThreadUtils.assertOnUiThread();
182 if (!setStage(STAGE_INITIALIZED, STAGE_COPY_TAB_STATES_STARTED)) return; 186 if (!setStage(STAGE_INITIALIZED, STAGE_COPY_TAB_STATES_STARTED)) return;
183 187
184 new AsyncTask<Void, Void, Void>() { 188 new AsyncTask<Void, Void, Void>() {
185 private DocumentTabModelImpl mNormalTabModel;
186
187 @Override
188 protected void onPreExecute() {
189 if (documentDirectoryOverride == null) {
190 mNormalTabModel = (DocumentTabModelImpl)
191 ChromeApplication.getDocumentTabModelSelector().getM odel(false);
192 }
193 }
194
195 @Override 189 @Override
196 protected Void doInBackground(Void... params) { 190 protected Void doInBackground(Void... params) {
197 File documentDirectory = documentDirectoryOverride == null 191 File documentDirectory = getDocumentDataDirectory();
198 ? mNormalTabModel.getStorageDelegate().getStateDirectory () 192 File tabbedDirectory = getTabbedDataDirectory();
199 : documentDirectoryOverride;
200 File tabbedDirectory = tabbedDirectoryOverride == null
201 ? TabPersistentStore.getStateDirectory(context, TAB_MODE L_INDEX)
202 : tabbedDirectoryOverride;
203 193
204 Log.d(TAG, "Copying TabState files from document to tabbed mode directory."); 194 Log.d(TAG, "Copying TabState files from document to tabbed mode directory.");
205 assert mMigratedTabIds.size() == 0; 195 assert mMigratedTabIds.size() == 0;
206 196
207 File[] allTabStates = documentDirectory.listFiles(); 197 File[] allTabStates = documentDirectory.listFiles();
208 if (allTabStates != null) { 198 if (allTabStates != null) {
209 // If we know what tab the user was last viewing, copy just that TabState file 199 // If we know what tab the user was last viewing, copy just that TabState file
210 // before all the other ones to mitigate storage issues for devices with limited 200 // before all the other ones to mitigate storage issues for devices with limited
211 // available storage. 201 // available storage.
212 if (selectedTabId != Tab.INVALID_TAB_ID) { 202 if (selectedTabId != Tab.INVALID_TAB_ID) {
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
300 * 290 *
301 * 1) {@link TabPersistentStore} uses the URL to allow reusing already open tabs for Home screen 291 * 1) {@link TabPersistentStore} uses the URL to allow reusing already open tabs for Home screen
302 * Intents. If a Tab doesn't match the Intent's URL, a new Tab is create d. This is already 292 * Intents. If a Tab doesn't match the Intent's URL, a new Tab is create d. This is already
303 * the case when a cold start launches into document mode because the dat a is unavailable at 293 * the case when a cold start launches into document mode because the dat a is unavailable at
304 * startup. 294 * startup.
305 * 295 *
306 * 2) {@link TabModelImpl} uses the URL when it fails to load a Tab's persis ted TabState. This 296 * 2) {@link TabModelImpl} uses the URL when it fails to load a Tab's persis ted TabState. This
307 * means that the user loses some navigation history, but it's not a case document mode would 297 * means that the user loses some navigation history, but it's not a case document mode would
308 * have been able to recover from anyway because the TabState stores the URL data. 298 * have been able to recover from anyway because the TabState stores the URL data.
309 * 299 *
310 * @param normalTabModel DocumentTabModel containing info about n on-Incognito tabs. 300 * @param migratedTabIds IDs of Tabs whose TabState files were copied suc cessfully.
311 * @param migratedTabIds IDs of Tabs whose TabState files were co pied successfully.
312 * @param context Context to access Files from.
313 * @param tabbedDirectoryOverride Overrides the default location for where tabbed mode's
314 * TabState files are expected to be.
315 */ 301 */
316 void writeTabModelMetadata(final DocumentTabModel normalTabModel, 302 final void writeTabModelMetadata(final Set<Integer> migratedTabIds) {
317 final Set<Integer> migratedTabIds, final Context context,
318 @Nullable final File tabbedDirectoryOverride) {
319 ThreadUtils.assertOnUiThread(); 303 ThreadUtils.assertOnUiThread();
320 if (!setStage(STAGE_COPY_TAB_STATES_DONE, STAGE_WRITE_TABMODEL_METADATA_ STARTED)) return; 304 if (!setStage(STAGE_COPY_TAB_STATES_DONE, STAGE_WRITE_TABMODEL_METADATA_ STARTED)) return;
321 305
322 new AsyncTask<Void, Void, Boolean>() { 306 new AsyncTask<Void, Void, Boolean>() {
323 private byte[] mSerializedMetadata; 307 private byte[] mSerializedMetadata;
324 308
325 @Override 309 @Override
326 protected void onPreExecute() { 310 protected void onPreExecute() {
327 Log.d(TAG, "Beginning to write tabbed mode metadata files."); 311 Log.d(TAG, "Beginning to write tabbed mode metadata files.");
328 312
329 // Collect information about all the normal tabs on the UI threa d. 313 // Collect information about all the normal tabs on the UI threa d.
314 final DocumentTabModel normalTabModel = getNormalDocumentTabMode l();
330 TabModelMetadata normalMetadata = new TabModelMetadata(normalTab Model.index()); 315 TabModelMetadata normalMetadata = new TabModelMetadata(normalTab Model.index());
331 for (int i = 0; i < normalTabModel.getCount(); i++) { 316 for (int i = 0; i < normalTabModel.getCount(); i++) {
332 int tabId = normalTabModel.getTabAt(i).getId(); 317 int tabId = normalTabModel.getTabAt(i).getId();
333 normalMetadata.ids.add(tabId); 318 normalMetadata.ids.add(tabId);
334 319
335 if (migratedTabIds.contains(tabId)) { 320 if (migratedTabIds.contains(tabId)) {
336 // Don't save a URL because it's in the TabState. 321 // Don't save a URL because it's in the TabState.
337 normalMetadata.urls.add(""); 322 normalMetadata.urls.add("");
338 } else { 323 } else {
339 // The best that can be done is to fall back to the init ial URL for the Tab. 324 // The best that can be done is to fall back to the init ial URL for the Tab.
(...skipping 11 matching lines...) Expand all
351 normalMetadata, incognitoMetadata, null); 336 normalMetadata, incognitoMetadata, null);
352 } catch (IOException e) { 337 } catch (IOException e) {
353 Log.e(TAG, "Failed to serialize the TabModel.", e); 338 Log.e(TAG, "Failed to serialize the TabModel.", e);
354 mSerializedMetadata = null; 339 mSerializedMetadata = null;
355 } 340 }
356 } 341 }
357 342
358 @Override 343 @Override
359 protected Boolean doInBackground(Void... params) { 344 protected Boolean doInBackground(Void... params) {
360 if (mSerializedMetadata != null) { 345 if (mSerializedMetadata != null) {
361 File tabbedDirectory = tabbedDirectoryOverride == null 346 File tabbedDirectory = getTabbedDataDirectory();
362 ? TabPersistentStore.getStateDirectory(context, TAB_ MODEL_INDEX)
363 : tabbedDirectoryOverride;
364 TabPersistentStore.saveListToFile(tabbedDirectory, mSerializ edMetadata); 347 TabPersistentStore.saveListToFile(tabbedDirectory, mSerializ edMetadata);
365 return true; 348 return true;
366 } else { 349 } else {
367 return false; 350 return false;
368 } 351 }
369 } 352 }
370 353
371 @Override 354 @Override
372 protected void onPostExecute(Boolean result) { 355 protected void onPostExecute(Boolean result) {
373 // TODO(dfalcantara): What do we do if the metadata file failed to be written out? 356 // TODO(dfalcantara): What do we do if the metadata file failed to be written out?
374 Log.d(TAG, "Finished writing tabbed mode metadata file."); 357 Log.d(TAG, "Finished writing tabbed mode metadata file.");
375 setStage(STAGE_WRITE_TABMODEL_METADATA_STARTED, STAGE_WRITE_TABM ODEL_METADATA_DONE); 358 setStage(STAGE_WRITE_TABMODEL_METADATA_STARTED, STAGE_WRITE_TABM ODEL_METADATA_DONE);
376 } 359 }
377 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); 360 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
378 } 361 }
379 362
380 /** 363 /** Moves the user to tabbed mode by opting them out and removing all the ta sks. */
381 * Moves the user to tabbed mode by opting them out. 364 final void switchToTabbedMode() {
382 * @param context Context to grab SharedPreferences from.
383 */
384 void changePreferences(Context context) {
385 ThreadUtils.assertOnUiThread(); 365 ThreadUtils.assertOnUiThread();
386 if (!setStage(STAGE_WRITE_TABMODEL_METADATA_DONE, STAGE_CHANGE_SETTINGS_ STARTED)) return; 366 if (!setStage(STAGE_WRITE_TABMODEL_METADATA_DONE, STAGE_CHANGE_SETTINGS_ STARTED)) return;
387 367
388 // Record that the user has opted-out of document mode now that their da ta has been 368 // Record that the user has opted-out of document mode now that their da ta has been
389 // safely copied to the other directory. 369 // safely copied to the other directory.
390 Log.d(TAG, "Setting tabbed mode preference."); 370 Log.d(TAG, "Setting tabbed mode preference.");
391 DocumentModeManager.getInstance(context).setOptedOutState( 371 DocumentModeManager.getInstance(getContext()).setOptedOutState(
392 DocumentModeManager.OPTED_OUT_OF_DOCUMENT_MODE); 372 DocumentModeManager.OPTED_OUT_OF_DOCUMENT_MODE);
393 373
374 // Remove all the {@link DocumentActivity} tasks from Android's Recents list. Users
375 // viewing Recents during migration will continue to see their tabs unti l they exit.
376 // Reselecting a migrated tab will kick the user to the launcher without crashing.
377 // TODO(dfalcantara): Confirm that the different Android flavors work th e same way.
378 createActivityDelegate().finishAllDocumentActivities();
379
380 // Dismiss the "Close all incognito tabs" notification.
381 IncognitoNotificationManager.dismissIncognitoNotification();
382
394 setStage(STAGE_CHANGE_SETTINGS_STARTED, STAGE_CHANGE_SETTINGS_DONE); 383 setStage(STAGE_CHANGE_SETTINGS_STARTED, STAGE_CHANGE_SETTINGS_DONE);
395 } 384 }
396 385
397 /** TODO(dfalcantara): Add a unit test for this function. */ 386 /** Deletes all remnants of the document mode directory and preferences. */
398 private void deleteDocumentModeData(final Context context) { 387 final void deleteDocumentModeData() {
399 ThreadUtils.assertOnUiThread(); 388 ThreadUtils.assertOnUiThread();
400 if (!setStage(STAGE_CHANGE_SETTINGS_DONE, STAGE_DELETION_STARTED)) retur n; 389 if (!setStage(STAGE_CHANGE_SETTINGS_DONE, STAGE_DELETION_STARTED)) retur n;
401 390
402 new AsyncTask<Void, Void, Void>() { 391 new AsyncTask<Void, Void, Void>() {
403 @Override 392 @Override
404 protected Void doInBackground(Void... params) { 393 protected Void doInBackground(Void... params) {
405 Log.d(TAG, "Starting to delete document mode data."); 394 Log.d(TAG, "Starting to delete document mode data.");
406 395
407 // Remove all the {@link DocumentActivity} tasks from Android's Recents list. Users
408 // viewing Recents during migration will continue to see their t abs until they exit.
409 // Reselecting a migrated tab will kick the user to the launcher without crashing.
410 // TODO(dfalcantara): Confirm that the different Android flavors work the same way.
411 ActivityDelegate delegate = new ActivityDelegateImpl(
412 DocumentActivity.class, IncognitoDocumentActivity.class) ;
413 delegate.finishAllDocumentActivities();
414
415 // Delete the old tab state directory. 396 // Delete the old tab state directory.
416 StorageDelegate migrationStorageDelegate = new StorageDelegate() ; 397 FileUtils.recursivelyDeleteFile(getDocumentDataDirectory());
417 FileUtils.recursivelyDeleteFile(migrationStorageDelegate.getStat eDirectory());
418 398
419 // Clean up the {@link DocumentTabModel} shared preferences. 399 // Clean up the {@link DocumentTabModel} shared preferences.
420 SharedPreferences prefs = context.getSharedPreferences( 400 SharedPreferences prefs = getContext().getSharedPreferences(
421 DocumentTabModelImpl.PREF_PACKAGE, Context.MODE_PRIVATE) ; 401 DocumentTabModelImpl.PREF_PACKAGE, Context.MODE_PRIVATE) ;
422 SharedPreferences.Editor editor = prefs.edit(); 402 SharedPreferences.Editor editor = prefs.edit();
423 editor.clear(); 403 editor.clear();
424 editor.apply(); 404 editor.apply();
425 return null; 405 return null;
426 } 406 }
427 407
428 @Override 408 @Override
429 protected void onPostExecute(Void result) { 409 protected void onPostExecute(Void result) {
430 Log.d(TAG, "Finished deleting document mode data."); 410 Log.d(TAG, "Finished deleting document mode data.");
431 setStage(STAGE_DELETION_STARTED, STAGE_DONE); 411 setStage(STAGE_DELETION_STARTED, STAGE_DONE);
432 } 412 }
433 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); 413 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
434 } 414 }
435 415
436 /** 416 /**
437 * Updates the stage of the migration. 417 * Updates the stage of the migration.
438 * @param expectedStage Stage of the pipeline that is currently expected. 418 * @param expectedStage Stage of the pipeline that is currently expected.
439 * @param newStage Stage of the pipeline that is being activated. 419 * @param newStage Stage of the pipeline that is being activated.
420 * @return Whether or not the stage was updated.
440 */ 421 */
441 private boolean setStage(int expectedStage, int newStage) { 422 private final boolean setStage(int expectedStage, int newStage) {
442 ThreadUtils.assertOnUiThread(); 423 ThreadUtils.assertOnUiThread();
443 424
444 assert mStage == expectedStage; 425 if (mStage != expectedStage) {
426 Log.e(TAG, "Wrong stage encountered: expected " + expectedStage + " but in " + mStage);
427 return false;
428 }
445 mStage = newStage; 429 mStage = newStage;
446 430
447 for (DocumentModeAssassinObserver callback : mObservers) callback.onStag eChange(newStage); 431 for (DocumentModeAssassinObserver callback : mObservers) callback.onStag eChange(newStage);
448 startStage(newStage); 432 startStage(newStage);
449 return true; 433 return true;
450 } 434 }
451 435
452 /** 436 /**
453 * Kicks off tasks for the new state of the pipeline. 437 * Kicks off tasks for the new state of the pipeline.
454 * 438 *
455 * We don't wait for the DocumentTabModel to finish parsing its metadata fil e before proceeding 439 * We don't wait for the DocumentTabModel to finish parsing its metadata fil e before proceeding
456 * with migration because it doesn't have actionable information: 440 * with migration because it doesn't have actionable information:
457 * 441 *
458 * 1) WE DON'T NEED TO RE-POPULATE THE "RECENTLY CLOSED" LIST: 442 * 1) WE DON'T NEED TO RE-POPULATE THE "RECENTLY CLOSED" LIST:
459 * The metadata file contains a list of tabs Chrome knew about before it died, which 443 * The metadata file contains a list of tabs Chrome knew about before it died, which
460 * could differ from the list of tabs in Android Overview. The canonical list of 444 * could differ from the list of tabs in Android Overview. The canonical list of
461 * live tabs, however, has always been the ones displayed by the Android Overview. 445 * live tabs, however, has always been the ones displayed by the Android Overview.
462 * 446 *
463 * 2) RETARGETING MIGRATED TABS FROM THE HOME SCREEN IS A CORNER CASE: 447 * 2) RETARGETING MIGRATED TABS FROM THE HOME SCREEN IS A CORNER CASE:
464 * The only downside here is that Chrome ends up creating a new tab for a home screen 448 * The only downside here is that Chrome ends up creating a new tab for a home screen
465 * shortcut the first time they start Chrome after migration. This was a lready 449 * shortcut the first time they start Chrome after migration. This was a lready
466 * broken for document mode during cold starts, anyway. 450 * broken for document mode during cold starts, anyway.
467 */ 451 */
468 private void startStage(int newStage) { 452 private final void startStage(int newStage) {
469 ThreadUtils.assertOnUiThread(); 453 ThreadUtils.assertOnUiThread();
470 if (!mIsPipelineActive) return; 454 if (!mIsPipelineActive) return;
471 455
472 Context context = ApplicationStatus.getApplicationContext();
473 if (newStage == STAGE_INITIALIZED) { 456 if (newStage == STAGE_INITIALIZED) {
474 Log.d(TAG, "Migrating user into tabbed mode."); 457 Log.d(TAG, "Migrating user into tabbed mode.");
475 int selectedTabId = DocumentUtils.getLastShownTabIdFromPrefs(context , false); 458 int selectedTabId = DocumentUtils.getLastShownTabIdFromPrefs(getCont ext(), false);
476 copyTabStateFiles(selectedTabId, context, null, null); 459 copyTabStateFiles(selectedTabId);
477 } else if (newStage == STAGE_COPY_TAB_STATES_DONE) { 460 } else if (newStage == STAGE_COPY_TAB_STATES_DONE) {
478 Log.d(TAG, "Writing tabbed mode metadata file."); 461 Log.d(TAG, "Writing tabbed mode metadata file.");
479 DocumentTabModelSelector selector = ChromeApplication.getDocumentTab ModelSelector(); 462 writeTabModelMetadata(mMigratedTabIds);
480 DocumentTabModelImpl normalTabModel =
481 (DocumentTabModelImpl) selector.getModel(false);
482 writeTabModelMetadata(normalTabModel, mMigratedTabIds, context, null );
483 } else if (newStage == STAGE_WRITE_TABMODEL_METADATA_DONE) { 463 } else if (newStage == STAGE_WRITE_TABMODEL_METADATA_DONE) {
484 Log.d(TAG, "Changing user preference."); 464 Log.d(TAG, "Changing user preference.");
485 changePreferences(context); 465 switchToTabbedMode();
486 } else if (newStage == STAGE_CHANGE_SETTINGS_DONE) { 466 } else if (newStage == STAGE_CHANGE_SETTINGS_DONE) {
487 Log.d(TAG, "Cleaning up document mode data."); 467 Log.d(TAG, "Cleaning up document mode data.");
488 deleteDocumentModeData(context); 468 deleteDocumentModeData();
489 } 469 }
490 } 470 }
491 471
492 472 /** @return the current stage of the pipeline. */
493 /** 473 public final int getStage() {
494 * Returns the current stage of the pipeline.
495 */
496 @VisibleForTesting
497 public int getStage() {
498 ThreadUtils.assertOnUiThread(); 474 ThreadUtils.assertOnUiThread();
499 return mStage; 475 return mStage;
500 } 476 }
501 477
502
503 /** 478 /**
504 * Adds a observer that is alerted as migration progresses. 479 * Adds a observer that is alerted as migration progresses.
505 * 480 *
506 * @param observer Observer to add. 481 * @param observer Observer to add.
507 */ 482 */
508 @VisibleForTesting 483 public final void addObserver(final DocumentModeAssassinObserver observer) {
509 public void addObserver(final DocumentModeAssassinObserver observer) {
510 ThreadUtils.assertOnUiThread(); 484 ThreadUtils.assertOnUiThread();
511 mObservers.addObserver(observer); 485 mObservers.addObserver(observer);
512 } 486 }
513 487
514 /** 488 /**
515 * Removes an Observer. 489 * Removes an Observer.
516 * 490 *
517 * @param observer Observer to remove. 491 * @param observer Observer to remove.
518 */ 492 */
519 @VisibleForTesting 493 public final void removeObserver(final DocumentModeAssassinObserver observer ) {
520 public void removeObserver(final DocumentModeAssassinObserver observer) {
521 ThreadUtils.assertOnUiThread(); 494 ThreadUtils.assertOnUiThread();
522 mObservers.removeObserver(observer); 495 mObservers.removeObserver(observer);
523 } 496 }
524 497
525 /**
526 * Creates a DocumentModeAssassin that starts at the given stage and does no t automatically
527 * move along the pipeline.
528 *
529 * @param stage Stage to start at. See the STAGE_* values above.
530 * @return DocumentModeAssassin that can be used for testing specific stages of the pipeline.
531 */
532 @VisibleForTesting
533 public static DocumentModeAssassin createForTesting(int stage) {
534 return new DocumentModeAssassin(stage, false);
535 }
536
537 private DocumentModeAssassin() { 498 private DocumentModeAssassin() {
538 this(isMigrationNecessary() ? STAGE_UNINITIALIZED : STAGE_DONE, true); 499 mStage = isMigrationNecessary() ? STAGE_UNINITIALIZED : STAGE_DONE;
500 mIsPipelineActive = true;
539 } 501 }
540 502
541 private DocumentModeAssassin(int stage, boolean isPipelineActive) { 503 private DocumentModeAssassin(int stage, boolean isPipelineActive) {
542 mStage = stage; 504 mStage = stage;
543 mIsPipelineActive = isPipelineActive; 505 mIsPipelineActive = isPipelineActive;
544 } 506 }
507
508 /** DocumentModeAssassin that can have its methods be overridden for testing . */
509 static class DocumentModeAssassinForTesting extends DocumentModeAssassin {
510 /**
511 * Creates a DocumentModeAssassin that starts at the given stage.
512 *
513 * @param stage Stage to start at. See the STAGE_* values a bove.
514 * @param isPipelineActive Whether the pipeline should continue after a stage finishes.
515 */
516 @VisibleForTesting
517 DocumentModeAssassinForTesting(int stage, boolean isPipelineActive) {
518 super(stage, isPipelineActive);
519 }
520 }
521
522 /** @return Whether or not a migration to tabbed mode from document mode is necessary. */
523 public boolean isMigrationNecessary() {
524 return CommandLine.getInstance().hasSwitch(ChromeSwitches.ENABLE_FORCED_ MIGRATION)
525 && FeatureUtilities.isDocumentMode(getContext());
526 }
527
528 /** @return Context to use when grabbing SharedPreferences, Files, and other resources. */
529 protected Context getContext() {
530 return ApplicationStatus.getApplicationContext();
531 }
532
533 /** @return Interfaces with the Android ActivityManager. */
534 protected ActivityDelegate createActivityDelegate() {
535 return new ActivityDelegateImpl(DocumentActivity.class, IncognitoDocumen tActivity.class);
536 }
537
538 /** @return The {@link DocumentTabModelImpl} for regular tabs. */
539 protected DocumentTabModel getNormalDocumentTabModel() {
540 return ChromeApplication.getDocumentTabModelSelector().getModel(false);
541 }
542
543 /** @return Where document mode data is stored for the normal {@link Documen tTabModel}. */
544 protected File getDocumentDataDirectory() {
545 return new StorageDelegate().getStateDirectory();
546 }
547
548 /** @return Where tabbed mode data is stored for the main {@link TabModelImp l}. */
549 protected File getTabbedDataDirectory() {
550 return TabPersistentStore.getStateDirectory(getContext(), TAB_MODEL_INDE X);
551 }
545 } 552 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698