1 /* 2 * Copyright (C) 2008 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 17 package com.android.settings.deviceinfo; 18 19 import android.app.ActivityManager; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.DialogInterface.OnCancelListener; 28 import android.content.pm.ApplicationInfo; 29 import android.content.res.Resources; 30 import android.os.Bundle; 31 import android.os.IBinder; 32 import android.os.RemoteException; 33 import android.os.Environment; 34 import android.os.storage.IMountService; 35 import android.os.ServiceManager; 36 import android.os.StatFs; 37 import android.os.storage.StorageManager; 38 import android.os.storage.StorageEventListener; 39 import android.preference.Preference; 40 import android.preference.PreferenceActivity; 41 import android.preference.PreferenceGroup; 42 import android.preference.PreferenceScreen; 43 import android.text.format.Formatter; 44 import android.util.Log; 45 import android.widget.Toast; 46 47 import com.android.settings.R; 48 49 import java.io.File; 50 import java.util.List; 51 52 public class Memory extends PreferenceActivity implements OnCancelListener { 53 private static final String TAG = "Memory"; 54 private static final boolean localLOGV = false; 55 56 private static final String MEMORY_SD_SIZE = "memory_sd_size"; 57 58 private static final String MEMORY_SD_AVAIL = "memory_sd_avail"; 59 60 private static final String MEMORY_SD_MOUNT_TOGGLE = "memory_sd_mount_toggle"; 61 62 private static final String MEMORY_SD_FORMAT = "memory_sd_format"; 63 64 private static final String MEMORY_SD_GROUP = "memory_sd"; 65 66 private static final int DLG_CONFIRM_UNMOUNT = 1; 67 private static final int DLG_ERROR_UNMOUNT = 2; 68 69 private Resources mRes; 70 71 private Preference mSdSize; 72 private Preference mSdAvail; 73 private Preference mSdMountToggle; 74 private Preference mSdFormat; 75 private PreferenceGroup mSdMountPreferenceGroup; 76 77 boolean mSdMountToggleAdded = true; 78 79 // Access using getMountService() 80 private IMountService mMountService = null; 81 82 private StorageManager mStorageManager = null; 83 84 @Override onCreate(Bundle icicle)85 protected void onCreate(Bundle icicle) { 86 super.onCreate(icicle); 87 88 if (mStorageManager == null) { 89 mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE); 90 mStorageManager.registerListener(mStorageListener); 91 } 92 93 addPreferencesFromResource(R.xml.device_info_memory); 94 95 mRes = getResources(); 96 mSdSize = findPreference(MEMORY_SD_SIZE); 97 mSdAvail = findPreference(MEMORY_SD_AVAIL); 98 mSdMountToggle = findPreference(MEMORY_SD_MOUNT_TOGGLE); 99 mSdFormat = findPreference(MEMORY_SD_FORMAT); 100 101 mSdMountPreferenceGroup = (PreferenceGroup)findPreference(MEMORY_SD_GROUP); 102 } 103 104 @Override onResume()105 protected void onResume() { 106 super.onResume(); 107 108 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED); 109 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 110 intentFilter.addDataScheme("file"); 111 registerReceiver(mReceiver, intentFilter); 112 113 updateMemoryStatus(); 114 } 115 116 StorageEventListener mStorageListener = new StorageEventListener() { 117 118 @Override 119 public void onStorageStateChanged(String path, String oldState, String newState) { 120 Log.i(TAG, "Received storage state changed notification that " + 121 path + " changed state from " + oldState + 122 " to " + newState); 123 updateMemoryStatus(); 124 } 125 }; 126 127 @Override onPause()128 protected void onPause() { 129 super.onPause(); 130 unregisterReceiver(mReceiver); 131 } 132 133 @Override onDestroy()134 protected void onDestroy() { 135 if (mStorageManager != null && mStorageListener != null) { 136 mStorageManager.unregisterListener(mStorageListener); 137 } 138 super.onDestroy(); 139 } 140 getMountService()141 private synchronized IMountService getMountService() { 142 if (mMountService == null) { 143 IBinder service = ServiceManager.getService("mount"); 144 if (service != null) { 145 mMountService = IMountService.Stub.asInterface(service); 146 } else { 147 Log.e(TAG, "Can't get mount service"); 148 } 149 } 150 return mMountService; 151 } 152 153 @Override onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)154 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 155 if (preference == mSdMountToggle) { 156 String status = Environment.getExternalStorageState(); 157 if (status.equals(Environment.MEDIA_MOUNTED)) { 158 unmount(); 159 } else { 160 mount(); 161 } 162 return true; 163 } else if (preference == mSdFormat) { 164 Intent intent = new Intent(Intent.ACTION_VIEW); 165 intent.setClass(this, com.android.settings.MediaFormat.class); 166 startActivity(intent); 167 return true; 168 } 169 170 return false; 171 } 172 173 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 174 @Override 175 public void onReceive(Context context, Intent intent) { 176 updateMemoryStatus(); 177 } 178 }; 179 180 @Override onCreateDialog(int id, Bundle args)181 public Dialog onCreateDialog(int id, Bundle args) { 182 switch (id) { 183 case DLG_CONFIRM_UNMOUNT: 184 return new AlertDialog.Builder(this) 185 .setTitle(R.string.dlg_confirm_unmount_title) 186 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 187 public void onClick(DialogInterface dialog, int which) { 188 doUnmount(true); 189 }}) 190 .setNegativeButton(R.string.cancel, null) 191 .setMessage(R.string.dlg_confirm_unmount_text) 192 .setOnCancelListener(this) 193 .create(); 194 case DLG_ERROR_UNMOUNT: 195 return new AlertDialog.Builder(this ) 196 .setTitle(R.string.dlg_error_unmount_title) 197 .setNeutralButton(R.string.dlg_ok, null) 198 .setMessage(R.string.dlg_error_unmount_text) 199 .setOnCancelListener(this) 200 .create(); 201 } 202 return null; 203 } 204 205 private void doUnmount(boolean force) { 206 // Present a toast here 207 Toast.makeText(this, R.string.unmount_inform_text, Toast.LENGTH_SHORT).show(); 208 IMountService mountService = getMountService(); 209 String extStoragePath = Environment.getExternalStorageDirectory().toString(); 210 try { 211 mSdMountToggle.setEnabled(false); 212 mSdMountToggle.setTitle(mRes.getString(R.string.sd_ejecting_title)); 213 mSdMountToggle.setSummary(mRes.getString(R.string.sd_ejecting_summary)); 214 mountService.unmountVolume(extStoragePath, force); 215 } catch (RemoteException e) { 216 // Informative dialog to user that 217 // unmount failed. 218 showDialogInner(DLG_ERROR_UNMOUNT); 219 } 220 } 221 222 private void showDialogInner(int id) { 223 removeDialog(id); 224 showDialog(id); 225 } 226 227 private boolean hasAppsAccessingStorage() throws RemoteException { 228 String extStoragePath = Environment.getExternalStorageDirectory().toString(); 229 IMountService mountService = getMountService(); 230 int stUsers[] = mountService.getStorageUsers(extStoragePath); 231 if (stUsers != null && stUsers.length > 0) { 232 return true; 233 } 234 ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); 235 List<ApplicationInfo> list = am.getRunningExternalApplications(); 236 if (list != null && list.size() > 0) { 237 return true; 238 } 239 return false; 240 } 241 242 private void unmount() { 243 // Check if external media is in use. 244 try { 245 if (hasAppsAccessingStorage()) { 246 if (localLOGV) Log.i(TAG, "Do have storage users accessing media"); 247 // Present dialog to user 248 showDialogInner(DLG_CONFIRM_UNMOUNT); 249 } else { 250 doUnmount(true); 251 } 252 } catch (RemoteException e) { 253 // Very unlikely. But present an error dialog anyway 254 Log.e(TAG, "Is MountService running?"); 255 showDialogInner(DLG_ERROR_UNMOUNT); 256 } 257 } 258 259 private void mount() { 260 IMountService mountService = getMountService(); 261 try { 262 if (mountService != null) { 263 mountService.mountVolume(Environment.getExternalStorageDirectory().toString()); 264 } else { 265 Log.e(TAG, "Mount service is null, can't mount"); 266 } 267 } catch (RemoteException ex) { 268 } 269 } 270 271 private void updateMemoryStatus() { 272 String status = Environment.getExternalStorageState(); 273 String readOnly = ""; 274 if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) { 275 status = Environment.MEDIA_MOUNTED; 276 readOnly = mRes.getString(R.string.read_only); 277 } 278 279 if (status.equals(Environment.MEDIA_MOUNTED)) { 280 if (!Environment.isExternalStorageRemovable()) { 281 // This device has built-in storage that is not removable. 282 // There is no reason for the user to unmount it. 283 if (mSdMountToggleAdded) { 284 mSdMountPreferenceGroup.removePreference(mSdMountToggle); 285 mSdMountToggleAdded = false; 286 } 287 } 288 try { 289 File path = Environment.getExternalStorageDirectory(); 290 StatFs stat = new StatFs(path.getPath()); 291 long blockSize = stat.getBlockSize(); 292 long totalBlocks = stat.getBlockCount(); 293 long availableBlocks = stat.getAvailableBlocks(); 294 295 mSdSize.setSummary(formatSize(totalBlocks * blockSize)); 296 mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly); 297 298 mSdMountToggle.setEnabled(true); 299 mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject)); 300 mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary)); 301 302 } catch (IllegalArgumentException e) { 303 // this can occur if the SD card is removed, but we haven't received the 304 // ACTION_MEDIA_REMOVED Intent yet. 305 status = Environment.MEDIA_REMOVED; 306 } 307 308 } else { 309 mSdSize.setSummary(mRes.getString(R.string.sd_unavailable)); 310 mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable)); 311 312 313 if (!Environment.isExternalStorageRemovable()) { 314 if (status.equals(Environment.MEDIA_UNMOUNTED)) { 315 if (!mSdMountToggleAdded) { 316 mSdMountPreferenceGroup.addPreference(mSdMountToggle); 317 mSdMountToggleAdded = true; 318 } 319 } 320 } 321 322 if (status.equals(Environment.MEDIA_UNMOUNTED) || 323 status.equals(Environment.MEDIA_NOFS) || 324 status.equals(Environment.MEDIA_UNMOUNTABLE) ) { 325 mSdMountToggle.setEnabled(true); 326 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount)); 327 mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary)); 328 } else { 329 mSdMountToggle.setEnabled(false); 330 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount)); 331 mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary)); 332 } 333 } 334 335 File path = Environment.getDataDirectory(); 336 StatFs stat = new StatFs(path.getPath()); 337 long blockSize = stat.getBlockSize(); 338 long availableBlocks = stat.getAvailableBlocks(); 339 findPreference("memory_internal_avail").setSummary(formatSize(availableBlocks * blockSize)); 340 } 341 342 private String formatSize(long size) { 343 return Formatter.formatFileSize(this, size); 344 } 345 346 public void onCancel(DialogInterface dialog) { 347 finish(); 348 } 349 350 } 351