package com.android.internal.os.storage; import android.app.ProgressDialog; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Environment; import android.os.IBinder; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.storage.IMountService; import android.os.storage.StorageEventListener; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.util.Log; import android.view.WindowManager; import android.widget.Toast; import com.android.internal.R; /** * Takes care of unmounting and formatting external storage. */ public class ExternalStorageFormatter extends Service implements DialogInterface.OnCancelListener { static final String TAG = "ExternalStorageFormatter"; public static final String FORMAT_ONLY = "com.android.internal.os.storage.FORMAT_ONLY"; public static final String FORMAT_AND_FACTORY_RESET = "com.android.internal.os.storage.FORMAT_AND_FACTORY_RESET"; public static final String EXTRA_ALWAYS_RESET = "always_reset"; // If non-null, the volume to format. Otherwise, will use the default external storage directory private StorageVolume mStorageVolume; public static final ComponentName COMPONENT_NAME = new ComponentName("android", ExternalStorageFormatter.class.getName()); // Access using getMountService() private IMountService mMountService = null; private StorageManager mStorageManager = null; private PowerManager.WakeLock mWakeLock; private ProgressDialog mProgressDialog = null; private boolean mFactoryReset = false; private boolean mAlwaysReset = false; StorageEventListener mStorageListener = new StorageEventListener() { @Override public void onStorageStateChanged(String path, String oldState, String newState) { Log.i(TAG, "Received storage state changed notification that " + path + " changed state from " + oldState + " to " + newState); updateProgressState(); } }; @Override public void onCreate() { super.onCreate(); if (mStorageManager == null) { mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE); mStorageManager.registerListener(mStorageListener); } mWakeLock = ((PowerManager)getSystemService(Context.POWER_SERVICE)) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ExternalStorageFormatter"); mWakeLock.acquire(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (FORMAT_AND_FACTORY_RESET.equals(intent.getAction())) { mFactoryReset = true; } if (intent.getBooleanExtra(EXTRA_ALWAYS_RESET, false)) { mAlwaysReset = true; } mStorageVolume = intent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME); if (mProgressDialog == null) { mProgressDialog = new ProgressDialog(this); mProgressDialog.setIndeterminate(true); mProgressDialog.setCancelable(true); mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); if (!mAlwaysReset) { mProgressDialog.setOnCancelListener(this); } updateProgressState(); mProgressDialog.show(); } return Service.START_REDELIVER_INTENT; } @Override public void onDestroy() { if (mStorageManager != null) { mStorageManager.unregisterListener(mStorageListener); } if (mProgressDialog != null) { mProgressDialog.dismiss(); } mWakeLock.release(); super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCancel(DialogInterface dialog) { IMountService mountService = getMountService(); String extStoragePath = mStorageVolume == null ? Environment.getExternalStorageDirectory().toString() : mStorageVolume.getPath(); try { mountService.mountVolume(extStoragePath); } catch (RemoteException e) { Log.w(TAG, "Failed talking with mount service", e); } stopSelf(); } void fail(int msg) { Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); if (mAlwaysReset) { sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR")); } stopSelf(); } void updateProgressState() { String status = mStorageVolume == null ? Environment.getExternalStorageState() : mStorageManager.getVolumeState(mStorageVolume.getPath()); if (Environment.MEDIA_MOUNTED.equals(status) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status)) { updateProgressDialog(R.string.progress_unmounting); IMountService mountService = getMountService(); final String extStoragePath = mStorageVolume == null ? Environment.getExternalStorageDirectory().toString() : mStorageVolume.getPath(); try { // Remove encryption mapping if this is an unmount for a factory reset. mountService.unmountVolume(extStoragePath, true, mFactoryReset); } catch (RemoteException e) { Log.w(TAG, "Failed talking with mount service", e); } } else if (Environment.MEDIA_NOFS.equals(status) || Environment.MEDIA_UNMOUNTED.equals(status) || Environment.MEDIA_UNMOUNTABLE.equals(status)) { updateProgressDialog(R.string.progress_erasing); final IMountService mountService = getMountService(); final String extStoragePath = mStorageVolume == null ? Environment.getExternalStorageDirectory().toString() : mStorageVolume.getPath(); if (mountService != null) { new Thread() { @Override public void run() { boolean success = false; try { mountService.formatVolume(extStoragePath); success = true; } catch (Exception e) { Toast.makeText(ExternalStorageFormatter.this, R.string.format_error, Toast.LENGTH_LONG).show(); } if (success) { if (mFactoryReset) { sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR")); // Intent handling is asynchronous -- assume it will happen soon. stopSelf(); return; } } // If we didn't succeed, or aren't doing a full factory // reset, then it is time to remount the storage. if (!success && mAlwaysReset) { sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR")); } else { try { mountService.mountVolume(extStoragePath); } catch (RemoteException e) { Log.w(TAG, "Failed talking with mount service", e); } } stopSelf(); return; } }.start(); } else { Log.w(TAG, "Unable to locate IMountService"); } } else if (Environment.MEDIA_BAD_REMOVAL.equals(status)) { fail(R.string.media_bad_removal); } else if (Environment.MEDIA_CHECKING.equals(status)) { fail(R.string.media_checking); } else if (Environment.MEDIA_REMOVED.equals(status)) { fail(R.string.media_removed); } else if (Environment.MEDIA_SHARED.equals(status)) { fail(R.string.media_shared); } else { fail(R.string.media_unknown_state); Log.w(TAG, "Unknown storage state: " + status); stopSelf(); } } public void updateProgressDialog(int msg) { if (mProgressDialog == null) { mProgressDialog = new ProgressDialog(this); mProgressDialog.setIndeterminate(true); mProgressDialog.setCancelable(false); mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); mProgressDialog.show(); } mProgressDialog.setMessage(getText(msg)); } IMountService getMountService() { if (mMountService == null) { IBinder service = ServiceManager.getService("mount"); if (service != null) { mMountService = IMountService.Stub.asInterface(service); } else { Log.e(TAG, "Can't get mount service"); } } return mMountService; } }