blob: 6d8d921562de2e0b987c105663708e8bad57b7d1 [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;
25import android.os.Bundle;
John Reck378a4102011-06-09 16:23:01 -070026import android.os.Handler;
27import android.os.HandlerThread;
John Reck847b5322011-04-14 17:02:18 -070028import android.os.Parcel;
John Reck378a4102011-06-09 16:23:01 -070029import android.os.Process;
John Reck847b5322011-04-14 17:02:18 -070030import android.util.Log;
31
32import java.io.ByteArrayOutputStream;
33import java.io.FileInputStream;
34import java.io.FileNotFoundException;
35import java.io.FileOutputStream;
36
37public class CrashRecoveryHandler {
38
39 private static final String LOGTAG = "BrowserCrashRecovery";
40 private static final String STATE_FILE = "browser_state.parcel";
41 private static final int BUFFER_SIZE = 4096;
John Reck378a4102011-06-09 16:23:01 -070042 private static final long BACKUP_DELAY = 500; // 500ms between writes
43
44 private static CrashRecoveryHandler sInstance;
John Reck847b5322011-04-14 17:02:18 -070045
46 private Controller mController;
John Reck378a4102011-06-09 16:23:01 -070047 private Handler mForegroundHandler;
48 private Handler mBackgroundHandler;
John Reck847b5322011-04-14 17:02:18 -070049
John Reck378a4102011-06-09 16:23:01 -070050 public static CrashRecoveryHandler initialize(Controller controller) {
51 if (sInstance == null) {
52 sInstance = new CrashRecoveryHandler(controller);
53 } else {
54 sInstance.mController = controller;
55 }
56 return sInstance;
57 }
58
59 public static CrashRecoveryHandler getInstance() {
60 return sInstance;
61 }
62
63 private CrashRecoveryHandler(Controller controller) {
John Reck847b5322011-04-14 17:02:18 -070064 mController = controller;
John Reck378a4102011-06-09 16:23:01 -070065 mForegroundHandler = new Handler();
66 HandlerThread thread = new HandlerThread(LOGTAG,
67 Process.THREAD_PRIORITY_BACKGROUND);
68 thread.start();
69 mBackgroundHandler = new Handler(thread.getLooper());
John Reck847b5322011-04-14 17:02:18 -070070 }
71
72 public void backupState() {
John Reck378a4102011-06-09 16:23:01 -070073 mForegroundHandler.postDelayed(mCreateState, BACKUP_DELAY);
John Reck847b5322011-04-14 17:02:18 -070074 }
75
John Reck378a4102011-06-09 16:23:01 -070076 private Runnable mCreateState = new Runnable() {
77
78 @Override
79 public void run() {
80 try {
81 final Bundle state = new Bundle();
82 mController.onSaveInstanceState(state, false);
83 Context context = mController.getActivity()
84 .getApplicationContext();
85 mBackgroundHandler.post(new WriteState(context, state));
86 // Remove any queued up saves
87 mForegroundHandler.removeCallbacks(mCreateState);
88 } catch (Throwable t) {
89 Log.w(LOGTAG, "Failed to save state", t);
90 return;
91 }
92 }
93
94 };
95
96 static class WriteState implements Runnable {
97 private Context mContext;
98 private Bundle mState;
99
100 WriteState(Context context, Bundle state) {
101 mContext = context;
102 mState = state;
103 }
104
105 @Override
106 public void run() {
John Reck24f18262011-06-17 14:47:20 -0700107 if (mState.isEmpty()) {
108 clearState(mContext);
109 return;
110 }
John Reck378a4102011-06-09 16:23:01 -0700111 Parcel p = Parcel.obtain();
112 try {
113 mState.writeToParcel(p, 0);
114 FileOutputStream fout = mContext.openFileOutput(STATE_FILE,
115 Context.MODE_PRIVATE);
116 fout.write(p.marshall());
117 fout.close();
118 } catch (Throwable e) {
119 Log.i(LOGTAG, "Failed to save persistent state", e);
120 } finally {
121 p.recycle();
122 }
123 }
124
125 }
126
John Reck24f18262011-06-17 14:47:20 -0700127 private static void clearState(Context context) {
John Reck847b5322011-04-14 17:02:18 -0700128 context.deleteFile(STATE_FILE);
129 }
130
131 public void promptToRecover(final Bundle state, final Intent intent) {
132 new AlertDialog.Builder(mController.getActivity())
133 .setTitle(R.string.recover_title)
134 .setMessage(R.string.recover_prompt)
John Reck24f18262011-06-17 14:47:20 -0700135 .setIcon(R.mipmap.ic_launcher_browser)
John Reck847b5322011-04-14 17:02:18 -0700136 .setPositiveButton(R.string.recover_yes, new OnClickListener() {
137 @Override
138 public void onClick(DialogInterface dialog, int which) {
139 mController.doStart(state, intent);
140 }
141 })
142 .setNegativeButton(R.string.recover_no, new OnClickListener() {
143 @Override
144 public void onClick(DialogInterface dialog, int which) {
John Reck0b3165d2011-06-22 10:44:33 -0700145 dialog.cancel();
146 }
147 })
148 .setOnCancelListener(new OnCancelListener() {
149 @Override
150 public void onCancel(DialogInterface dialog) {
John Reck24f18262011-06-17 14:47:20 -0700151 clearState(mController.getActivity());
John Reck847b5322011-04-14 17:02:18 -0700152 mController.doStart(null, intent);
153 }
154 })
155 .show();
156 }
157
158 public void startRecovery(Intent intent) {
159 Parcel parcel = Parcel.obtain();
160 try {
161 Context context = mController.getActivity();
162 FileInputStream fin = context.openFileInput(STATE_FILE);
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 Bundle state = parcel.readBundle();
173 promptToRecover(state, intent);
174 } catch (FileNotFoundException e) {
175 // No state to recover
176 mController.doStart(null, intent);
177 } catch (Exception e) {
178 Log.w(LOGTAG, "Failed to recover state!", e);
179 mController.doStart(null, intent);
180 } finally {
181 parcel.recycle();
182 }
183 }
184}