| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.deviceinfo; |
| |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.app.DialogFragment; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.IPackageDataObserver; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.hardware.usb.UsbManager; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.UserManager; |
| import android.os.storage.IMountService; |
| import android.os.storage.StorageEventListener; |
| import android.os.storage.StorageManager; |
| import android.os.storage.StorageVolume; |
| import android.preference.Preference; |
| import android.preference.PreferenceActivity; |
| import android.preference.PreferenceScreen; |
| import android.util.Log; |
| import android.view.Menu; |
| import android.view.MenuInflater; |
| import android.view.MenuItem; |
| import android.widget.Toast; |
| |
| import com.android.settings.R; |
| import com.android.settings.SettingsPreferenceFragment; |
| import com.android.settings.Utils; |
| import com.google.android.collect.Lists; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Panel showing storage usage on disk for known {@link StorageVolume} returned |
| * by {@link StorageManager}. Calculates and displays usage of data types. |
| */ |
| public class Memory extends SettingsPreferenceFragment { |
| private static final String TAG = "MemorySettings"; |
| |
| private static final String TAG_CONFIRM_CLEAR_CACHE = "confirmClearCache"; |
| |
| private static final int DLG_CONFIRM_UNMOUNT = 1; |
| private static final int DLG_ERROR_UNMOUNT = 2; |
| |
| // The mountToggle Preference that has last been clicked. |
| // Assumes no two successive unmount event on 2 different volumes are performed before the first |
| // one's preference is disabled |
| private static Preference sLastClickedMountToggle; |
| private static String sClickedMountPoint; |
| |
| // Access using getMountService() |
| private IMountService mMountService; |
| private StorageManager mStorageManager; |
| private UsbManager mUsbManager; |
| |
| private ArrayList<StorageVolumePreferenceCategory> mCategories = Lists.newArrayList(); |
| |
| @Override |
| public void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| final Context context = getActivity(); |
| |
| mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE); |
| |
| mStorageManager = StorageManager.from(context); |
| mStorageManager.registerListener(mStorageListener); |
| |
| addPreferencesFromResource(R.xml.device_info_memory); |
| |
| addCategory(StorageVolumePreferenceCategory.buildForInternal(context)); |
| |
| final StorageVolume[] storageVolumes = mStorageManager.getVolumeList(); |
| for (StorageVolume volume : storageVolumes) { |
| if (!volume.isEmulated()) { |
| addCategory(StorageVolumePreferenceCategory.buildForPhysical(context, volume)); |
| } |
| } |
| |
| setHasOptionsMenu(true); |
| } |
| |
| private void addCategory(StorageVolumePreferenceCategory category) { |
| mCategories.add(category); |
| getPreferenceScreen().addPreference(category); |
| category.init(); |
| } |
| |
| private boolean isMassStorageEnabled() { |
| // Mass storage is enabled if primary volume supports it |
| final StorageVolume[] volumes = mStorageManager.getVolumeList(); |
| final StorageVolume primary = StorageManager.getPrimaryVolume(volumes); |
| return primary != null && primary.allowMassStorage(); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED); |
| intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); |
| intentFilter.addDataScheme("file"); |
| getActivity().registerReceiver(mMediaScannerReceiver, intentFilter); |
| |
| intentFilter = new IntentFilter(); |
| intentFilter.addAction(UsbManager.ACTION_USB_STATE); |
| getActivity().registerReceiver(mMediaScannerReceiver, intentFilter); |
| |
| for (StorageVolumePreferenceCategory category : mCategories) { |
| category.onResume(); |
| } |
| } |
| |
| 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); |
| for (StorageVolumePreferenceCategory category : mCategories) { |
| final StorageVolume volume = category.getStorageVolume(); |
| if (volume != null && path.equals(volume.getPath())) { |
| category.onStorageStateChanged(); |
| break; |
| } |
| } |
| } |
| }; |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| getActivity().unregisterReceiver(mMediaScannerReceiver); |
| for (StorageVolumePreferenceCategory category : mCategories) { |
| category.onPause(); |
| } |
| } |
| |
| @Override |
| public void onDestroy() { |
| if (mStorageManager != null && mStorageListener != null) { |
| mStorageManager.unregisterListener(mStorageListener); |
| } |
| super.onDestroy(); |
| } |
| |
| @Override |
| public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { |
| inflater.inflate(R.menu.storage, menu); |
| } |
| |
| @Override |
| public void onPrepareOptionsMenu(Menu menu) { |
| final MenuItem usb = menu.findItem(R.id.storage_usb); |
| UserManager um = (UserManager)getActivity().getSystemService(Context.USER_SERVICE); |
| boolean usbItemVisible = !isMassStorageEnabled() |
| && !um.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER); |
| usb.setVisible(usbItemVisible); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| case R.id.storage_usb: |
| if (getActivity() instanceof PreferenceActivity) { |
| ((PreferenceActivity) getActivity()).startPreferencePanel( |
| UsbSettings.class.getCanonicalName(), |
| null, |
| R.string.storage_title_usb, null, |
| this, 0); |
| } else { |
| startFragment(this, UsbSettings.class.getCanonicalName(), -1, null); |
| } |
| return true; |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| private synchronized 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; |
| } |
| |
| @Override |
| public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { |
| if (StorageVolumePreferenceCategory.KEY_CACHE.equals(preference.getKey())) { |
| ConfirmClearCacheFragment.show(this); |
| return true; |
| } |
| |
| for (StorageVolumePreferenceCategory category : mCategories) { |
| Intent intent = category.intentForClick(preference); |
| if (intent != null) { |
| // Don't go across app boundary if monkey is running |
| if (!Utils.isMonkeyRunning()) { |
| startActivity(intent); |
| } |
| return true; |
| } |
| |
| final StorageVolume volume = category.getStorageVolume(); |
| if (volume != null && category.mountToggleClicked(preference)) { |
| sLastClickedMountToggle = preference; |
| sClickedMountPoint = volume.getPath(); |
| String state = mStorageManager.getVolumeState(volume.getPath()); |
| if (Environment.MEDIA_MOUNTED.equals(state) || |
| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { |
| unmount(); |
| } else { |
| mount(); |
| } |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private final BroadcastReceiver mMediaScannerReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (action.equals(UsbManager.ACTION_USB_STATE)) { |
| boolean isUsbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); |
| String usbFunction = mUsbManager.getDefaultFunction(); |
| for (StorageVolumePreferenceCategory category : mCategories) { |
| category.onUsbStateChanged(isUsbConnected, usbFunction); |
| } |
| } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { |
| for (StorageVolumePreferenceCategory category : mCategories) { |
| category.onMediaScannerFinished(); |
| } |
| } |
| } |
| }; |
| |
| @Override |
| public Dialog onCreateDialog(int id) { |
| switch (id) { |
| case DLG_CONFIRM_UNMOUNT: |
| return new AlertDialog.Builder(getActivity()) |
| .setTitle(R.string.dlg_confirm_unmount_title) |
| .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| doUnmount(); |
| }}) |
| .setNegativeButton(R.string.cancel, null) |
| .setMessage(R.string.dlg_confirm_unmount_text) |
| .create(); |
| case DLG_ERROR_UNMOUNT: |
| return new AlertDialog.Builder(getActivity()) |
| .setTitle(R.string.dlg_error_unmount_title) |
| .setNeutralButton(R.string.dlg_ok, null) |
| .setMessage(R.string.dlg_error_unmount_text) |
| .create(); |
| } |
| return null; |
| } |
| |
| private void doUnmount() { |
| // Present a toast here |
| Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show(); |
| IMountService mountService = getMountService(); |
| try { |
| sLastClickedMountToggle.setEnabled(false); |
| sLastClickedMountToggle.setTitle(getString(R.string.sd_ejecting_title)); |
| sLastClickedMountToggle.setSummary(getString(R.string.sd_ejecting_summary)); |
| mountService.unmountVolume(sClickedMountPoint, true, false); |
| } catch (RemoteException e) { |
| // Informative dialog to user that unmount failed. |
| showDialogInner(DLG_ERROR_UNMOUNT); |
| } |
| } |
| |
| private void showDialogInner(int id) { |
| removeDialog(id); |
| showDialog(id); |
| } |
| |
| private boolean hasAppsAccessingStorage() throws RemoteException { |
| IMountService mountService = getMountService(); |
| int stUsers[] = mountService.getStorageUsers(sClickedMountPoint); |
| if (stUsers != null && stUsers.length > 0) { |
| return true; |
| } |
| // TODO FIXME Parameterize with mountPoint and uncomment. |
| // On HC-MR2, no apps can be installed on sd and the emulated internal storage is not |
| // removable: application cannot interfere with unmount |
| /* |
| ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); |
| List<ApplicationInfo> list = am.getRunningExternalApplications(); |
| if (list != null && list.size() > 0) { |
| return true; |
| } |
| */ |
| // Better safe than sorry. Assume the storage is used to ask for confirmation. |
| return true; |
| } |
| |
| private void unmount() { |
| // Check if external media is in use. |
| try { |
| if (hasAppsAccessingStorage()) { |
| // Present dialog to user |
| showDialogInner(DLG_CONFIRM_UNMOUNT); |
| } else { |
| doUnmount(); |
| } |
| } catch (RemoteException e) { |
| // Very unlikely. But present an error dialog anyway |
| Log.e(TAG, "Is MountService running?"); |
| showDialogInner(DLG_ERROR_UNMOUNT); |
| } |
| } |
| |
| private void mount() { |
| IMountService mountService = getMountService(); |
| try { |
| if (mountService != null) { |
| mountService.mountVolume(sClickedMountPoint); |
| } else { |
| Log.e(TAG, "Mount service is null, can't mount"); |
| } |
| } catch (RemoteException ex) { |
| // Not much can be done |
| } |
| } |
| |
| private void onCacheCleared() { |
| for (StorageVolumePreferenceCategory category : mCategories) { |
| category.onCacheCleared(); |
| } |
| } |
| |
| private static class ClearCacheObserver extends IPackageDataObserver.Stub { |
| private final Memory mTarget; |
| private int mRemaining; |
| |
| public ClearCacheObserver(Memory target, int remaining) { |
| mTarget = target; |
| mRemaining = remaining; |
| } |
| |
| @Override |
| public void onRemoveCompleted(final String packageName, final boolean succeeded) { |
| synchronized (this) { |
| if (--mRemaining == 0) { |
| mTarget.onCacheCleared(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Dialog to request user confirmation before clearing all cache data. |
| */ |
| public static class ConfirmClearCacheFragment extends DialogFragment { |
| public static void show(Memory parent) { |
| if (!parent.isAdded()) return; |
| |
| final ConfirmClearCacheFragment dialog = new ConfirmClearCacheFragment(); |
| dialog.setTargetFragment(parent, 0); |
| dialog.show(parent.getFragmentManager(), TAG_CONFIRM_CLEAR_CACHE); |
| } |
| |
| @Override |
| public Dialog onCreateDialog(Bundle savedInstanceState) { |
| final Context context = getActivity(); |
| |
| final AlertDialog.Builder builder = new AlertDialog.Builder(context); |
| builder.setTitle(R.string.memory_clear_cache_title); |
| builder.setMessage(getString(R.string.memory_clear_cache_message)); |
| |
| builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| final Memory target = (Memory) getTargetFragment(); |
| final PackageManager pm = context.getPackageManager(); |
| final List<PackageInfo> infos = pm.getInstalledPackages(0); |
| final ClearCacheObserver observer = new ClearCacheObserver( |
| target, infos.size()); |
| for (PackageInfo info : infos) { |
| pm.deleteApplicationCacheFiles(info.packageName, observer); |
| } |
| } |
| }); |
| builder.setNegativeButton(android.R.string.cancel, null); |
| |
| return builder.create(); |
| } |
| } |
| } |