blob: acc2a3021f68aaa3497699f370396ef89fd91402 [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() {
John Reck24f18262011-06-17 14:47:20 -0700106 if (mState.isEmpty()) {
107 clearState(mContext);
108 return;
109 }
John Reck378a4102011-06-09 16:23:01 -0700110 Parcel p = Parcel.obtain();
111 try {
112 mState.writeToParcel(p, 0);
113 FileOutputStream fout = mContext.openFileOutput(STATE_FILE,
114 Context.MODE_PRIVATE);
115 fout.write(p.marshall());
116 fout.close();
117 } catch (Throwable e) {
118 Log.i(LOGTAG, "Failed to save persistent state", e);
119 } finally {
120 p.recycle();
121 }
122 }
123
124 }
125
John Reck24f18262011-06-17 14:47:20 -0700126 private static void clearState(Context context) {
John Reck847b5322011-04-14 17:02:18 -0700127 context.deleteFile(STATE_FILE);
128 }
129
130 public void promptToRecover(final Bundle state, final Intent intent) {
131 new AlertDialog.Builder(mController.getActivity())
132 .setTitle(R.string.recover_title)
133 .setMessage(R.string.recover_prompt)
John Reck24f18262011-06-17 14:47:20 -0700134 .setIcon(R.mipmap.ic_launcher_browser)
John Reck847b5322011-04-14 17:02:18 -0700135 .setPositiveButton(R.string.recover_yes, new OnClickListener() {
136 @Override
137 public void onClick(DialogInterface dialog, int which) {
138 mController.doStart(state, intent);
139 }
140 })
141 .setNegativeButton(R.string.recover_no, new OnClickListener() {
142 @Override
143 public void onClick(DialogInterface dialog, int which) {
John Reck24f18262011-06-17 14:47:20 -0700144 clearState(mController.getActivity());
John Reck847b5322011-04-14 17:02:18 -0700145 mController.doStart(null, intent);
146 }
147 })
148 .show();
149 }
150
151 public void startRecovery(Intent intent) {
152 Parcel parcel = Parcel.obtain();
153 try {
154 Context context = mController.getActivity();
155 FileInputStream fin = context.openFileInput(STATE_FILE);
156 ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
157 byte[] buffer = new byte[BUFFER_SIZE];
158 int read;
159 while ((read = fin.read(buffer)) > 0) {
160 dataStream.write(buffer, 0, read);
161 }
162 byte[] data = dataStream.toByteArray();
163 parcel.unmarshall(data, 0, data.length);
164 parcel.setDataPosition(0);
165 Bundle state = parcel.readBundle();
166 promptToRecover(state, intent);
167 } catch (FileNotFoundException e) {
168 // No state to recover
169 mController.doStart(null, intent);
170 } catch (Exception e) {
171 Log.w(LOGTAG, "Failed to recover state!", e);
172 mController.doStart(null, intent);
173 } finally {
174 parcel.recycle();
175 }
176 }
177}