• 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 String RECOVERY_PREFERENCES = "browser_recovery_prefs";
41     private static final String KEY_LAST_RECOVERED = "last_recovered";
42     private static final int BUFFER_SIZE = 4096;
43     private static final long BACKUP_DELAY = 500; // 500ms between writes
44     /* This is the duration for which we will prompt to restore
45      * instead of automatically restoring. The first time the browser crashes,
46      * we will automatically restore. If we then crash again within XX minutes,
47      * we will prompt instead of automatically restoring.
48      */
49     private static final long PROMPT_INTERVAL = 5 * 60 * 1000; // 5 minutes
50 
51     private static final int MSG_WRITE_STATE = 1;
52     private static final int MSG_CLEAR_STATE = 2;
53     private static final int MSG_PRELOAD_STATE = 3;
54 
55     private static CrashRecoveryHandler sInstance;
56 
57     private Controller mController;
58     private Context mContext;
59     private Handler mForegroundHandler;
60     private Handler mBackgroundHandler;
61     private boolean mIsPreloading = false;
62     private boolean mDidPreload = false;
63     private Bundle mRecoveryState = null;
64 
initialize(Controller controller)65     public static CrashRecoveryHandler initialize(Controller controller) {
66         if (sInstance == null) {
67             sInstance = new CrashRecoveryHandler(controller);
68         } else {
69             sInstance.mController = controller;
70         }
71         return sInstance;
72     }
73 
getInstance()74     public static CrashRecoveryHandler getInstance() {
75         return sInstance;
76     }
77 
CrashRecoveryHandler(Controller controller)78     private CrashRecoveryHandler(Controller controller) {
79         mController = controller;
80         mContext = mController.getActivity().getApplicationContext();
81         mForegroundHandler = new Handler();
82         mBackgroundHandler = new Handler(BackgroundHandler.getLooper()) {
83 
84             @Override
85             public void handleMessage(Message msg) {
86                 switch (msg.what) {
87                 case MSG_WRITE_STATE:
88                     if (LOGV_ENABLED) {
89                         Log.v(LOGTAG, "Saving crash recovery state");
90                     }
91                     Parcel p = Parcel.obtain();
92                     try {
93                         Bundle state = (Bundle) msg.obj;
94                         state.writeToParcel(p, 0);
95                         File stateJournal = new File(mContext.getCacheDir(),
96                                 STATE_FILE + ".journal");
97                         FileOutputStream fout = new FileOutputStream(stateJournal);
98                         fout.write(p.marshall());
99                         fout.close();
100                         File stateFile = new File(mContext.getCacheDir(),
101                                 STATE_FILE);
102                         if (!stateJournal.renameTo(stateFile)) {
103                             // Failed to rename, try deleting the existing
104                             // file and try again
105                             stateFile.delete();
106                             stateJournal.renameTo(stateFile);
107                         }
108                     } catch (Throwable e) {
109                         Log.i(LOGTAG, "Failed to save persistent state", e);
110                     } finally {
111                         p.recycle();
112                     }
113                     break;
114                 case MSG_CLEAR_STATE:
115                     if (LOGV_ENABLED) {
116                         Log.v(LOGTAG, "Clearing crash recovery state");
117                     }
118                     File state = new File(mContext.getCacheDir(), STATE_FILE);
119                     if (state.exists()) {
120                         state.delete();
121                     }
122                     break;
123                 case MSG_PRELOAD_STATE:
124                     mRecoveryState = loadCrashState();
125                     synchronized (CrashRecoveryHandler.this) {
126                         mIsPreloading = false;
127                         mDidPreload = true;
128                         CrashRecoveryHandler.this.notifyAll();
129                     }
130                     break;
131                 }
132             }
133         };
134     }
135 
backupState()136     public void backupState() {
137         mForegroundHandler.postDelayed(mCreateState, BACKUP_DELAY);
138     }
139 
140     private Runnable mCreateState = new Runnable() {
141 
142         @Override
143         public void run() {
144             try {
145                 final Bundle state = new Bundle();
146                 mController.onSaveInstanceState(state);
147                 Message.obtain(mBackgroundHandler, MSG_WRITE_STATE, state)
148                         .sendToTarget();
149                 // Remove any queued up saves
150                 mForegroundHandler.removeCallbacks(mCreateState);
151             } catch (Throwable t) {
152                 Log.w(LOGTAG, "Failed to save state", t);
153                 return;
154             }
155         }
156 
157     };
158 
clearState()159     public void clearState() {
160         mBackgroundHandler.sendEmptyMessage(MSG_CLEAR_STATE);
161         updateLastRecovered(0);
162     }
163 
shouldRestore()164     private boolean shouldRestore() {
165         SharedPreferences prefs = mContext.getSharedPreferences(
166                 RECOVERY_PREFERENCES, Context.MODE_PRIVATE);
167         long lastRecovered = prefs.getLong(KEY_LAST_RECOVERED, 0);
168         long timeSinceLastRecover = System.currentTimeMillis() - lastRecovered;
169         if (timeSinceLastRecover > PROMPT_INTERVAL) {
170             return true;
171         }
172         return false;
173     }
174 
updateLastRecovered(long time)175     private void updateLastRecovered(long time) {
176         SharedPreferences prefs = mContext.getSharedPreferences(
177                 RECOVERY_PREFERENCES, Context.MODE_PRIVATE);
178         prefs.edit()
179             .putLong(KEY_LAST_RECOVERED, time)
180             .apply();
181     }
182 
loadCrashState()183     private Bundle loadCrashState() {
184         if (!shouldRestore()) {
185             return null;
186         }
187         Bundle state = null;
188         Parcel parcel = Parcel.obtain();
189         FileInputStream fin = null;
190         try {
191             File stateFile = new File(mContext.getCacheDir(), STATE_FILE);
192             fin = new FileInputStream(stateFile);
193             ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
194             byte[] buffer = new byte[BUFFER_SIZE];
195             int read;
196             while ((read = fin.read(buffer)) > 0) {
197                 dataStream.write(buffer, 0, read);
198             }
199             byte[] data = dataStream.toByteArray();
200             parcel.unmarshall(data, 0, data.length);
201             parcel.setDataPosition(0);
202             state = parcel.readBundle();
203             if (state != null && !state.isEmpty()) {
204                 return state;
205             }
206         } catch (FileNotFoundException e) {
207             // No state to recover
208         } catch (Throwable e) {
209             Log.w(LOGTAG, "Failed to recover state!", e);
210         } finally {
211             parcel.recycle();
212             if (fin != null) {
213                 try {
214                     fin.close();
215                 } catch (IOException e) { }
216             }
217         }
218         return null;
219     }
220 
startRecovery(Intent intent)221     public void startRecovery(Intent intent) {
222         synchronized (CrashRecoveryHandler.this) {
223             while (mIsPreloading) {
224                 try {
225                     CrashRecoveryHandler.this.wait();
226                 } catch (InterruptedException e) {}
227             }
228         }
229         if (!mDidPreload) {
230             mRecoveryState = loadCrashState();
231         }
232         updateLastRecovered(mRecoveryState != null
233                 ? System.currentTimeMillis() : 0);
234         mController.doStart(mRecoveryState, intent, true);
235         mRecoveryState = null;
236     }
237 
preloadCrashState()238     public void preloadCrashState() {
239         synchronized (CrashRecoveryHandler.this) {
240             if (mIsPreloading) {
241                 return;
242             }
243             mIsPreloading = true;
244         }
245         mBackgroundHandler.sendEmptyMessage(MSG_PRELOAD_STATE);
246     }
247 
248 }
249