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.AlertDialog; 20 import android.app.Dialog; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.res.Resources; 27 import android.os.Bundle; 28 import android.os.Environment; 29 import android.os.IBinder; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.os.storage.IMountService; 33 import android.os.storage.StorageEventListener; 34 import android.os.storage.StorageManager; 35 import android.os.storage.StorageVolume; 36 import android.preference.Preference; 37 import android.preference.PreferenceActivity; 38 import android.preference.PreferenceScreen; 39 import android.util.Log; 40 import android.view.Menu; 41 import android.view.MenuInflater; 42 import android.view.MenuItem; 43 import android.widget.Toast; 44 45 import com.android.settings.R; 46 import com.android.settings.SettingsPreferenceFragment; 47 import com.android.settings.Utils; 48 49 public class Memory extends SettingsPreferenceFragment { 50 private static final String TAG = "MemorySettings"; 51 52 private static final int DLG_CONFIRM_UNMOUNT = 1; 53 private static final int DLG_ERROR_UNMOUNT = 2; 54 55 private Resources mResources; 56 57 // The mountToggle Preference that has last been clicked. 58 // Assumes no two successive unmount event on 2 different volumes are performed before the first 59 // one's preference is disabled 60 private Preference mLastClickedMountToggle; 61 private String mClickedMountPoint; 62 63 // Access using getMountService() 64 private IMountService mMountService = null; 65 66 private StorageManager mStorageManager = null; 67 68 private StorageVolumePreferenceCategory mInternalStorageVolumePreferenceCategory; 69 private StorageVolumePreferenceCategory[] mStorageVolumePreferenceCategories; 70 71 @Override onCreate(Bundle icicle)72 public void onCreate(Bundle icicle) { 73 super.onCreate(icicle); 74 75 if (mStorageManager == null) { 76 mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE); 77 mStorageManager.registerListener(mStorageListener); 78 } 79 80 addPreferencesFromResource(R.xml.device_info_memory); 81 82 mResources = getResources(); 83 84 if (!Environment.isExternalStorageEmulated()) { 85 // External storage is separate from internal storage; need to 86 // show internal storage as a separate item. 87 mInternalStorageVolumePreferenceCategory = new StorageVolumePreferenceCategory( 88 getActivity(), mResources, null, mStorageManager, false); 89 getPreferenceScreen().addPreference(mInternalStorageVolumePreferenceCategory); 90 mInternalStorageVolumePreferenceCategory.init(); 91 } 92 93 StorageVolume[] storageVolumes = mStorageManager.getVolumeList(); 94 int length = storageVolumes.length; 95 mStorageVolumePreferenceCategories = new StorageVolumePreferenceCategory[length]; 96 for (int i = 0; i < length; i++) { 97 StorageVolume storageVolume = storageVolumes[i]; 98 boolean isPrimary = i == 0; 99 mStorageVolumePreferenceCategories[i] = new StorageVolumePreferenceCategory( 100 getActivity(), mResources, storageVolume, mStorageManager, isPrimary); 101 getPreferenceScreen().addPreference(mStorageVolumePreferenceCategories[i]); 102 mStorageVolumePreferenceCategories[i].init(); 103 } 104 105 setHasOptionsMenu(true); 106 } 107 isMassStorageEnabled()108 private boolean isMassStorageEnabled() { 109 // mass storage is enabled if primary volume supports it 110 final StorageVolume[] storageVolumes = mStorageManager.getVolumeList(); 111 return (storageVolumes.length > 0 && storageVolumes[0].allowMassStorage()); 112 } 113 114 @Override onResume()115 public void onResume() { 116 super.onResume(); 117 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED); 118 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 119 intentFilter.addDataScheme("file"); 120 getActivity().registerReceiver(mMediaScannerReceiver, intentFilter); 121 122 if (mInternalStorageVolumePreferenceCategory != null) { 123 mInternalStorageVolumePreferenceCategory.onResume(); 124 } 125 for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) { 126 mStorageVolumePreferenceCategories[i].onResume(); 127 } 128 } 129 130 StorageEventListener mStorageListener = new StorageEventListener() { 131 @Override 132 public void onStorageStateChanged(String path, String oldState, String newState) { 133 Log.i(TAG, "Received storage state changed notification that " + path + 134 " changed state from " + oldState + " to " + newState); 135 for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) { 136 StorageVolumePreferenceCategory svpc = mStorageVolumePreferenceCategories[i]; 137 if (path.equals(svpc.getStorageVolume().getPath())) { 138 svpc.onStorageStateChanged(); 139 break; 140 } 141 } 142 } 143 }; 144 145 @Override onPause()146 public void onPause() { 147 super.onPause(); 148 getActivity().unregisterReceiver(mMediaScannerReceiver); 149 if (mInternalStorageVolumePreferenceCategory != null) { 150 mInternalStorageVolumePreferenceCategory.onPause(); 151 } 152 for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) { 153 mStorageVolumePreferenceCategories[i].onPause(); 154 } 155 } 156 157 @Override onDestroy()158 public void onDestroy() { 159 if (mStorageManager != null && mStorageListener != null) { 160 mStorageManager.unregisterListener(mStorageListener); 161 } 162 super.onDestroy(); 163 } 164 165 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)166 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 167 inflater.inflate(R.menu.storage, menu); 168 } 169 170 @Override onPrepareOptionsMenu(Menu menu)171 public void onPrepareOptionsMenu(Menu menu) { 172 final MenuItem usb = menu.findItem(R.id.storage_usb); 173 usb.setVisible(!isMassStorageEnabled()); 174 } 175 176 @Override onOptionsItemSelected(MenuItem item)177 public boolean onOptionsItemSelected(MenuItem item) { 178 switch (item.getItemId()) { 179 case R.id.storage_usb: 180 if (getActivity() instanceof PreferenceActivity) { 181 ((PreferenceActivity) getActivity()).startPreferencePanel( 182 UsbSettings.class.getCanonicalName(), 183 null, 184 R.string.storage_title_usb, null, 185 this, 0); 186 } else { 187 startFragment(this, UsbSettings.class.getCanonicalName(), -1, null); 188 } 189 return true; 190 } 191 return super.onOptionsItemSelected(item); 192 } 193 getMountService()194 private synchronized IMountService getMountService() { 195 if (mMountService == null) { 196 IBinder service = ServiceManager.getService("mount"); 197 if (service != null) { 198 mMountService = IMountService.Stub.asInterface(service); 199 } else { 200 Log.e(TAG, "Can't get mount service"); 201 } 202 } 203 return mMountService; 204 } 205 206 @Override onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)207 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 208 for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) { 209 StorageVolumePreferenceCategory svpc = mStorageVolumePreferenceCategories[i]; 210 Intent intent = svpc.intentForClick(preference); 211 if (intent != null) { 212 // Don't go across app boundary if monkey is running 213 if (!Utils.isMonkeyRunning()) { 214 startActivity(intent); 215 } 216 return true; 217 } 218 219 if (svpc.mountToggleClicked(preference)) { 220 mLastClickedMountToggle = preference; 221 final StorageVolume storageVolume = svpc.getStorageVolume(); 222 mClickedMountPoint = storageVolume.getPath(); 223 String state = mStorageManager.getVolumeState(storageVolume.getPath()); 224 if (Environment.MEDIA_MOUNTED.equals(state) || 225 Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { 226 unmount(); 227 } else { 228 mount(); 229 } 230 return true; 231 } 232 } 233 234 return false; 235 } 236 237 private final BroadcastReceiver mMediaScannerReceiver = new BroadcastReceiver() { 238 @Override 239 public void onReceive(Context context, Intent intent) { 240 // mInternalStorageVolumePreferenceCategory is not affected by the media scanner 241 for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) { 242 mStorageVolumePreferenceCategories[i].onMediaScannerFinished(); 243 } 244 } 245 }; 246 247 @Override onCreateDialog(int id)248 public Dialog onCreateDialog(int id) { 249 switch (id) { 250 case DLG_CONFIRM_UNMOUNT: 251 return new AlertDialog.Builder(getActivity()) 252 .setTitle(R.string.dlg_confirm_unmount_title) 253 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 254 public void onClick(DialogInterface dialog, int which) { 255 doUnmount(); 256 }}) 257 .setNegativeButton(R.string.cancel, null) 258 .setMessage(R.string.dlg_confirm_unmount_text) 259 .create(); 260 case DLG_ERROR_UNMOUNT: 261 return new AlertDialog.Builder(getActivity()) 262 .setTitle(R.string.dlg_error_unmount_title) 263 .setNeutralButton(R.string.dlg_ok, null) 264 .setMessage(R.string.dlg_error_unmount_text) 265 .create(); 266 } 267 return null; 268 } 269 270 private void doUnmount() { 271 // Present a toast here 272 Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show(); 273 IMountService mountService = getMountService(); 274 try { 275 mLastClickedMountToggle.setEnabled(false); 276 mLastClickedMountToggle.setTitle(mResources.getString(R.string.sd_ejecting_title)); 277 mLastClickedMountToggle.setSummary(mResources.getString(R.string.sd_ejecting_summary)); 278 mountService.unmountVolume(mClickedMountPoint, true, false); 279 } catch (RemoteException e) { 280 // Informative dialog to user that unmount failed. 281 showDialogInner(DLG_ERROR_UNMOUNT); 282 } 283 } 284 285 private void showDialogInner(int id) { 286 removeDialog(id); 287 showDialog(id); 288 } 289 290 private boolean hasAppsAccessingStorage() throws RemoteException { 291 IMountService mountService = getMountService(); 292 int stUsers[] = mountService.getStorageUsers(mClickedMountPoint); 293 if (stUsers != null && stUsers.length > 0) { 294 return true; 295 } 296 // TODO FIXME Parameterize with mountPoint and uncomment. 297 // On HC-MR2, no apps can be installed on sd and the emulated internal storage is not 298 // removable: application cannot interfere with unmount 299 /* 300 ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); 301 List<ApplicationInfo> list = am.getRunningExternalApplications(); 302 if (list != null && list.size() > 0) { 303 return true; 304 } 305 */ 306 // Better safe than sorry. Assume the storage is used to ask for confirmation. 307 return true; 308 } 309 310 private void unmount() { 311 // Check if external media is in use. 312 try { 313 if (hasAppsAccessingStorage()) { 314 // Present dialog to user 315 showDialogInner(DLG_CONFIRM_UNMOUNT); 316 } else { 317 doUnmount(); 318 } 319 } catch (RemoteException e) { 320 // Very unlikely. But present an error dialog anyway 321 Log.e(TAG, "Is MountService running?"); 322 showDialogInner(DLG_ERROR_UNMOUNT); 323 } 324 } 325 326 private void mount() { 327 IMountService mountService = getMountService(); 328 try { 329 if (mountService != null) { 330 mountService.mountVolume(mClickedMountPoint); 331 } else { 332 Log.e(TAG, "Mount service is null, can't mount"); 333 } 334 } catch (RemoteException ex) { 335 // Not much can be done 336 } 337 } 338 } 339