2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package com.android.server.power;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.app.IActivityManager;
23 import android.app.ProgressDialog;
24 import android.bluetooth.BluetoothAdapter;
25 import android.bluetooth.IBluetoothManager;
26 import android.media.AudioAttributes;
27 import android.nfc.NfcAdapter;
28 import android.nfc.INfcAdapter;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.DialogInterface;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.os.FileUtils;
35 import android.os.Handler;
36 import android.os.PowerManager;
37 import android.os.RecoverySystem;
38 import android.os.RemoteException;
39 import android.os.ServiceManager;
40 import android.os.SystemClock;
41 import android.os.SystemProperties;
42 import android.os.UserHandle;
43 import android.os.UserManager;
44 import android.os.Vibrator;
45 import android.os.SystemVibrator;
46 import android.os.storage.IStorageShutdownObserver;
47 import android.os.storage.IStorageManager;
48 import android.system.ErrnoException;
49 import android.system.Os;
51 import com.android.internal.telephony.ITelephony;
52 import com.android.server.pm.PackageManagerService;
54 import android.util.Log;
55 import android.view.WindowManager;
57 import java.io.BufferedReader;
59 import java.io.FileReader;
60 import java.io.IOException;
62 public final class ShutdownThread extends Thread {
64 private static final String TAG = "ShutdownThread";
65 private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
66 // maximum time we wait for the shutdown broadcast before going on.
67 private static final int MAX_BROADCAST_TIME = 10*1000;
68 private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
69 private static final int MAX_RADIO_WAIT_TIME = 12*1000;
70 private static final int MAX_UNCRYPT_WAIT_TIME = 15*60*1000;
71 // constants for progress bar. the values are roughly estimated based on timeout.
72 private static final int BROADCAST_STOP_PERCENT = 2;
73 private static final int ACTIVITY_MANAGER_STOP_PERCENT = 4;
74 private static final int PACKAGE_MANAGER_STOP_PERCENT = 6;
75 private static final int RADIO_STOP_PERCENT = 18;
76 private static final int MOUNT_SERVICE_STOP_PERCENT = 20;
78 // length of vibration before shutting down
79 private static final int SHUTDOWN_VIBRATE_MS = 500;
82 private static final Object sIsStartedGuard = new Object();
83 private static boolean sIsStarted = false;
85 private static boolean mReboot;
86 private static boolean mRebootSafeMode;
87 private static boolean mRebootHasProgressBar;
88 private static String mReason;
90 // Provides shutdown assurance in case the system_server is killed
91 public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
93 // Indicates whether we are rebooting into safe mode
94 public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode";
95 public static final String RO_SAFEMODE_PROPERTY = "ro.sys.safemode";
97 // static instance of this thread
98 private static final ShutdownThread sInstance = new ShutdownThread();
100 private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
101 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
102 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
105 private final Object mActionDoneSync = new Object();
106 private boolean mActionDone;
107 private Context mContext;
108 private PowerManager mPowerManager;
109 private PowerManager.WakeLock mCpuWakeLock;
110 private PowerManager.WakeLock mScreenWakeLock;
111 private Handler mHandler;
113 private static AlertDialog sConfirmDialog;
114 private ProgressDialog mProgressDialog;
116 private ShutdownThread() {
120 * Request a clean shutdown, waiting for subsystems to clean up their
121 * state etc. Must be called from a Looper thread in which its UI
124 * @param context Context used to display the shutdown progress dialog. This must be a context
125 * suitable for displaying UI (aka Themable).
126 * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
127 * @param confirm true if user confirmation is needed before shutting down.
129 public static void shutdown(final Context context, String reason, boolean confirm) {
131 mRebootSafeMode = false;
133 shutdownInner(context, confirm);
136 private static void shutdownInner(final Context context, boolean confirm) {
137 // ShutdownThread is called from many places, so best to verify here that the context passed
139 context.assertRuntimeOverlayThemable();
141 // ensure that only one thread is trying to power down.
142 // any additional calls are just returned
143 synchronized (sIsStartedGuard) {
145 Log.d(TAG, "Request to shutdown already running, returning.");
150 final int longPressBehavior = context.getResources().getInteger(
151 com.android.internal.R.integer.config_longPressOnPowerBehavior);
152 final int resourceId = mRebootSafeMode
153 ? com.android.internal.R.string.reboot_safemode_confirm
154 : (longPressBehavior == 2
155 ? com.android.internal.R.string.shutdown_confirm_question
156 : com.android.internal.R.string.shutdown_confirm);
158 Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
161 final CloseDialogReceiver closer = new CloseDialogReceiver(context);
162 if (sConfirmDialog != null) {
163 sConfirmDialog.dismiss();
165 sConfirmDialog = new AlertDialog.Builder(context)
166 .setTitle(mRebootSafeMode
167 ? com.android.internal.R.string.reboot_safemode_title
168 : com.android.internal.R.string.power_off)
169 .setMessage(resourceId)
170 .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
171 public void onClick(DialogInterface dialog, int which) {
172 beginShutdownSequence(context);
175 .setNegativeButton(com.android.internal.R.string.no, null)
177 closer.dialog = sConfirmDialog;
178 sConfirmDialog.setOnDismissListener(closer);
179 sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
180 sConfirmDialog.show();
182 beginShutdownSequence(context);
186 private static class CloseDialogReceiver extends BroadcastReceiver
187 implements DialogInterface.OnDismissListener {
188 private Context mContext;
189 public Dialog dialog;
191 CloseDialogReceiver(Context context) {
193 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
194 context.registerReceiver(this, filter);
198 public void onReceive(Context context, Intent intent) {
202 public void onDismiss(DialogInterface unused) {
203 mContext.unregisterReceiver(this);
208 * Request a clean shutdown, waiting for subsystems to clean up their
209 * state etc. Must be called from a Looper thread in which its UI
212 * @param context Context used to display the shutdown progress dialog. This must be a context
213 * suitable for displaying UI (aka Themable).
214 * @param reason code to pass to the kernel (e.g. "recovery"), or null.
215 * @param confirm true if user confirmation is needed before shutting down.
217 public static void reboot(final Context context, String reason, boolean confirm) {
219 mRebootSafeMode = false;
220 mRebootHasProgressBar = false;
222 shutdownInner(context, confirm);
226 * Request a reboot into safe mode. Must be called from a Looper thread in which its UI
229 * @param context Context used to display the shutdown progress dialog. This must be a context
230 * suitable for displaying UI (aka Themable).
231 * @param confirm true if user confirmation is needed before shutting down.
233 public static void rebootSafeMode(final Context context, boolean confirm) {
234 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
235 if (um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
240 mRebootSafeMode = true;
241 mRebootHasProgressBar = false;
243 shutdownInner(context, confirm);
246 private static void beginShutdownSequence(Context context) {
247 synchronized (sIsStartedGuard) {
249 Log.d(TAG, "Shutdown sequence already running, returning.");
255 // Throw up a system dialog to indicate the device is rebooting / shutting down.
256 ProgressDialog pd = new ProgressDialog(context);
258 // Path 1: Reboot to recovery for update
259 // Condition: mReason startswith REBOOT_RECOVERY_UPDATE
261 // Path 1a: uncrypt needed
262 // Condition: if /cache/recovery/uncrypt_file exists but
263 // /cache/recovery/block.map doesn't.
264 // UI: determinate progress bar (mRebootHasProgressBar == True)
266 // * Path 1a is expected to be removed once the GmsCore shipped on
267 // device always calls uncrypt prior to reboot.
269 // Path 1b: uncrypt already done
270 // UI: spinning circle only (no progress bar)
272 // Path 2: Reboot to recovery for factory reset
273 // Condition: mReason == REBOOT_RECOVERY
274 // UI: spinning circle only (no progress bar)
276 // Path 3: Regular reboot / shutdown
277 // Condition: Otherwise
278 // UI: spinning circle only (no progress bar)
280 // mReason could be "recovery-update" or "recovery-update,quiescent".
281 if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
282 // We need the progress bar if uncrypt will be invoked during the
283 // reboot, which might be time-consuming.
284 mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
285 && !(RecoverySystem.BLOCK_MAP_FILE.exists());
286 pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
287 if (mRebootHasProgressBar) {
290 pd.setIndeterminate(false);
291 pd.setProgressNumberFormat(null);
292 pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
293 pd.setMessage(context.getText(
294 com.android.internal.R.string.reboot_to_update_prepare));
296 pd.setIndeterminate(true);
297 pd.setMessage(context.getText(
298 com.android.internal.R.string.reboot_to_update_reboot));
300 } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
301 // Factory reset path. Set the dialog message accordingly.
302 pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
303 pd.setMessage(context.getText(
304 com.android.internal.R.string.reboot_to_reset_message));
305 pd.setIndeterminate(true);
307 pd.setTitle(context.getText(com.android.internal.R.string.power_off));
308 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
309 pd.setIndeterminate(true);
311 pd.setCancelable(false);
312 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
316 sInstance.mProgressDialog = pd;
317 sInstance.mContext = context;
318 sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
320 // make sure we never fall asleep again
321 sInstance.mCpuWakeLock = null;
323 sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
324 PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
325 sInstance.mCpuWakeLock.setReferenceCounted(false);
326 sInstance.mCpuWakeLock.acquire();
327 } catch (SecurityException e) {
328 Log.w(TAG, "No permission to acquire wake lock", e);
329 sInstance.mCpuWakeLock = null;
332 // also make sure the screen stays on for better user experience
333 sInstance.mScreenWakeLock = null;
334 if (sInstance.mPowerManager.isScreenOn()) {
336 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
337 PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
338 sInstance.mScreenWakeLock.setReferenceCounted(false);
339 sInstance.mScreenWakeLock.acquire();
340 } catch (SecurityException e) {
341 Log.w(TAG, "No permission to acquire wake lock", e);
342 sInstance.mScreenWakeLock = null;
346 // start the thread that initiates shutdown
347 sInstance.mHandler = new Handler() {
353 synchronized (mActionDoneSync) {
355 mActionDoneSync.notifyAll();
360 * Makes sure we handle the shutdown gracefully.
361 * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
364 BroadcastReceiver br = new BroadcastReceiver() {
365 @Override public void onReceive(Context context, Intent intent) {
366 // We don't allow apps to cancel this, so ignore the result.
372 * Write a system property in case the system_server reboots before we
373 * get to the actual hardware restart. If that happens, we'll retry at
374 * the beginning of the SystemServer startup.
377 String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");
378 SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
382 * If we are rebooting into safe mode, write a system property
385 if (mRebootSafeMode) {
386 SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
389 Log.i(TAG, "Sending shutdown broadcast...");
391 // First send the high-level shut down broadcast.
393 Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
394 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
395 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
396 mContext.sendOrderedBroadcastAsUser(intent,
397 UserHandle.ALL, null, br, mHandler, 0, null, null);
399 final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
400 synchronized (mActionDoneSync) {
401 while (!mActionDone) {
402 long delay = endTime - SystemClock.elapsedRealtime();
404 Log.w(TAG, "Shutdown broadcast timed out");
406 } else if (mRebootHasProgressBar) {
407 int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
408 BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
409 sInstance.setRebootProgress(status, null);
412 mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
413 } catch (InterruptedException e) {
417 if (mRebootHasProgressBar) {
418 sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
421 Log.i(TAG, "Shutting down activity manager...");
423 final IActivityManager am =
424 IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
427 am.shutdown(MAX_BROADCAST_TIME);
428 } catch (RemoteException e) {
431 if (mRebootHasProgressBar) {
432 sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
435 Log.i(TAG, "Shutting down package manager...");
437 final PackageManagerService pm = (PackageManagerService)
438 ServiceManager.getService("package");
442 if (mRebootHasProgressBar) {
443 sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
447 shutdownRadios(MAX_RADIO_WAIT_TIME);
448 if (mRebootHasProgressBar) {
449 sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
452 // Shutdown StorageManagerService to ensure media is in a safe state
453 IStorageShutdownObserver observer = new IStorageShutdownObserver.Stub() {
454 public void onShutDownComplete(int statusCode) throws RemoteException {
455 Log.w(TAG, "Result code " + statusCode + " from StorageManagerService.shutdown");
460 Log.i(TAG, "Shutting down StorageManagerService");
462 // Set initial variables and time out time.
464 final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
465 synchronized (mActionDoneSync) {
467 final IStorageManager storageManager = IStorageManager.Stub.asInterface(
468 ServiceManager.checkService("mount"));
469 if (storageManager != null) {
470 storageManager.shutdown(observer);
472 Log.w(TAG, "StorageManagerService unavailable for shutdown");
474 } catch (Exception e) {
475 Log.e(TAG, "Exception during StorageManagerService shutdown", e);
477 while (!mActionDone) {
478 long delay = endShutTime - SystemClock.elapsedRealtime();
480 Log.w(TAG, "Shutdown wait timed out");
482 } else if (mRebootHasProgressBar) {
483 int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
484 (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
485 MAX_SHUTDOWN_WAIT_TIME);
486 status += RADIO_STOP_PERCENT;
487 sInstance.setRebootProgress(status, null);
490 mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
491 } catch (InterruptedException e) {
495 if (mRebootHasProgressBar) {
496 sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
498 // If it's to reboot to install an update and uncrypt hasn't been
499 // done yet, trigger it now.
503 rebootOrShutdown(mContext, mReboot, mReason);
506 private void setRebootProgress(final int progress, final CharSequence message) {
507 mHandler.post(new Runnable() {
510 if (mProgressDialog != null) {
511 mProgressDialog.setProgress(progress);
512 if (message != null) {
513 mProgressDialog.setMessage(message);
520 private void shutdownRadios(final int timeout) {
521 // If a radio is wedged, disabling it may hang so we do this work in another thread,
523 final long endTime = SystemClock.elapsedRealtime() + timeout;
524 final boolean[] done = new boolean[1];
525 Thread t = new Thread() {
528 boolean bluetoothOff;
531 final INfcAdapter nfc =
532 INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
533 final ITelephony phone =
534 ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
535 final IBluetoothManager bluetooth =
536 IBluetoothManager.Stub.asInterface(ServiceManager.checkService(
537 BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE));
540 nfcOff = nfc == null ||
541 nfc.getState() == NfcAdapter.STATE_OFF;
543 Log.w(TAG, "Turning off NFC...");
544 nfc.disable(false); // Don't persist new state
546 } catch (RemoteException ex) {
547 Log.e(TAG, "RemoteException during NFC shutdown", ex);
552 bluetoothOff = bluetooth == null ||
553 bluetooth.getState() == BluetoothAdapter.STATE_OFF;
555 Log.w(TAG, "Disabling Bluetooth...");
556 bluetooth.disable(mContext.getPackageName(), false); // disable but don't persist new state
558 } catch (RemoteException ex) {
559 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
564 radioOff = phone == null || !phone.needMobileRadioShutdown();
566 Log.w(TAG, "Turning off cellular radios...");
567 phone.shutdownMobileRadios();
569 } catch (RemoteException ex) {
570 Log.e(TAG, "RemoteException during radio shutdown", ex);
574 Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
576 long delay = endTime - SystemClock.elapsedRealtime();
578 if (mRebootHasProgressBar) {
579 int status = (int)((timeout - delay) * 1.0 *
580 (RADIO_STOP_PERCENT - PACKAGE_MANAGER_STOP_PERCENT) / timeout);
581 status += PACKAGE_MANAGER_STOP_PERCENT;
582 sInstance.setRebootProgress(status, null);
587 bluetoothOff = bluetooth.getState() == BluetoothAdapter.STATE_OFF;
588 } catch (RemoteException ex) {
589 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
593 Log.i(TAG, "Bluetooth turned off.");
598 radioOff = !phone.needMobileRadioShutdown();
599 } catch (RemoteException ex) {
600 Log.e(TAG, "RemoteException during radio shutdown", ex);
604 Log.i(TAG, "Radio turned off.");
609 nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
610 } catch (RemoteException ex) {
611 Log.e(TAG, "RemoteException during NFC shutdown", ex);
615 Log.i(TAG, "NFC turned off.");
619 if (radioOff && bluetoothOff && nfcOff) {
620 Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
624 SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
626 delay = endTime - SystemClock.elapsedRealtime();
634 } catch (InterruptedException ex) {
637 Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown.");
642 * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
643 * or {@link #shutdown(Context, boolean)} instead.
645 * @param context Context used to vibrate or null without vibration
646 * @param reboot true to reboot or false to shutdown
647 * @param reason reason for reboot/shutdown
649 public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
651 Log.i(TAG, "Rebooting, reason: " + reason);
652 PowerManagerService.lowLevelReboot(reason);
653 Log.e(TAG, "Reboot failed, will attempt shutdown instead");
655 } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
656 // vibrate before shutting down
657 Vibrator vibrator = new SystemVibrator(context);
659 vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
660 } catch (Exception e) {
661 // Failure to vibrate shouldn't interrupt shutdown. Just log it.
662 Log.w(TAG, "Failed to vibrate during shutdown.", e);
665 // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
667 Thread.sleep(SHUTDOWN_VIBRATE_MS);
668 } catch (InterruptedException unused) {
673 Log.i(TAG, "Performing low-level shutdown...");
674 PowerManagerService.lowLevelShutdown(reason);
677 private void uncrypt() {
678 Log.i(TAG, "Calling uncrypt and monitoring the progress...");
680 final RecoverySystem.ProgressListener progressListener =
681 new RecoverySystem.ProgressListener() {
683 public void onProgress(int status) {
684 if (status >= 0 && status < 100) {
685 // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
686 status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
687 status += MOUNT_SERVICE_STOP_PERCENT;
688 CharSequence msg = mContext.getText(
689 com.android.internal.R.string.reboot_to_update_package);
690 sInstance.setRebootProgress(status, msg);
691 } else if (status == 100) {
692 CharSequence msg = mContext.getText(
693 com.android.internal.R.string.reboot_to_update_reboot);
694 sInstance.setRebootProgress(status, msg);
701 final boolean[] done = new boolean[1];
703 Thread t = new Thread() {
706 RecoverySystem rs = (RecoverySystem) mContext.getSystemService(
707 Context.RECOVERY_SERVICE);
708 String filename = null;
710 filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null);
711 rs.processPackage(mContext, new File(filename), progressListener);
712 } catch (IOException e) {
713 Log.e(TAG, "Error uncrypting file", e);
721 t.join(MAX_UNCRYPT_WAIT_TIME);
722 } catch (InterruptedException unused) {
725 Log.w(TAG, "Timed out waiting for uncrypt.");
726 final int uncryptTimeoutError = 100;
727 String timeoutMessage = String.format("uncrypt_time: %d\n" + "uncrypt_error: %d\n",
728 MAX_UNCRYPT_WAIT_TIME / 1000, uncryptTimeoutError);
730 FileUtils.stringToFile(RecoverySystem.UNCRYPT_STATUS_FILE, timeoutMessage);
731 } catch (IOException e) {
732 Log.e(TAG, "Failed to write timeout message to uncrypt status", e);