1847b532045e3cb117a847ebb956c9919401f332dJohn Reck/*
2847b532045e3cb117a847ebb956c9919401f332dJohn Reck * Copyright (C) 2011 The Android Open Source Project
3847b532045e3cb117a847ebb956c9919401f332dJohn Reck *
4847b532045e3cb117a847ebb956c9919401f332dJohn Reck * Licensed under the Apache License, Version 2.0 (the "License");
5847b532045e3cb117a847ebb956c9919401f332dJohn Reck * you may not use this file except in compliance with the License.
6847b532045e3cb117a847ebb956c9919401f332dJohn Reck * You may obtain a copy of the License at
7847b532045e3cb117a847ebb956c9919401f332dJohn Reck *
8847b532045e3cb117a847ebb956c9919401f332dJohn Reck *      http://www.apache.org/licenses/LICENSE-2.0
9847b532045e3cb117a847ebb956c9919401f332dJohn Reck *
10847b532045e3cb117a847ebb956c9919401f332dJohn Reck * Unless required by applicable law or agreed to in writing, software
11847b532045e3cb117a847ebb956c9919401f332dJohn Reck * distributed under the License is distributed on an "AS IS" BASIS,
12847b532045e3cb117a847ebb956c9919401f332dJohn Reck * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13847b532045e3cb117a847ebb956c9919401f332dJohn Reck * See the License for the specific language governing permissions and
14847b532045e3cb117a847ebb956c9919401f332dJohn Reck * limitations under the License.
15847b532045e3cb117a847ebb956c9919401f332dJohn Reck */
16847b532045e3cb117a847ebb956c9919401f332dJohn Reck
17847b532045e3cb117a847ebb956c9919401f332dJohn Reckpackage com.android.browser;
18847b532045e3cb117a847ebb956c9919401f332dJohn Reck
19847b532045e3cb117a847ebb956c9919401f332dJohn Reckimport android.content.Context;
20847b532045e3cb117a847ebb956c9919401f332dJohn Reckimport android.content.Intent;
21cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reckimport android.content.SharedPreferences;
22847b532045e3cb117a847ebb956c9919401f332dJohn Reckimport android.os.Bundle;
23378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reckimport android.os.Handler;
24bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reckimport android.os.Message;
25847b532045e3cb117a847ebb956c9919401f332dJohn Reckimport android.os.Parcel;
26847b532045e3cb117a847ebb956c9919401f332dJohn Reckimport android.util.Log;
27847b532045e3cb117a847ebb956c9919401f332dJohn Reck
28847b532045e3cb117a847ebb956c9919401f332dJohn Reckimport java.io.ByteArrayOutputStream;
29a4816530199095b8e7ad0ebd6ac9f22dff32ea2aJohn Reckimport java.io.File;
30847b532045e3cb117a847ebb956c9919401f332dJohn Reckimport java.io.FileInputStream;
31847b532045e3cb117a847ebb956c9919401f332dJohn Reckimport java.io.FileNotFoundException;
32847b532045e3cb117a847ebb956c9919401f332dJohn Reckimport java.io.FileOutputStream;
33679da25ab920d163b3f79a9f5410e78c98d557bcBen Murdochimport java.io.IOException;
34847b532045e3cb117a847ebb956c9919401f332dJohn Reck
35847b532045e3cb117a847ebb956c9919401f332dJohn Reckpublic class CrashRecoveryHandler {
36847b532045e3cb117a847ebb956c9919401f332dJohn Reck
376c2e2f34718043f36488b4a94879dc2605aaac49John Reck    private static final boolean LOGV_ENABLED = Browser.LOGV_ENABLED;
38847b532045e3cb117a847ebb956c9919401f332dJohn Reck    private static final String LOGTAG = "BrowserCrashRecovery";
39847b532045e3cb117a847ebb956c9919401f332dJohn Reck    private static final String STATE_FILE = "browser_state.parcel";
40847b532045e3cb117a847ebb956c9919401f332dJohn Reck    private static final int BUFFER_SIZE = 4096;
41378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    private static final long BACKUP_DELAY = 500; // 500ms between writes
42cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck    /* This is the duration for which we will prompt to restore
43cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck     * instead of automatically restoring. The first time the browser crashes,
44cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck     * we will automatically restore. If we then crash again within XX minutes,
45cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck     * we will prompt instead of automatically restoring.
46cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck     */
47023124e33a36c31cee61b08020189c16ffaf8624John Reck    private static final long PROMPT_INTERVAL = 5 * 60 * 1000; // 5 minutes
48378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck
49bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    private static final int MSG_WRITE_STATE = 1;
50bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    private static final int MSG_CLEAR_STATE = 2;
51bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    private static final int MSG_PRELOAD_STATE = 3;
52bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck
53378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    private static CrashRecoveryHandler sInstance;
54847b532045e3cb117a847ebb956c9919401f332dJohn Reck
55847b532045e3cb117a847ebb956c9919401f332dJohn Reck    private Controller mController;
56bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    private Context mContext;
57378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    private Handler mForegroundHandler;
58378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    private Handler mBackgroundHandler;
59bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    private boolean mIsPreloading = false;
60bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    private boolean mDidPreload = false;
61bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    private Bundle mRecoveryState = null;
62378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck
63378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    public static CrashRecoveryHandler initialize(Controller controller) {
64378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck        if (sInstance == null) {
65378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck            sInstance = new CrashRecoveryHandler(controller);
66378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck        } else {
67378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck            sInstance.mController = controller;
68378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck        }
69378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck        return sInstance;
70378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    }
71847b532045e3cb117a847ebb956c9919401f332dJohn Reck
72378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    public static CrashRecoveryHandler getInstance() {
73378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck        return sInstance;
74378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    }
75378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck
76378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    private CrashRecoveryHandler(Controller controller) {
77847b532045e3cb117a847ebb956c9919401f332dJohn Reck        mController = controller;
78bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        mContext = mController.getActivity().getApplicationContext();
79378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck        mForegroundHandler = new Handler();
80cadae72b6309303bc7b22e85181222b73e176c32John Reck        mBackgroundHandler = new Handler(BackgroundHandler.getLooper()) {
81bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck
82bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck            @Override
83bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck            public void handleMessage(Message msg) {
84bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                switch (msg.what) {
85bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                case MSG_WRITE_STATE:
863636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount                    Bundle saveState = (Bundle) msg.obj;
873636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount                    writeState(saveState);
88bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                    break;
89bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                case MSG_CLEAR_STATE:
906c2e2f34718043f36488b4a94879dc2605aaac49John Reck                    if (LOGV_ENABLED) {
916c2e2f34718043f36488b4a94879dc2605aaac49John Reck                        Log.v(LOGTAG, "Clearing crash recovery state");
926c2e2f34718043f36488b4a94879dc2605aaac49John Reck                    }
93bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                    File state = new File(mContext.getCacheDir(), STATE_FILE);
94bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                    if (state.exists()) {
95bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                        state.delete();
96bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                    }
97bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                    break;
98bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                case MSG_PRELOAD_STATE:
99bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                    mRecoveryState = loadCrashState();
100bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                    synchronized (CrashRecoveryHandler.this) {
101bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                        mIsPreloading = false;
102bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                        mDidPreload = true;
103bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                        CrashRecoveryHandler.this.notifyAll();
104bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                    }
105bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                    break;
106bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                }
107bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck            }
108bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        };
109847b532045e3cb117a847ebb956c9919401f332dJohn Reck    }
110847b532045e3cb117a847ebb956c9919401f332dJohn Reck
111847b532045e3cb117a847ebb956c9919401f332dJohn Reck    public void backupState() {
112378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck        mForegroundHandler.postDelayed(mCreateState, BACKUP_DELAY);
113378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    }
114378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck
115378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    private Runnable mCreateState = new Runnable() {
116378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck
117378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck        @Override
118378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck        public void run() {
119378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck            try {
1203636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount                final Bundle state = mController.createSaveState();
121bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                Message.obtain(mBackgroundHandler, MSG_WRITE_STATE, state)
122bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                        .sendToTarget();
123378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck                // Remove any queued up saves
124378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck                mForegroundHandler.removeCallbacks(mCreateState);
125378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck            } catch (Throwable t) {
126378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck                Log.w(LOGTAG, "Failed to save state", t);
127378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck                return;
128847b532045e3cb117a847ebb956c9919401f332dJohn Reck            }
129378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck        }
130378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck
131378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck    };
132378a41055fab6c521cdc1d9b2cfeefff2af19e7cJohn Reck
133bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    public void clearState() {
134bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        mBackgroundHandler.sendEmptyMessage(MSG_CLEAR_STATE);
135023124e33a36c31cee61b08020189c16ffaf8624John Reck        updateLastRecovered(0);
136847b532045e3cb117a847ebb956c9919401f332dJohn Reck    }
137847b532045e3cb117a847ebb956c9919401f332dJohn Reck
138023124e33a36c31cee61b08020189c16ffaf8624John Reck    private boolean shouldRestore() {
1393636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        BrowserSettings browserSettings = BrowserSettings.getInstance();
1403636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        long lastRecovered = browserSettings.getLastRecovered();
141cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck        long timeSinceLastRecover = System.currentTimeMillis() - lastRecovered;
1423636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        return (timeSinceLastRecover > PROMPT_INTERVAL)
1433636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount                || browserSettings.wasLastRunPaused();
144cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck    }
145cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck
146023124e33a36c31cee61b08020189c16ffaf8624John Reck    private void updateLastRecovered(long time) {
1473636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        BrowserSettings browserSettings = BrowserSettings.getInstance();
1483636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        browserSettings.setLastRecovered(time);
149cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck    }
150cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck
1513636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount    synchronized private Bundle loadCrashState() {
152023124e33a36c31cee61b08020189c16ffaf8624John Reck        if (!shouldRestore()) {
153023124e33a36c31cee61b08020189c16ffaf8624John Reck            return null;
154023124e33a36c31cee61b08020189c16ffaf8624John Reck        }
1553636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        BrowserSettings browserSettings = BrowserSettings.getInstance();
1563636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        browserSettings.setLastRunPaused(false);
157cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck        Bundle state = null;
158847b532045e3cb117a847ebb956c9919401f332dJohn Reck        Parcel parcel = Parcel.obtain();
159679da25ab920d163b3f79a9f5410e78c98d557bcBen Murdoch        FileInputStream fin = null;
160847b532045e3cb117a847ebb956c9919401f332dJohn Reck        try {
161bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck            File stateFile = new File(mContext.getCacheDir(), STATE_FILE);
162679da25ab920d163b3f79a9f5410e78c98d557bcBen Murdoch            fin = new FileInputStream(stateFile);
163847b532045e3cb117a847ebb956c9919401f332dJohn Reck            ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
164847b532045e3cb117a847ebb956c9919401f332dJohn Reck            byte[] buffer = new byte[BUFFER_SIZE];
165847b532045e3cb117a847ebb956c9919401f332dJohn Reck            int read;
166847b532045e3cb117a847ebb956c9919401f332dJohn Reck            while ((read = fin.read(buffer)) > 0) {
167847b532045e3cb117a847ebb956c9919401f332dJohn Reck                dataStream.write(buffer, 0, read);
168847b532045e3cb117a847ebb956c9919401f332dJohn Reck            }
169847b532045e3cb117a847ebb956c9919401f332dJohn Reck            byte[] data = dataStream.toByteArray();
170847b532045e3cb117a847ebb956c9919401f332dJohn Reck            parcel.unmarshall(data, 0, data.length);
171847b532045e3cb117a847ebb956c9919401f332dJohn Reck            parcel.setDataPosition(0);
172cfeae6d3dc6f62f2d475bf50c0fc26a0e9c730deJohn Reck            state = parcel.readBundle();
173e659d7b3363a01d238197c65ab032b73c2a0b9d4John Reck            if (state != null && !state.isEmpty()) {
174e659d7b3363a01d238197c65ab032b73c2a0b9d4John Reck                return state;
175e659d7b3363a01d238197c65ab032b73c2a0b9d4John Reck            }
176847b532045e3cb117a847ebb956c9919401f332dJohn Reck        } catch (FileNotFoundException e) {
177847b532045e3cb117a847ebb956c9919401f332dJohn Reck            // No state to recover
178bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        } catch (Throwable e) {
179847b532045e3cb117a847ebb956c9919401f332dJohn Reck            Log.w(LOGTAG, "Failed to recover state!", e);
180847b532045e3cb117a847ebb956c9919401f332dJohn Reck        } finally {
181847b532045e3cb117a847ebb956c9919401f332dJohn Reck            parcel.recycle();
182679da25ab920d163b3f79a9f5410e78c98d557bcBen Murdoch            if (fin != null) {
183679da25ab920d163b3f79a9f5410e78c98d557bcBen Murdoch                try {
184679da25ab920d163b3f79a9f5410e78c98d557bcBen Murdoch                    fin.close();
185679da25ab920d163b3f79a9f5410e78c98d557bcBen Murdoch                } catch (IOException e) { }
186679da25ab920d163b3f79a9f5410e78c98d557bcBen Murdoch            }
187847b532045e3cb117a847ebb956c9919401f332dJohn Reck        }
188023124e33a36c31cee61b08020189c16ffaf8624John Reck        return null;
189bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    }
190bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck
191bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    public void startRecovery(Intent intent) {
192bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        synchronized (CrashRecoveryHandler.this) {
193bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck            while (mIsPreloading) {
194bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                try {
195bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                    CrashRecoveryHandler.this.wait();
196bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                } catch (InterruptedException e) {}
197bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck            }
198bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        }
199bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        if (!mDidPreload) {
200bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck            mRecoveryState = loadCrashState();
201bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        }
202023124e33a36c31cee61b08020189c16ffaf8624John Reck        updateLastRecovered(mRecoveryState != null
203023124e33a36c31cee61b08020189c16ffaf8624John Reck                ? System.currentTimeMillis() : 0);
2043636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        mController.doStart(mRecoveryState, intent);
205bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        mRecoveryState = null;
206847b532045e3cb117a847ebb956c9919401f332dJohn Reck    }
207bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck
208bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    public void preloadCrashState() {
209bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        synchronized (CrashRecoveryHandler.this) {
210bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck            if (mIsPreloading) {
211bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck                return;
212bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck            }
213bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck            mIsPreloading = true;
214bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        }
215bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck        mBackgroundHandler.sendEmptyMessage(MSG_PRELOAD_STATE);
216bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck    }
217bc490d2c32aac4e12069be1b92bfaf185ff5dbb5John Reck
2183636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount    /**
2193636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount     * Writes the crash recovery state to a file synchronously.
2203636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount     * Errors are swallowed, but logged.
2213636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount     * @param state The state to write out
2223636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount     */
2233636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount    synchronized void writeState(Bundle state) {
2243636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        if (LOGV_ENABLED) {
2253636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount            Log.v(LOGTAG, "Saving crash recovery state");
2263636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        }
2273636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        Parcel p = Parcel.obtain();
2283636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        try {
2293636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount            state.writeToParcel(p, 0);
2303636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount            File stateJournal = new File(mContext.getCacheDir(),
2313636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount                    STATE_FILE + ".journal");
2323636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount            FileOutputStream fout = new FileOutputStream(stateJournal);
2333636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount            fout.write(p.marshall());
2343636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount            fout.close();
2353636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount            File stateFile = new File(mContext.getCacheDir(),
2363636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount                    STATE_FILE);
2373636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount            if (!stateJournal.renameTo(stateFile)) {
2383636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount                // Failed to rename, try deleting the existing
2393636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount                // file and try again
2403636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount                stateFile.delete();
2413636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount                stateJournal.renameTo(stateFile);
2423636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount            }
2433636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        } catch (Throwable e) {
2443636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount            Log.i(LOGTAG, "Failed to save persistent state", e);
2453636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        } finally {
2463636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount            p.recycle();
2473636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount        }
2483636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount    }
2493636d0a6d90fd8de55a4894210b2dbe23f32aac9George Mount}