blob: 514095233e2fce5b4cb8e7bf33380c4f0abbc707 [file] [log] [blame]
John Reck847b5322011-04-14 17:02:18 -07001/*
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
17package com.android.browser;
18
19import android.app.AlertDialog;
20import android.content.Context;
21import android.content.DialogInterface;
John Reck0b3165d2011-06-22 10:44:33 -070022import android.content.DialogInterface.OnCancelListener;
John Reck847b5322011-04-14 17:02:18 -070023import android.content.DialogInterface.OnClickListener;
24import android.content.Intent;
John Reckcfeae6d2011-06-24 15:33:57 -070025import android.content.SharedPreferences;
John Reck847b5322011-04-14 17:02:18 -070026import android.os.Bundle;
John Reck378a4102011-06-09 16:23:01 -070027import android.os.Handler;
28import android.os.HandlerThread;
John Reck847b5322011-04-14 17:02:18 -070029import android.os.Parcel;
John Reck378a4102011-06-09 16:23:01 -070030import android.os.Process;
John Reck847b5322011-04-14 17:02:18 -070031import android.util.Log;
32
33import java.io.ByteArrayOutputStream;
34import java.io.FileInputStream;
35import java.io.FileNotFoundException;
36import java.io.FileOutputStream;
37
38public class CrashRecoveryHandler {
39
40 private static final String LOGTAG = "BrowserCrashRecovery";
41 private static final String STATE_FILE = "browser_state.parcel";
John Reckcfeae6d2011-06-24 15:33:57 -070042 private static final String RECOVERY_PREFERENCES = "browser_recovery_prefs";
43 private static final String KEY_LAST_RECOVERED = "last_recovered";
John Reck847b5322011-04-14 17:02:18 -070044 private static final int BUFFER_SIZE = 4096;
John Reck378a4102011-06-09 16:23:01 -070045 private static final long BACKUP_DELAY = 500; // 500ms between writes
John Reckcfeae6d2011-06-24 15:33:57 -070046 /* This is the duration for which we will prompt to restore
47 * instead of automatically restoring. The first time the browser crashes,
48 * we will automatically restore. If we then crash again within XX minutes,
49 * we will prompt instead of automatically restoring.
50 */
51 private static final long PROMPT_INTERVAL = 30 * 60 * 1000; // 30 minutes
John Reck378a4102011-06-09 16:23:01 -070052
53 private static CrashRecoveryHandler sInstance;
John Reck847b5322011-04-14 17:02:18 -070054
55 private Controller mController;
John Reck378a4102011-06-09 16:23:01 -070056 private Handler mForegroundHandler;
57 private Handler mBackgroundHandler;
John Reck847b5322011-04-14 17:02:18 -070058
John Reck378a4102011-06-09 16:23:01 -070059 public static CrashRecoveryHandler initialize(Controller controller) {
60 if (sInstance == null) {
61 sInstance = new CrashRecoveryHandler(controller);
62 } else {
63 sInstance.mController = controller;
64 }
65 return sInstance;
66 }
67
68 public static CrashRecoveryHandler getInstance() {
69 return sInstance;
70 }
71
72 private CrashRecoveryHandler(Controller controller) {
John Reck847b5322011-04-14 17:02:18 -070073 mController = controller;
John Reck378a4102011-06-09 16:23:01 -070074 mForegroundHandler = new Handler();
75 HandlerThread thread = new HandlerThread(LOGTAG,
76 Process.THREAD_PRIORITY_BACKGROUND);
77 thread.start();
78 mBackgroundHandler = new Handler(thread.getLooper());
John Reck847b5322011-04-14 17:02:18 -070079 }
80
81 public void backupState() {
John Reck378a4102011-06-09 16:23:01 -070082 mForegroundHandler.postDelayed(mCreateState, BACKUP_DELAY);
John Reck847b5322011-04-14 17:02:18 -070083 }
84
John Reck378a4102011-06-09 16:23:01 -070085 private Runnable mCreateState = new Runnable() {
86
87 @Override
88 public void run() {
89 try {
90 final Bundle state = new Bundle();
91 mController.onSaveInstanceState(state, false);
92 Context context = mController.getActivity()
93 .getApplicationContext();
94 mBackgroundHandler.post(new WriteState(context, state));
95 // Remove any queued up saves
96 mForegroundHandler.removeCallbacks(mCreateState);
97 } catch (Throwable t) {
98 Log.w(LOGTAG, "Failed to save state", t);
99 return;
100 }
101 }
102
103 };
104
105 static class WriteState implements Runnable {
106 private Context mContext;
107 private Bundle mState;
108
109 WriteState(Context context, Bundle state) {
110 mContext = context;
111 mState = state;
112 }
113
114 @Override
115 public void run() {
John Reck24f18262011-06-17 14:47:20 -0700116 if (mState.isEmpty()) {
117 clearState(mContext);
118 return;
119 }
John Reck378a4102011-06-09 16:23:01 -0700120 Parcel p = Parcel.obtain();
121 try {
122 mState.writeToParcel(p, 0);
123 FileOutputStream fout = mContext.openFileOutput(STATE_FILE,
124 Context.MODE_PRIVATE);
125 fout.write(p.marshall());
126 fout.close();
127 } catch (Throwable e) {
128 Log.i(LOGTAG, "Failed to save persistent state", e);
129 } finally {
130 p.recycle();
131 }
132 }
133
134 }
135
John Reck24f18262011-06-17 14:47:20 -0700136 private static void clearState(Context context) {
John Reck847b5322011-04-14 17:02:18 -0700137 context.deleteFile(STATE_FILE);
138 }
139
140 public void promptToRecover(final Bundle state, final Intent intent) {
141 new AlertDialog.Builder(mController.getActivity())
142 .setTitle(R.string.recover_title)
143 .setMessage(R.string.recover_prompt)
John Reck24f18262011-06-17 14:47:20 -0700144 .setIcon(R.mipmap.ic_launcher_browser)
John Reck847b5322011-04-14 17:02:18 -0700145 .setPositiveButton(R.string.recover_yes, new OnClickListener() {
146 @Override
147 public void onClick(DialogInterface dialog, int which) {
John Reckcfeae6d2011-06-24 15:33:57 -0700148 updateLastRecovered();
John Reck847b5322011-04-14 17:02:18 -0700149 mController.doStart(state, intent);
150 }
151 })
152 .setNegativeButton(R.string.recover_no, new OnClickListener() {
153 @Override
154 public void onClick(DialogInterface dialog, int which) {
John Reck0b3165d2011-06-22 10:44:33 -0700155 dialog.cancel();
156 }
157 })
158 .setOnCancelListener(new OnCancelListener() {
159 @Override
160 public void onCancel(DialogInterface dialog) {
John Reck24f18262011-06-17 14:47:20 -0700161 clearState(mController.getActivity());
John Reck847b5322011-04-14 17:02:18 -0700162 mController.doStart(null, intent);
163 }
164 })
165 .show();
166 }
167
John Reckcfeae6d2011-06-24 15:33:57 -0700168 private boolean shouldPrompt() {
169 Context context = mController.getActivity();
170 SharedPreferences prefs = context.getSharedPreferences(
171 RECOVERY_PREFERENCES, Context.MODE_PRIVATE);
172 long lastRecovered = prefs.getLong(KEY_LAST_RECOVERED,
173 System.currentTimeMillis());
174 long timeSinceLastRecover = System.currentTimeMillis() - lastRecovered;
175 if (timeSinceLastRecover > PROMPT_INTERVAL) {
176 return false;
177 }
178 return true;
179 }
180
181 private void updateLastRecovered() {
182 Context context = mController.getActivity();
183 SharedPreferences prefs = context.getSharedPreferences(
184 RECOVERY_PREFERENCES, Context.MODE_PRIVATE);
185 prefs.edit()
186 .putLong(KEY_LAST_RECOVERED, System.currentTimeMillis())
187 .commit();
188 }
189
John Reck847b5322011-04-14 17:02:18 -0700190 public void startRecovery(Intent intent) {
John Reckcfeae6d2011-06-24 15:33:57 -0700191 Bundle state = null;
John Reck847b5322011-04-14 17:02:18 -0700192 Parcel parcel = Parcel.obtain();
193 try {
194 Context context = mController.getActivity();
195 FileInputStream fin = context.openFileInput(STATE_FILE);
196 ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
197 byte[] buffer = new byte[BUFFER_SIZE];
198 int read;
199 while ((read = fin.read(buffer)) > 0) {
200 dataStream.write(buffer, 0, read);
201 }
202 byte[] data = dataStream.toByteArray();
203 parcel.unmarshall(data, 0, data.length);
204 parcel.setDataPosition(0);
John Reckcfeae6d2011-06-24 15:33:57 -0700205 state = parcel.readBundle();
206 if (shouldPrompt()) {
207 promptToRecover(state, intent);
208 return;
209 } else {
210 updateLastRecovered();
211 }
John Reck847b5322011-04-14 17:02:18 -0700212 } catch (FileNotFoundException e) {
213 // No state to recover
John Reckcfeae6d2011-06-24 15:33:57 -0700214 state = null;
John Reck847b5322011-04-14 17:02:18 -0700215 } catch (Exception e) {
216 Log.w(LOGTAG, "Failed to recover state!", e);
John Reckcfeae6d2011-06-24 15:33:57 -0700217 state = null;
John Reck847b5322011-04-14 17:02:18 -0700218 } finally {
219 parcel.recycle();
220 }
John Reckcfeae6d2011-06-24 15:33:57 -0700221 mController.doStart(state, intent);
John Reck847b5322011-04-14 17:02:18 -0700222 }
223}