blob: 7ee9ceeb169887d0b0bfde7ec796c6b612cc33f4 [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;
22import android.content.DialogInterface.OnClickListener;
23import android.content.Intent;
24import android.os.Bundle;
John Reck378a4102011-06-09 16:23:01 -070025import android.os.Handler;
26import android.os.HandlerThread;
John Reck847b5322011-04-14 17:02:18 -070027import android.os.Parcel;
John Reck378a4102011-06-09 16:23:01 -070028import android.os.Process;
John Reck847b5322011-04-14 17:02:18 -070029import android.util.Log;
30
31import java.io.ByteArrayOutputStream;
32import java.io.FileInputStream;
33import java.io.FileNotFoundException;
34import java.io.FileOutputStream;
35
36public class CrashRecoveryHandler {
37
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;
John Reck378a4102011-06-09 16:23:01 -070041 private static final long BACKUP_DELAY = 500; // 500ms between writes
42
43 private static CrashRecoveryHandler sInstance;
John Reck847b5322011-04-14 17:02:18 -070044
45 private Controller mController;
John Reck378a4102011-06-09 16:23:01 -070046 private Handler mForegroundHandler;
47 private Handler mBackgroundHandler;
John Reck847b5322011-04-14 17:02:18 -070048
John Reck378a4102011-06-09 16:23:01 -070049 public static CrashRecoveryHandler initialize(Controller controller) {
50 if (sInstance == null) {
51 sInstance = new CrashRecoveryHandler(controller);
52 } else {
53 sInstance.mController = controller;
54 }
55 return sInstance;
56 }
57
58 public static CrashRecoveryHandler getInstance() {
59 return sInstance;
60 }
61
62 private CrashRecoveryHandler(Controller controller) {
John Reck847b5322011-04-14 17:02:18 -070063 mController = controller;
John Reck378a4102011-06-09 16:23:01 -070064 mForegroundHandler = new Handler();
65 HandlerThread thread = new HandlerThread(LOGTAG,
66 Process.THREAD_PRIORITY_BACKGROUND);
67 thread.start();
68 mBackgroundHandler = new Handler(thread.getLooper());
John Reck847b5322011-04-14 17:02:18 -070069 }
70
71 public void backupState() {
John Reck378a4102011-06-09 16:23:01 -070072 mForegroundHandler.postDelayed(mCreateState, BACKUP_DELAY);
John Reck847b5322011-04-14 17:02:18 -070073 }
74
John Reck378a4102011-06-09 16:23:01 -070075 private Runnable mCreateState = new Runnable() {
76
77 @Override
78 public void run() {
79 try {
80 final Bundle state = new Bundle();
81 mController.onSaveInstanceState(state, false);
82 Context context = mController.getActivity()
83 .getApplicationContext();
84 mBackgroundHandler.post(new WriteState(context, state));
85 // Remove any queued up saves
86 mForegroundHandler.removeCallbacks(mCreateState);
87 } catch (Throwable t) {
88 Log.w(LOGTAG, "Failed to save state", t);
89 return;
90 }
91 }
92
93 };
94
95 static class WriteState implements Runnable {
96 private Context mContext;
97 private Bundle mState;
98
99 WriteState(Context context, Bundle state) {
100 mContext = context;
101 mState = state;
102 }
103
104 @Override
105 public void run() {
106 Parcel p = Parcel.obtain();
107 try {
108 mState.writeToParcel(p, 0);
109 FileOutputStream fout = mContext.openFileOutput(STATE_FILE,
110 Context.MODE_PRIVATE);
111 fout.write(p.marshall());
112 fout.close();
113 } catch (Throwable e) {
114 Log.i(LOGTAG, "Failed to save persistent state", e);
115 } finally {
116 p.recycle();
117 }
118 }
119
120 }
121
122 private void clearState() {
John Reck847b5322011-04-14 17:02:18 -0700123 Context context = mController.getActivity();
124 context.deleteFile(STATE_FILE);
125 }
126
127 public void promptToRecover(final Bundle state, final Intent intent) {
128 new AlertDialog.Builder(mController.getActivity())
129 .setTitle(R.string.recover_title)
130 .setMessage(R.string.recover_prompt)
131 .setPositiveButton(R.string.recover_yes, new OnClickListener() {
132 @Override
133 public void onClick(DialogInterface dialog, int which) {
134 mController.doStart(state, intent);
135 }
136 })
137 .setNegativeButton(R.string.recover_no, new OnClickListener() {
138 @Override
139 public void onClick(DialogInterface dialog, int which) {
140 clearState();
141 mController.doStart(null, intent);
142 }
143 })
144 .show();
145 }
146
147 public void startRecovery(Intent intent) {
148 Parcel parcel = Parcel.obtain();
149 try {
150 Context context = mController.getActivity();
151 FileInputStream fin = context.openFileInput(STATE_FILE);
152 ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
153 byte[] buffer = new byte[BUFFER_SIZE];
154 int read;
155 while ((read = fin.read(buffer)) > 0) {
156 dataStream.write(buffer, 0, read);
157 }
158 byte[] data = dataStream.toByteArray();
159 parcel.unmarshall(data, 0, data.length);
160 parcel.setDataPosition(0);
161 Bundle state = parcel.readBundle();
162 promptToRecover(state, intent);
163 } catch (FileNotFoundException e) {
164 // No state to recover
165 mController.doStart(null, intent);
166 } catch (Exception e) {
167 Log.w(LOGTAG, "Failed to recover state!", e);
168 mController.doStart(null, intent);
169 } finally {
170 parcel.recycle();
171 }
172 }
173}