• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.browser;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.SharedPreferences;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.Message;
25 import android.os.Parcel;
26 import android.util.Log;
27 
28 import java.io.ByteArrayOutputStream;
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.FileNotFoundException;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 
35 public class CrashRecoveryHandler {
36 
37     private static final boolean LOGV_ENABLED = Browser.LOGV_ENABLED;
38     private static final String LOGTAG = "BrowserCrashRecovery";
39     private static final String STATE_FILE = "browser_state.parcel";
40     private static final int BUFFER_SIZE = 4096;
41     private static final long BACKUP_DELAY = 500; // 500ms between writes
42     /* This is the duration for which we will prompt to restore
43      * instead of automatically restoring. The first time the browser crashes,
44      * we will automatically restore. If we then crash again within XX minutes,
45      * we will prompt instead of automatically restoring.
46      */
47     private static final long PROMPT_INTERVAL = 5 * 60 * 1000; // 5 minutes
48 
49     private static final int MSG_WRITE_STATE = 1;
50     private static final int MSG_CLEAR_STATE = 2;
51     private static final int MSG_PRELOAD_STATE = 3;
52 
53     private static CrashRecoveryHandler sInstance;
54 
55     private Controller mController;
56     private Context mContext;
57     private Handler mForegroundHandler;
58     private Handler mBackgroundHandler;
59     private boolean mIsPreloading = false;
60     private boolean mDidPreload = false;
61     private Bundle mRecoveryState = null;
62 
initialize(Controller controller)63     public static CrashRecoveryHandler initialize(Controller controller) {
64         if (sInstance == null) {
65             sInstance = new CrashRecoveryHandler(controller);
66         } else {
67             sInstance.mController = controller;
68         }
69         return sInstance;
70     }
71 
getInstance()72     public static CrashRecoveryHandler getInstance() {
73         return sInstance;
74     }
75 
CrashRecoveryHandler(Controller controller)76     private CrashRecoveryHandler(Controller controller) {
77         mController = controller;
78         mContext = mController.getActivity().getApplicationContext();
79         mForegroundHandler = new Handler();
80         mBackgroundHandler = new Handler(BackgroundHandler.getLooper()) {
81 
82             @Override
83             public void handleMessage(Message msg) {
84                 switch (msg.what) {
85                 case MSG_WRITE_STATE:
86                     Bundle saveState = (Bundle) msg.obj;
87                     writeState(saveState);
88                     break;
89                 case MSG_CLEAR_STATE:
90                     if (LOGV_ENABLED) {
91                         Log.v(LOGTAG, "Clearing crash recovery state");
92                     }
93                     File state = new File(mContext.getCacheDir(), STATE_FILE);
94                     if (state.exists()) {
95                         state.delete();
96                     }
97                     break;
98                 case MSG_PRELOAD_STATE:
99                     mRecoveryState = loadCrashState();
100                     synchronized (CrashRecoveryHandler.this) {
101                         mIsPreloading = false;
102                         mDidPreload = true;
103                         CrashRecoveryHandler.this.notifyAll();
104                     }
105                     break;
106                 }
107             }
108         };
109     }
110 
backupState()111     public void backupState() {
112         mForegroundHandler.postDelayed(mCreateState, BACKUP_DELAY);
113     }
114 
115     private Runnable mCreateState = new Runnable() {
116 
117         @Override
118         public void run() {
119             try {
120                 final Bundle state = mController.createSaveState();
121                 Message.obtain(mBackgroundHandler, MSG_WRITE_STATE, state)
122                         .sendToTarget();
123                 // Remove any queued up saves
124                 mForegroundHandler.removeCallbacks(mCreateState);
125             } catch (Throwable t) {
126                 Log.w(LOGTAG, "Failed to save state", t);
127                 return;
128             }
129         }
130 
131     };
132 
clearState()133     public void clearState() {
134         mBackgroundHandler.sendEmptyMessage(MSG_CLEAR_STATE);
135         updateLastRecovered(0);
136     }
137 
shouldRestore()138     private boolean shouldRestore() {
139         BrowserSettings browserSettings = BrowserSettings.getInstance();
140         long lastRecovered = browserSettings.getLastRecovered();
141         long timeSinceLastRecover = System.currentTimeMillis() - lastRecovered;
142         return (timeSinceLastRecover > PROMPT_INTERVAL)
143                 || browserSettings.wasLastRunPaused();
144     }
145 
updateLastRecovered(long time)146     private void updateLastRecovered(long time) {
147         BrowserSettings browserSettings = BrowserSettings.getInstance();
148         browserSettings.setLastRecovered(time);
149     }
150 
loadCrashState()151     synchronized private Bundle loadCrashState() {
152         if (!shouldRestore()) {
153             return null;
154         }
155         BrowserSettings browserSettings = BrowserSettings.getInstance();
156         browserSettings.setLastRunPaused(false);
157         Bundle state = null;
158         Parcel parcel = Parcel.obtain();
159         FileInputStream fin = null;
160         try {
161             File stateFile = new File(mContext.getCacheDir(), STATE_FILE);
162             fin = new FileInputStream(stateFile);
163             ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
164             byte[] buffer = new byte[BUFFER_SIZE];
165             int read;
166             while ((read = fin.read(buffer)) > 0) {
167                 dataStream.write(buffer, 0, read);
168             }
169             byte[] data = dataStream.toByteArray();
170             parcel.unmarshall(data, 0, data.length);
171             parcel.setDataPosition(0);
172             state = parcel.readBundle();
173             if (state != null && !state.isEmpty()) {
174                 return state;
175             }
176         } catch (FileNotFoundException e) {
177             // No state to recover
178         } catch (Throwable e) {
179             Log.w(LOGTAG, "Failed to recover state!", e);
180         } finally {
181             parcel.recycle();
182             if (fin != null) {
183                 try {
184                     fin.close();
185                 } catch (IOException e) { }
186             }
187         }
188         return null;
189     }
190 
startRecovery(Intent intent)191     public void startRecovery(Intent intent) {
192         synchronized (CrashRecoveryHandler.this) {
193             while (mIsPreloading) {
194                 try {
195                     CrashRecoveryHandler.this.wait();
196                 } catch (InterruptedException e) {}
197             }
198         }
199         if (!mDidPreload) {
200             mRecoveryState = loadCrashState();
201         }
202         updateLastRecovered(mRecoveryState != null
203                 ? System.currentTimeMillis() : 0);
204         mController.doStart(mRecoveryState, intent);
205         mRecoveryState = null;
206     }
207 
preloadCrashState()208     public void preloadCrashState() {
209         synchronized (CrashRecoveryHandler.this) {
210             if (mIsPreloading) {
211                 return;
212             }
213             mIsPreloading = true;
214         }
215         mBackgroundHandler.sendEmptyMessage(MSG_PRELOAD_STATE);
216     }
217 
218     /**
219      * Writes the crash recovery state to a file synchronously.
220      * Errors are swallowed, but logged.
221      * @param state The state to write out
222      */
writeState(Bundle state)223     synchronized void writeState(Bundle state) {
224         if (LOGV_ENABLED) {
225             Log.v(LOGTAG, "Saving crash recovery state");
226         }
227         Parcel p = Parcel.obtain();
228         try {
229             state.writeToParcel(p, 0);
230             File stateJournal = new File(mContext.getCacheDir(),
231                     STATE_FILE + ".journal");
232             FileOutputStream fout = new FileOutputStream(stateJournal);
233             fout.write(p.marshall());
234             fout.close();
235             File stateFile = new File(mContext.getCacheDir(),
236                     STATE_FILE);
237             if (!stateJournal.renameTo(stateFile)) {
238                 // Failed to rename, try deleting the existing
239                 // file and try again
240                 stateFile.delete();
241                 stateJournal.renameTo(stateFile);
242             }
243         } catch (Throwable e) {
244             Log.i(LOGTAG, "Failed to save persistent state", e);
245         } finally {
246             p.recycle();
247         }
248     }
249 }