From 42833b2ff4d7a26dd9a609d2fd4436d9a26f28b5 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Sat, 11 Apr 2015 21:27:33 -0700 Subject: [PATCH] Checkpoint of new storage UI. Top-level storage UI now shows list of all devices, both internal and adopted/private volumes, and public/shared volumes. When viewing a private volume, show traditional clustering of data types, including summary of other users. For adopted volumes, any actions are tucked away in a menu, since they're not primary. Misc files browsing is now provided by DocumentsUI. Teach StorageMeasurement about new private volumes, including handling emulated volumes stacked above them. When measuring, only consider apps actually hosted on the current volume UUID. When viewing a public volume, we default to launching into file management mode, and offer a simple eject button at the top-level view. File management mode is offered by new DocumentsUI browse intent, and a Settings link there redirects back to us for actual operations like ejecting/formatting. When unmounted, we launch into our action view. Actions like ejecting/formatting just show simple toasts for now. Bug: 19993667 Change-Id: Ie990ef3c01fb3717aaf8c79bfc53aac7edefdcf7 --- AndroidManifest.xml | 22 +- res/layout/preference_storage_action.xml | 47 ++ res/menu/storage_volume.xml | 33 ++ res/values/strings.xml | 43 +- res/xml/dashboard_categories.xml | 2 +- res/xml/device_info_storage.xml | 31 ++ ...o_memory.xml => device_info_storage_volume.xml} | 15 +- src/com/android/settings/Settings.java | 1 + src/com/android/settings/SettingsActivity.java | 6 +- src/com/android/settings/deviceinfo/Memory.java | 509 ------------------- .../settings/deviceinfo/MiscFilesHandler.java | 286 ----------- .../settings/deviceinfo/PrivateVolumeSettings.java | 548 +++++++++++++++++++++ .../settings/deviceinfo/PublicVolumeSettings.java | 205 ++++++++ .../settings/deviceinfo/StorageItemPreference.java | 4 + .../settings/deviceinfo/StorageMeasurement.java | 302 ++++-------- .../settings/deviceinfo/StorageSettings.java | 421 ++++++++++++++++ .../deviceinfo/StorageVolumePreference.java | 89 ++++ .../StorageVolumePreferenceCategory.java | 483 ------------------ src/com/android/settings/search/Ranking.java | 4 +- .../settings/search/SearchIndexableResources.java | 8 +- 20 files changed, 1561 insertions(+), 1498 deletions(-) create mode 100644 res/layout/preference_storage_action.xml create mode 100644 res/menu/storage_volume.xml create mode 100644 res/xml/device_info_storage.xml rename res/xml/{device_info_memory.xml => device_info_storage_volume.xml} (64%) delete mode 100644 src/com/android/settings/deviceinfo/Memory.java delete mode 100644 src/com/android/settings/deviceinfo/MiscFilesHandler.java create mode 100644 src/com/android/settings/deviceinfo/PrivateVolumeSettings.java create mode 100644 src/com/android/settings/deviceinfo/PublicVolumeSettings.java create mode 100644 src/com/android/settings/deviceinfo/StorageSettings.java create mode 100644 src/com/android/settings/deviceinfo/StorageVolumePreference.java delete mode 100644 src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 248483102f..66a081d8ef 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1415,15 +1415,31 @@ + android:value="com.android.settings.deviceinfo.StorageSettings" /> - + + + + + + + + + diff --git a/res/layout/preference_storage_action.xml b/res/layout/preference_storage_action.xml new file mode 100644 index 0000000000..10f138b925 --- /dev/null +++ b/res/layout/preference_storage_action.xml @@ -0,0 +1,47 @@ + + + + + + + + + diff --git a/res/menu/storage_volume.xml b/res/menu/storage_volume.xml new file mode 100644 index 0000000000..093a4bb370 --- /dev/null +++ b/res/menu/storage_volume.xml @@ -0,0 +1,33 @@ + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index a8f7bb5855..a608410c05 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -593,6 +593,9 @@ Forget + + Save + Settings @@ -2185,7 +2188,7 @@ Calculating\u2026 - Apps (app data & media content) + Apps & app data Media @@ -2195,7 +2198,7 @@ Audio (music, ringtones, podcasts, etc.) - Misc. + Other files Cached data @@ -2270,6 +2273,16 @@ Some system functions, such as syncing, may not work correctly. Try to free space by deleting or unpinning items, such as apps or media content. + + Rename + + Mount + + Eject + + Erase & format + + Erase & format as internal storage USB computer connection @@ -2293,6 +2306,32 @@ Other users + + Device storage + + Removable storage + + + %1$s free (Total %2$s) + + + %1$s is mounted + + Couldn\'t mount %1$s + + + %1$s is safely ejected + + Couldn\'t safely eject %1$s + + + %1$s is formatted + + Couldn\'t format %1$s + + + Rename storage + Battery status diff --git a/res/xml/dashboard_categories.xml b/res/xml/dashboard_categories.xml index 0d8c1ac587..78fe0e79a0 100644 --- a/res/xml/dashboard_categories.xml +++ b/res/xml/dashboard_categories.xml @@ -106,7 +106,7 @@ diff --git a/res/xml/device_info_storage.xml b/res/xml/device_info_storage.xml new file mode 100644 index 0000000000..19c5982faa --- /dev/null +++ b/res/xml/device_info_storage.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/res/xml/device_info_memory.xml b/res/xml/device_info_storage_volume.xml similarity index 64% rename from res/xml/device_info_memory.xml rename to res/xml/device_info_storage_volume.xml index 38cc94a52b..35435d7cde 100644 --- a/res/xml/device_info_memory.xml +++ b/res/xml/device_info_storage_volume.xml @@ -4,9 +4,9 @@ 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. @@ -14,11 +14,10 @@ limitations under the License. --> - - - + diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index b58159e05d..fe0df59c7f 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -33,6 +33,7 @@ public class Settings extends SettingsActivity { public static class VpnSettingsActivity extends SettingsActivity { /* empty */ } public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ } public static class StorageSettingsActivity extends SettingsActivity { /* empty */ } + public static class StorageVolumeSettingsActivity extends SettingsActivity { /* empty */ } public static class WifiSettingsActivity extends SettingsActivity { /* empty */ } public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ } public static class InputMethodAndLanguageSettingsActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 24209b003a..7bfd249520 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -82,7 +82,8 @@ import com.android.settings.dashboard.DashboardSummary; import com.android.settings.dashboard.DashboardTile; import com.android.settings.dashboard.NoHomeDialogFragment; import com.android.settings.dashboard.SearchResultsSummary; -import com.android.settings.deviceinfo.Memory; +import com.android.settings.deviceinfo.PublicVolumeSettings; +import com.android.settings.deviceinfo.StorageSettings; import com.android.settings.deviceinfo.UsbSettings; import com.android.settings.fuelgauge.BatterySaverSettings; import com.android.settings.fuelgauge.PowerUsageSummary; @@ -305,7 +306,8 @@ public class SettingsActivity extends Activity CaptionPropertiesFragment.class.getName(), com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(), TextToSpeechSettings.class.getName(), - Memory.class.getName(), + StorageSettings.class.getName(), + PublicVolumeSettings.class.getName(), DevelopmentSettings.class.getName(), UsbSettings.class.getName(), AndroidBeam.class.getName(), diff --git a/src/com/android/settings/deviceinfo/Memory.java b/src/com/android/settings/deviceinfo/Memory.java deleted file mode 100644 index a07f7c2ccf..0000000000 --- a/src/com/android/settings/deviceinfo/Memory.java +++ /dev/null @@ -1,509 +0,0 @@ -/* - * 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.ActivityNotFoundException; -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.PreferenceScreen; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.widget.Toast; - -import com.android.internal.logging.MetricsLogger; -import com.android.settings.R; -import com.android.settings.SettingsActivity; -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.Utils; -import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settings.search.Indexable; -import com.android.settings.search.SearchIndexableRaw; -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 implements Indexable { - 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 mCategories = Lists.newArrayList(); - - @Override - protected int getMetricsCategory() { - return MetricsLogger.DEVICEINFO_MEMORY; - } - - @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 SettingsActivity) { - ((SettingsActivity) getActivity()).startPreferencePanel( - UsbSettings.class.getCanonicalName(), - null, R.string.storage_title_usb, null, this, 0); - } else { - startFragment(this, UsbSettings.class.getCanonicalName(), - R.string.storage_title_usb, -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()) { - try { - startActivity(intent); - } catch (ActivityNotFoundException anfe) { - Log.w(TAG, "No activity found for intent " + 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 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 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(); - } - } - - /** - * Enable indexing of searchable data - */ - public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider() { - @Override - public List getRawDataToIndex(Context context, boolean enabled) { - final List result = new ArrayList(); - - SearchIndexableRaw data = new SearchIndexableRaw(context); - data.title = context.getString(R.string.storage_settings); - data.screenTitle = context.getString(R.string.storage_settings); - result.add(data); - - data = new SearchIndexableRaw(context); - data.title = context.getString(R.string.internal_storage); - data.screenTitle = context.getString(R.string.storage_settings); - result.add(data); - - data = new SearchIndexableRaw(context); - final StorageVolume[] storageVolumes = StorageManager.from(context).getVolumeList(); - for (StorageVolume volume : storageVolumes) { - if (!volume.isEmulated()) { - data.title = volume.getDescription(context); - data.screenTitle = context.getString(R.string.storage_settings); - result.add(data); - } - } - - data = new SearchIndexableRaw(context); - data.title = context.getString(R.string.memory_size); - data.screenTitle = context.getString(R.string.storage_settings); - result.add(data); - - data = new SearchIndexableRaw(context); - data.title = context.getString(R.string.memory_available); - data.screenTitle = context.getString(R.string.storage_settings); - result.add(data); - - data = new SearchIndexableRaw(context); - data.title = context.getString(R.string.memory_apps_usage); - data.screenTitle = context.getString(R.string.storage_settings); - result.add(data); - - data = new SearchIndexableRaw(context); - data.title = context.getString(R.string.memory_dcim_usage); - data.screenTitle = context.getString(R.string.storage_settings); - result.add(data); - - data = new SearchIndexableRaw(context); - data.title = context.getString(R.string.memory_music_usage); - data.screenTitle = context.getString(R.string.storage_settings); - result.add(data); - - data = new SearchIndexableRaw(context); - data.title = context.getString(R.string.memory_downloads_usage); - data.screenTitle = context.getString(R.string.storage_settings); - result.add(data); - - data = new SearchIndexableRaw(context); - data.title = context.getString(R.string.memory_media_cache_usage); - data.screenTitle = context.getString(R.string.storage_settings); - result.add(data); - - data = new SearchIndexableRaw(context); - data.title = context.getString(R.string.memory_media_misc_usage); - data.screenTitle = context.getString(R.string.storage_settings); - result.add(data); - - return result; - } - }; - -} diff --git a/src/com/android/settings/deviceinfo/MiscFilesHandler.java b/src/com/android/settings/deviceinfo/MiscFilesHandler.java deleted file mode 100644 index 93e352b2e7..0000000000 --- a/src/com/android/settings/deviceinfo/MiscFilesHandler.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2010 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.Activity; -import android.app.ListActivity; -import android.content.Context; -import android.os.Bundle; -import android.os.storage.StorageVolume; -import android.text.format.Formatter; -import android.util.Log; -import android.util.SparseBooleanArray; -import android.view.ActionMode; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.ListView; - -import com.android.settings.R; -import com.android.settings.deviceinfo.StorageMeasurement.FileInfo; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * This class handles the selection and removal of Misc files. - */ -public class MiscFilesHandler extends ListActivity { - private static final String TAG = "MemorySettings"; - private String mNumSelectedFormat; - private String mNumBytesSelectedFormat; - private MemoryMearurementAdapter mAdapter; - private LayoutInflater mInflater; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setFinishOnTouchOutside(true); - setTitle(R.string.misc_files); - mNumSelectedFormat = getString(R.string.misc_files_selected_count); - mNumBytesSelectedFormat = getString(R.string.misc_files_selected_count_bytes); - mAdapter = new MemoryMearurementAdapter(this); - mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); - setContentView(R.layout.settings_storage_miscfiles_list); - ListView lv = getListView(); - lv.setItemsCanFocus(true); - lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); - lv.setMultiChoiceModeListener(new ModeCallback(this)); - setListAdapter(mAdapter); - } - - private class ModeCallback implements ListView.MultiChoiceModeListener { - private int mDataCount; - private final Context mContext; - - public ModeCallback(Context context) { - mContext = context; - mDataCount = mAdapter.getCount(); - } - - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - final MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.misc_files_menu, menu); - return true; - } - - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return true; - } - - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - ListView lv = getListView(); - switch (item.getItemId()) { - case R.id.action_delete: - // delete the files selected - SparseBooleanArray checkedItems = lv.getCheckedItemPositions(); - int checkedCount = getListView().getCheckedItemCount(); - if (checkedCount > mDataCount) { - throw new IllegalStateException("checked item counts do not match. " + - "checkedCount: " + checkedCount + ", dataSize: " + mDataCount); - } - if (mDataCount > 0) { - ArrayList toRemove = new ArrayList(); - for (int i = 0; i < mDataCount; i++) { - if (!checkedItems.get(i)) { - //item not selected - continue; - } - if (StorageMeasurement.LOGV) { - Log.i(TAG, "deleting: " + mAdapter.getItem(i)); - } - // delete the file - File file = new File(mAdapter.getItem(i).mFileName); - if (file.isDirectory()) { - deleteDir(file); - } else { - file.delete(); - } - toRemove.add(mAdapter.getItem(i)); - } - mAdapter.removeAll(toRemove); - mAdapter.notifyDataSetChanged(); - mDataCount = mAdapter.getCount(); - } - mode.finish(); - break; - - case R.id.action_select_all: - // check ALL items - for (int i = 0; i < mDataCount; i++) { - lv.setItemChecked(i, true); - } - // update the title and subtitle with number selected and numberBytes selected - onItemCheckedStateChanged(mode, 1, 0, true); - break; - } - return true; - } - - // Deletes all files and subdirectories under given dir. - // Returns true if all deletions were successful. - // If a deletion fails, the method stops attempting to delete and returns false. - private boolean deleteDir(File dir) { - String[] children = dir.list(); - if (children != null) { - for (int i=0; i < children.length; i++) { - boolean success = deleteDir(new File(dir, children[i])); - if (!success) { - return false; - } - } - } - // The directory is now empty so delete it - return dir.delete(); - } - - public void onDestroyActionMode(ActionMode mode) { - // This block intentionally left blank - } - - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { - ListView lv = getListView(); - int numChecked = lv.getCheckedItemCount(); - mode.setTitle(String.format(mNumSelectedFormat, numChecked, mAdapter.getCount())); - - // total the sizes of all items selected so far - SparseBooleanArray checkedItems = lv.getCheckedItemPositions(); - long selectedDataSize = 0; - if (numChecked > 0) { - for (int i = 0; i < mDataCount; i++) { - if (checkedItems.get(i)) { - // item is checked - selectedDataSize += mAdapter.getItem(i).mSize; - } - } - } - mode.setSubtitle(String.format(mNumBytesSelectedFormat, - Formatter.formatFileSize(mContext, selectedDataSize), - Formatter.formatFileSize(mContext, mAdapter.getDataSize()))); - } - } - - class MemoryMearurementAdapter extends BaseAdapter { - private ArrayList mData = null; - private long mDataSize = 0; - private Context mContext; - - public MemoryMearurementAdapter(Activity activity) { - mContext = activity; - final StorageVolume storageVolume = activity.getIntent().getParcelableExtra( - StorageVolume.EXTRA_STORAGE_VOLUME); - StorageMeasurement mMeasurement = StorageMeasurement.getInstance( - activity, storageVolume); - if (mMeasurement == null) return; - mData = (ArrayList) mMeasurement.mFileInfoForMisc; - if (mData != null) { - for (StorageMeasurement.FileInfo info : mData) { - mDataSize += info.mSize; - } - } - } - - @Override - public int getCount() { - return (mData == null) ? 0 : mData.size(); - } - - @Override - public StorageMeasurement.FileInfo getItem(int position) { - if (mData == null || mData.size() <= position) { - return null; - } - return mData.get(position); - } - - @Override - public long getItemId(int position) { - if (mData == null || mData.size() <= position) { - return 0; - } - return mData.get(position).mId; - } - - public void removeAll(List objs) { - if (mData == null) { - return; - } - for (Object o : objs) { - mData.remove(o); - mDataSize -= ((StorageMeasurement.FileInfo) o).mSize; - } - } - - public long getDataSize() { - return mDataSize; - } - - @Override - public void notifyDataSetChanged() { - super.notifyDataSetChanged(); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final FileItemInfoLayout view = (convertView == null) ? - (FileItemInfoLayout) mInflater.inflate(R.layout.settings_storage_miscfiles, - parent, false) : (FileItemInfoLayout) convertView; - FileInfo item = getItem(position); - view.setFileName(item.mFileName); - view.setFileSize(Formatter.formatFileSize(mContext, item.mSize)); - final ListView listView = (ListView) parent; - final int listPosition = position; - view.getCheckBox().setOnCheckedChangeListener(new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - listView.setItemChecked(listPosition, isChecked); - } - - }); - view.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (listView.getCheckedItemCount() > 0) { - return false; - } - listView.setItemChecked(listPosition, !view.isChecked()); - return true; - } - }); - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (listView.getCheckedItemCount() > 0) { - listView.setItemChecked(listPosition, !view.isChecked()); - } - } - }); - return view; - } - } -} diff --git a/src/com/android/settings/deviceinfo/PrivateVolumeSettings.java b/src/com/android/settings/deviceinfo/PrivateVolumeSettings.java new file mode 100644 index 0000000000..efb9a07d21 --- /dev/null +++ b/src/com/android/settings/deviceinfo/PrivateVolumeSettings.java @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2015 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 static com.android.settings.deviceinfo.StorageSettings.EXTRA_VOLUME_ID; +import static com.android.settings.deviceinfo.StorageSettings.TAG; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.DownloadManager; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.IPackageDataObserver; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.os.Bundle; +import android.os.Environment; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.storage.StorageEventListener; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.provider.MediaStore; +import android.text.TextUtils; +import android.text.format.Formatter; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.EditText; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.util.Preconditions; +import com.android.settings.R; +import com.android.settings.Settings; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.deviceinfo.StorageMeasurement.MeasurementDetails; +import com.android.settings.deviceinfo.StorageMeasurement.MeasurementReceiver; +import com.android.settings.deviceinfo.StorageSettings.FormatTask; +import com.android.settings.deviceinfo.StorageSettings.MountTask; +import com.android.settings.deviceinfo.StorageSettings.UnmountTask; +import com.google.android.collect.Lists; + +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +/** + * Panel showing summary and actions for a {@link VolumeInfo#TYPE_PRIVATE} + * storage volume. + */ +public class PrivateVolumeSettings extends SettingsPreferenceFragment { + // TODO: disable unmount when providing over MTP/PTP + + private static final String TAG_RENAME = "rename"; + private static final String TAG_CONFIRM_CLEAR_CACHE = "confirmClearCache"; + + private StorageManager mStorageManager; + private UserManager mUserManager; + + private VolumeInfo mVolume; + private VolumeInfo mSharedVolume; + + private StorageMeasurement mMeasure; + + private UserInfo mCurrentUser; + + private int mNextOrder = 0; + + private UsageBarPreference mGraph; + private StorageItemPreference mTotal; + private StorageItemPreference mAvailable; + private StorageItemPreference mApps; + private StorageItemPreference mDcim; + private StorageItemPreference mMusic; + private StorageItemPreference mDownloads; + private StorageItemPreference mCache; + private StorageItemPreference mMisc; + private List mUsers = Lists.newArrayList(); + + private long mTotalSize; + private long mAvailSize; + + @Override + protected int getMetricsCategory() { + return MetricsLogger.DEVICEINFO_STORAGE; + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + final Context context = getActivity(); + + mUserManager = context.getSystemService(UserManager.class); + mStorageManager = context.getSystemService(StorageManager.class); + + final String volId = getArguments().getString(EXTRA_VOLUME_ID); + mVolume = Preconditions.checkNotNull(mStorageManager.findVolumeById(volId)); + Preconditions.checkState(mVolume.type == VolumeInfo.TYPE_PRIVATE); + + addPreferencesFromResource(R.xml.device_info_storage_volume); + + // Find the emulated shared storage layered above this private volume + mSharedVolume = mStorageManager.findVolumeById( + mVolume.id.replace("private", "emulated")); + + mMeasure = new StorageMeasurement(context, mVolume, mSharedVolume); + mMeasure.setReceiver(mReceiver); + + mGraph = buildGraph(); + mTotal = buildItem(R.string.memory_size, 0); + mAvailable = buildItem(R.string.memory_available, R.color.memory_avail); + + mApps = buildItem(R.string.memory_apps_usage, R.color.memory_apps_usage); + mDcim = buildItem(R.string.memory_dcim_usage, R.color.memory_dcim); + mMusic = buildItem(R.string.memory_music_usage, R.color.memory_music); + mDownloads = buildItem(R.string.memory_downloads_usage, R.color.memory_downloads); + mCache = buildItem(R.string.memory_media_cache_usage, R.color.memory_cache); + mMisc = buildItem(R.string.memory_media_misc_usage, R.color.memory_misc); + + mCurrentUser = mUserManager.getUserInfo(UserHandle.myUserId()); + final List otherUsers = getUsersExcluding(mCurrentUser); + for (int i = 0; i < otherUsers.size(); i++) { + final UserInfo user = otherUsers.get(i); + final int colorRes = i % 2 == 0 ? R.color.memory_user_light + : R.color.memory_user_dark; + final StorageItemPreference userPref = new StorageItemPreference( + context, user.name, colorRes, user.id); + mUsers.add(userPref); + } + + setHasOptionsMenu(true); + } + + public void refresh() { + getActivity().setTitle(mStorageManager.getBestVolumeDescription(mVolume.id)); + + // Valid options may have changed + getFragmentManager().invalidateOptionsMenu(); + + final Context context = getActivity(); + final PreferenceScreen screen = getPreferenceScreen(); + + screen.removeAll(); + + if (mVolume.state != VolumeInfo.STATE_MOUNTED) { + return; + } + + screen.addPreference(mGraph); + screen.addPreference(mTotal); + screen.addPreference(mAvailable); + + final boolean showUsers = !mUsers.isEmpty(); + if (showUsers) { + screen.addPreference(new PreferenceHeader(context, mCurrentUser.name)); + } + + screen.addPreference(mApps); + screen.addPreference(mDcim); + screen.addPreference(mMusic); + screen.addPreference(mDownloads); + screen.addPreference(mCache); + screen.addPreference(mMisc); + + if (showUsers) { + screen.addPreference(new PreferenceHeader(context, R.string.storage_other_users)); + for (Preference pref : mUsers) { + screen.addPreference(pref); + } + } + + for (int i = 0; i < screen.getPreferenceCount(); i++) { + final Preference pref = screen.getPreference(i); + if (pref instanceof StorageItemPreference) { + ((StorageItemPreference) pref).setLoading(); + } + } + + final File file = new File(mVolume.path); + mTotalSize = file.getTotalSpace(); + mAvailSize = file.getFreeSpace(); + + mTotal.setSummary(Formatter.formatFileSize(context, mTotalSize)); + mAvailable.setSummary(Formatter.formatFileSize(context, mAvailSize)); + + mGraph.clear(); + mGraph.addEntry(0, (mTotalSize - mAvailSize) / (float) mTotalSize, + android.graphics.Color.GRAY); + mGraph.commit(); + + mMeasure.forceMeasure(); + } + + private UsageBarPreference buildGraph() { + final UsageBarPreference pref = new UsageBarPreference(getActivity()); + pref.setOrder(mNextOrder++); + return pref; + } + + private StorageItemPreference buildItem(int titleRes, int colorRes) { + final StorageItemPreference pref = new StorageItemPreference(getActivity(), titleRes, + colorRes); + pref.setOrder(mNextOrder++); + return pref; + } + + @Override + public void onResume() { + super.onResume(); + mStorageManager.registerListener(mStorageListener); + refresh(); + } + + @Override + public void onPause() { + super.onPause(); + mStorageManager.unregisterListener(mStorageListener); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mMeasure.onDestroy(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.storage_volume, menu); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + final MenuItem rename = menu.findItem(R.id.storage_rename); + final MenuItem mount = menu.findItem(R.id.storage_mount); + final MenuItem unmount = menu.findItem(R.id.storage_unmount); + final MenuItem format = menu.findItem(R.id.storage_format); + final MenuItem usb = menu.findItem(R.id.storage_usb); + + // Actions live in menu for non-internal private volumes; they're shown + // as preference items for public volumes. + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(mVolume.id)) { + rename.setVisible(false); + mount.setVisible(false); + unmount.setVisible(false); + format.setVisible(false); + } else { + rename.setVisible(mVolume.type == VolumeInfo.TYPE_PRIVATE); + mount.setVisible(mVolume.state == VolumeInfo.STATE_UNMOUNTED); + unmount.setVisible(mVolume.state == VolumeInfo.STATE_MOUNTED); + format.setVisible(true); + } + + // TODO: show usb if we jumped past first screen + usb.setVisible(false); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final Context context = getActivity(); + switch (item.getItemId()) { + case R.id.storage_rename: + RenameFragment.show(this); + return true; + case R.id.storage_mount: + new MountTask(context, mVolume.id).execute(); + return true; + case R.id.storage_unmount: + new UnmountTask(context, mVolume.id).execute(); + return true; + case R.id.storage_format: + new FormatTask(context, mVolume.id).execute(); + return true; + case R.id.storage_usb: + startFragment(this, UsbSettings.class.getCanonicalName(), + R.string.storage_title_usb, 0, null); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference pref) { + // TODO: launch better intents for specific volume + + Intent intent = null; + if (pref == mApps) { + intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE); + intent.setClass(getActivity(), Settings.ManageApplicationsActivity.class); + + } else if (pref == mDownloads) { + intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra( + DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true); + + } else if (pref == mMusic) { + intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("audio/mp3"); + + } else if (pref == mDcim) { + intent = new Intent(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + intent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + + } else if (pref == mCache) { + ConfirmClearCacheFragment.show(this); + return true; + + } else if (pref == mMisc) { + intent = StorageSettings.buildBrowseIntent(mSharedVolume); + } + + if (intent != null) { + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "No activity found for " + intent); + } + return true; + } + return super.onPreferenceTreeClick(preferenceScreen, pref); + } + + private final MeasurementReceiver mReceiver = new MeasurementReceiver() { + @Override + public void onDetailsChanged(MeasurementDetails details) { + updateDetails(details); + } + }; + + private void updateDetails(MeasurementDetails details) { + mGraph.clear(); + + updatePreference(mApps, details.appsSize); + + final long dcimSize = totalValues(details.mediaSize, Environment.DIRECTORY_DCIM, + Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES); + updatePreference(mDcim, dcimSize); + + final long musicSize = totalValues(details.mediaSize, Environment.DIRECTORY_MUSIC, + Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS, + Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS); + updatePreference(mMusic, musicSize); + + final long downloadsSize = totalValues(details.mediaSize, Environment.DIRECTORY_DOWNLOADS); + updatePreference(mDownloads, downloadsSize); + + updatePreference(mCache, details.cacheSize); + updatePreference(mMisc, details.miscSize); + + for (StorageItemPreference userPref : mUsers) { + final long userSize = details.usersSize.get(userPref.userHandle); + updatePreference(userPref, userSize); + } + + mGraph.commit(); + } + + private void updatePreference(StorageItemPreference pref, long size) { + pref.setSummary(Formatter.formatFileSize(getActivity(), size)); + if (size > 0) { + final int order = pref.getOrder(); + mGraph.addEntry(order, size / (float) mTotalSize, pref.color); + } + } + + /** + * Return list of other users, excluding the current user. + */ + private List getUsersExcluding(UserInfo excluding) { + final List users = mUserManager.getUsers(); + final Iterator i = users.iterator(); + while (i.hasNext()) { + if (i.next().id == excluding.id) { + i.remove(); + } + } + return users; + } + + private static long totalValues(HashMap map, String... keys) { + long total = 0; + for (String key : keys) { + if (map.containsKey(key)) { + total += map.get(key); + } + } + return total; + } + + private final StorageEventListener mStorageListener = new StorageEventListener() { + @Override + public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { + if (Objects.equals(mVolume.id, vol.id)) { + mVolume = vol; + refresh(); + } + } + }; + + /** + * Dialog that allows editing of volume nickname. + */ + public static class RenameFragment extends DialogFragment { + public static void show(PrivateVolumeSettings parent) { + if (!parent.isAdded()) return; + + final RenameFragment dialog = new RenameFragment(); + dialog.setTargetFragment(parent, 0); + dialog.setArguments(parent.getArguments()); + dialog.show(parent.getFragmentManager(), TAG_RENAME); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + final StorageManager storageManager = context.getSystemService(StorageManager.class); + + final String volId = getArguments().getString(EXTRA_VOLUME_ID); + final VolumeInfo vol = storageManager.findVolumeById(volId); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); + + final View view = dialogInflater.inflate(R.layout.dialog_edittext, null, false); + final EditText nickname = (EditText) view.findViewById(R.id.edittext); + + if (!TextUtils.isEmpty(vol.nickname)) { + nickname.setText(vol.nickname); + } else { + nickname.setText(storageManager.getBestVolumeDescription(volId)); + } + + builder.setTitle(R.string.storage_rename_title); + builder.setView(view); + + builder.setPositiveButton(R.string.save, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // TODO: persist the edited nickname! + } + }); + builder.setNegativeButton(R.string.cancel, null); + + return builder.create(); + } + } + + /** + * Dialog to request user confirmation before clearing all cache data. + */ + public static class ConfirmClearCacheFragment extends DialogFragment { + public static void show(PrivateVolumeSettings 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 PrivateVolumeSettings target = (PrivateVolumeSettings) getTargetFragment(); + final PackageManager pm = context.getPackageManager(); + final List 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(); + } + } + + private static class ClearCacheObserver extends IPackageDataObserver.Stub { + private final PrivateVolumeSettings mTarget; + private int mRemaining; + + public ClearCacheObserver(PrivateVolumeSettings target, int remaining) { + mTarget = target; + mRemaining = remaining; + } + + @Override + public void onRemoveCompleted(final String packageName, final boolean succeeded) { + synchronized (this) { + if (--mRemaining == 0) { + mTarget.refresh(); + } + } + } + } + + public static class PreferenceHeader extends Preference { + public PreferenceHeader(Context context, int titleRes) { + super(context, null, com.android.internal.R.attr.preferenceCategoryStyle); + setTitle(titleRes); + } + + public PreferenceHeader(Context context, CharSequence title) { + super(context, null, com.android.internal.R.attr.preferenceCategoryStyle); + setTitle(title); + } + + @Override + public boolean isEnabled() { + return false; + } + } +} diff --git a/src/com/android/settings/deviceinfo/PublicVolumeSettings.java b/src/com/android/settings/deviceinfo/PublicVolumeSettings.java new file mode 100644 index 0000000000..6edfc924c7 --- /dev/null +++ b/src/com/android/settings/deviceinfo/PublicVolumeSettings.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2015 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 static com.android.settings.deviceinfo.StorageSettings.EXTRA_VOLUME_ID; + +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.os.SystemProperties; +import android.os.storage.StorageEventListener; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.provider.DocumentsContract; +import android.text.format.Formatter; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.util.Preconditions; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.deviceinfo.StorageSettings.FormatTask; +import com.android.settings.deviceinfo.StorageSettings.MountTask; +import com.android.settings.deviceinfo.StorageSettings.UnmountTask; + +import java.io.File; +import java.util.Objects; + +/** + * Panel showing summary and actions for a {@link VolumeInfo#TYPE_PUBLIC} + * storage volume. + */ +public class PublicVolumeSettings extends SettingsPreferenceFragment { + // TODO: disable unmount when providing over MTP/PTP + + private static final String PREF_FORMAT_INTERNAL = "debug.format_internal"; + + private StorageManager mStorageManager; + + private VolumeInfo mVolume; + + private int mNextOrder = 0; + + private UsageBarPreference mGraph; + private StorageItemPreference mTotal; + private StorageItemPreference mAvailable; + + private Preference mMount; + private Preference mUnmount; + private Preference mFormat; + private Preference mFormatInternal; + + private long mTotalSize; + private long mAvailSize; + + @Override + protected int getMetricsCategory() { + return MetricsLogger.DEVICEINFO_STORAGE; + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + final Context context = getActivity(); + + mStorageManager = context.getSystemService(StorageManager.class); + + if (DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS.equals( + getActivity().getIntent().getAction())) { + final Uri rootUri = getActivity().getIntent().getData(); + final String fsUuid = DocumentsContract.getRootId(rootUri); + mVolume = mStorageManager.findVolumeByUuid(fsUuid); + } else { + final String volId = getArguments().getString(EXTRA_VOLUME_ID); + mVolume = mStorageManager.findVolumeById(volId); + } + + Preconditions.checkNotNull(mVolume); + Preconditions.checkState(mVolume.type == VolumeInfo.TYPE_PUBLIC); + + addPreferencesFromResource(R.xml.device_info_storage_volume); + + mGraph = buildGraph(); + mTotal = buildItem(R.string.memory_size, 0); + mAvailable = buildItem(R.string.memory_available, R.color.memory_avail); + + mMount = buildAction(R.string.storage_menu_mount); + mUnmount = buildAction(R.string.storage_menu_unmount); + mFormat = buildAction(R.string.storage_menu_format); + mFormatInternal = buildAction(R.string.storage_menu_format_internal); + } + + public void refresh() { + getActivity().setTitle(mStorageManager.getBestVolumeDescription(mVolume.id)); + + final Context context = getActivity(); + final PreferenceScreen screen = getPreferenceScreen(); + + screen.removeAll(); + + if (mVolume.state == VolumeInfo.STATE_MOUNTED) { + screen.addPreference(mGraph); + screen.addPreference(mTotal); + screen.addPreference(mAvailable); + } + + if (mVolume.state == VolumeInfo.STATE_UNMOUNTED) { + screen.addPreference(mMount); + } + if (mVolume.state == VolumeInfo.STATE_MOUNTED) { + screen.addPreference(mUnmount); + } + screen.addPreference(mFormat); + if (SystemProperties.getBoolean(PREF_FORMAT_INTERNAL, false)) { + screen.addPreference(mFormatInternal); + } + + final File file = new File(mVolume.path); + mTotalSize = file.getTotalSpace(); + mAvailSize = file.getFreeSpace(); + + mTotal.setSummary(Formatter.formatFileSize(context, mTotalSize)); + mAvailable.setSummary(Formatter.formatFileSize(context, mAvailSize)); + + mGraph.clear(); + mGraph.addEntry(0, (mTotalSize - mAvailSize) / (float) mTotalSize, + android.graphics.Color.GRAY); + mGraph.commit(); + } + + private UsageBarPreference buildGraph() { + final UsageBarPreference pref = new UsageBarPreference(getActivity()); + pref.setOrder(mNextOrder++); + return pref; + } + + private StorageItemPreference buildItem(int titleRes, int colorRes) { + final StorageItemPreference pref = new StorageItemPreference(getActivity(), titleRes, + colorRes); + pref.setOrder(mNextOrder++); + return pref; + } + + private Preference buildAction(int titleRes) { + final Preference pref = new Preference(getActivity()); + pref.setTitle(titleRes); + pref.setOrder(mNextOrder++); + return pref; + } + + @Override + public void onResume() { + super.onResume(); + mStorageManager.registerListener(mStorageListener); + refresh(); + } + + @Override + public void onPause() { + super.onPause(); + mStorageManager.unregisterListener(mStorageListener); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference pref) { + final Context context = getActivity(); + if (pref == mMount) { + new MountTask(context, mVolume.id).execute(); + } else if (pref == mUnmount) { + new UnmountTask(context, mVolume.id).execute(); + } else if (pref == mFormat) { + new FormatTask(context, mVolume.id).execute(); + } else if (pref == mFormatInternal) { + // TODO: implement this + } + + return super.onPreferenceTreeClick(preferenceScreen, pref); + } + + private final StorageEventListener mStorageListener = new StorageEventListener() { + @Override + public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { + if (Objects.equals(mVolume.id, vol.id)) { + mVolume = vol; + refresh(); + } + } + }; +} diff --git a/src/com/android/settings/deviceinfo/StorageItemPreference.java b/src/com/android/settings/deviceinfo/StorageItemPreference.java index 87e827e713..8d48cf09f6 100644 --- a/src/com/android/settings/deviceinfo/StorageItemPreference.java +++ b/src/com/android/settings/deviceinfo/StorageItemPreference.java @@ -62,4 +62,8 @@ public class StorageItemPreference extends Preference { shape.getPaint().setColor(color); return shape; } + + public void setLoading() { + setSummary(R.string.memory_calculating_size); + } } diff --git a/src/com/android/settings/deviceinfo/StorageMeasurement.java b/src/com/android/settings/deviceinfo/StorageMeasurement.java index 34ef62b17a..db91fdb1de 100644 --- a/src/com/android/settings/deviceinfo/StorageMeasurement.java +++ b/src/com/android/settings/deviceinfo/StorageMeasurement.java @@ -27,7 +27,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageStats; import android.content.pm.UserInfo; import android.os.Environment; -import android.os.Environment.UserEnvironment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -36,23 +35,22 @@ import android.os.Message; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageVolume; +import android.os.storage.VolumeInfo; import android.util.Log; import android.util.SparseLongArray; import com.android.internal.app.IMediaContainerService; -import com.google.android.collect.Maps; +import com.android.internal.util.ArrayUtils; import com.google.android.collect.Sets; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Objects; import java.util.Set; -import javax.annotation.concurrent.GuardedBy; - /** * Utility for measuring the disk usage of internal storage or a physical * {@link StorageVolume}. Connects with a remote {@link IMediaContainerService} @@ -77,28 +75,7 @@ public class StorageMeasurement { Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS, Environment.DIRECTORY_DOWNLOADS, Environment.DIRECTORY_ANDROID); - @GuardedBy("sInstances") - private static HashMap sInstances = Maps.newHashMap(); - - /** - * Obtain shared instance of {@link StorageMeasurement} for given physical - * {@link StorageVolume}, or internal storage if {@code null}. - */ - public static StorageMeasurement getInstance(Context context, StorageVolume volume) { - synchronized (sInstances) { - StorageMeasurement value = sInstances.get(volume); - if (value == null) { - value = new StorageMeasurement(context.getApplicationContext(), volume); - sInstances.put(volume, value); - } - return value; - } - } - public static class MeasurementDetails { - public long totalSize; - public long availSize; - /** * Total apps disk usage. *

@@ -128,7 +105,7 @@ public class StorageMeasurement { * When measuring a physical {@link StorageVolume}, this reflects media * on that volume. */ - public HashMap mediaSize = Maps.newHashMap(); + public HashMap mediaSize = new HashMap<>(); /** * Misc external disk usage for the current user, unaccounted in @@ -144,34 +121,31 @@ public class StorageMeasurement { } public interface MeasurementReceiver { - public void updateApproximate(StorageMeasurement meas, long totalSize, long availSize); - public void updateDetails(StorageMeasurement meas, MeasurementDetails details); + public void onDetailsChanged(MeasurementDetails details); } - private volatile WeakReference mReceiver; - - /** Physical volume being measured, or {@code null} for internal. */ - private final StorageVolume mVolume; + private WeakReference mReceiver; - private final boolean mIsInternal; - private final boolean mIsPrimary; + private final Context mContext; - private final MeasurementHandler mHandler; + private final VolumeInfo mVolume; + private final VolumeInfo mSharedVolume; - private long mTotalSize; - private long mAvailSize; + private final MainHandler mMainHandler; + private final MeasurementHandler mMeasurementHandler; - List mFileInfoForMisc; + public StorageMeasurement(Context context, VolumeInfo volume, VolumeInfo sharedVolume) { + mContext = context.getApplicationContext(); - private StorageMeasurement(Context context, StorageVolume volume) { mVolume = volume; - mIsInternal = volume == null; - mIsPrimary = volume != null ? volume.isPrimary() : false; + mSharedVolume = sharedVolume; // Start the thread that will measure the disk usage. final HandlerThread handlerThread = new HandlerThread("MemoryMeasurement"); handlerThread.start(); - mHandler = new MeasurementHandler(context, handlerThread.getLooper()); + + mMainHandler = new MainHandler(); + mMeasurementHandler = new MeasurementHandler(handlerThread.getLooper()); } public void setReceiver(MeasurementReceiver receiver) { @@ -180,52 +154,38 @@ public class StorageMeasurement { } } + public void forceMeasure() { + invalidate(); + measure(); + } + public void measure() { - if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE)) { - mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE); + if (!mMeasurementHandler.hasMessages(MeasurementHandler.MSG_MEASURE)) { + mMeasurementHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE); } } - public void cleanUp() { + public void onDestroy() { mReceiver = null; - mHandler.removeMessages(MeasurementHandler.MSG_MEASURE); - mHandler.sendEmptyMessage(MeasurementHandler.MSG_DISCONNECT); - } - - public void invalidate() { - mHandler.sendEmptyMessage(MeasurementHandler.MSG_INVALIDATE); + mMeasurementHandler.removeMessages(MeasurementHandler.MSG_MEASURE); + mMeasurementHandler.sendEmptyMessage(MeasurementHandler.MSG_DISCONNECT); } - private void sendInternalApproximateUpdate() { - MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null; - if (receiver == null) { - return; - } - receiver.updateApproximate(this, mTotalSize, mAvailSize); - } - - private void sendExactUpdate(MeasurementDetails details) { - MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null; - if (receiver == null) { - if (LOGV) { - Log.i(TAG, "measurements dropped because receiver is null! wasted effort"); - } - return; - } - receiver.updateDetails(this, details); + private void invalidate() { + mMeasurementHandler.sendEmptyMessage(MeasurementHandler.MSG_INVALIDATE); } private static class StatsObserver extends IPackageStatsObserver.Stub { - private final boolean mIsInternal; + private final boolean mIsPrivate; private final MeasurementDetails mDetails; private final int mCurrentUser; private final Message mFinished; private int mRemaining; - public StatsObserver(boolean isInternal, MeasurementDetails details, int currentUser, + public StatsObserver(boolean isPrivate, MeasurementDetails details, int currentUser, Message finished, int remaining) { - mIsInternal = isInternal; + mIsPrivate = isPrivate; mDetails = details; mCurrentUser = currentUser; mFinished = finished; @@ -245,7 +205,7 @@ public class StorageMeasurement { } private void addStatsLocked(PackageStats stats) { - if (mIsInternal) { + if (mIsPrivate) { long codeSize = stats.codeSize; long dataSize = stats.dataSize; long cacheSize = stats.cacheSize; @@ -279,6 +239,17 @@ public class StorageMeasurement { } } + private class MainHandler extends Handler { + @Override + public void handleMessage(Message msg) { + final MeasurementDetails details = (MeasurementDetails) msg.obj; + final MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null; + if (receiver != null) { + receiver.onDetailsChanged(details); + } + } + } + private class MeasurementHandler extends Handler { public static final int MSG_MEASURE = 1; public static final int MSG_CONNECTED = 2; @@ -294,8 +265,6 @@ public class StorageMeasurement { private MeasurementDetails mCached; - private final WeakReference mContext; - private final ServiceConnection mDefContainerConn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { @@ -313,9 +282,8 @@ public class StorageMeasurement { } }; - public MeasurementHandler(Context context, Looper looper) { + public MeasurementHandler(Looper looper) { super(looper); - mContext = new WeakReference(context); } @Override @@ -323,50 +291,39 @@ public class StorageMeasurement { switch (msg.what) { case MSG_MEASURE: { if (mCached != null) { - sendExactUpdate(mCached); + mMainHandler.obtainMessage(0, mCached).sendToTarget(); break; } - final Context context = (mContext != null) ? mContext.get() : null; - if (context == null) { - return; - } - synchronized (mLock) { if (mBound) { removeMessages(MSG_DISCONNECT); sendMessage(obtainMessage(MSG_CONNECTED, mDefaultContainer)); } else { Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); - context.bindServiceAsUser(service, mDefContainerConn, Context.BIND_AUTO_CREATE, - UserHandle.OWNER); + mContext.bindServiceAsUser(service, mDefContainerConn, + Context.BIND_AUTO_CREATE, UserHandle.OWNER); } } break; } case MSG_CONNECTED: { - IMediaContainerService imcs = (IMediaContainerService) msg.obj; - measureApproximateStorage(imcs); + final IMediaContainerService imcs = (IMediaContainerService) msg.obj; measureExactStorage(imcs); break; } case MSG_DISCONNECT: { synchronized (mLock) { if (mBound) { - final Context context = (mContext != null) ? mContext.get() : null; - if (context == null) { - return; - } - mBound = false; - context.unbindService(mDefContainerConn); + mContext.unbindService(mDefContainerConn); } } break; } case MSG_COMPLETED: { mCached = (MeasurementDetails) msg.obj; - sendExactUpdate(mCached); + mMainHandler.obtainMessage(0, mCached).sendToTarget(); break; } case MSG_INVALIDATE: { @@ -375,87 +332,74 @@ public class StorageMeasurement { } } } + } - private void measureApproximateStorage(IMediaContainerService imcs) { - final String path = mVolume != null ? mVolume.getPath() - : Environment.getDataDirectory().getPath(); - try { - final long[] stats = imcs.getFileSystemStats(path); - mTotalSize = stats[0]; - mAvailSize = stats[1]; - } catch (Exception e) { - Log.w(TAG, "Problem in container service", e); - } - - sendInternalApproximateUpdate(); - } - - private void measureExactStorage(IMediaContainerService imcs) { - final Context context = mContext != null ? mContext.get() : null; - if (context == null) { - return; - } - - final MeasurementDetails details = new MeasurementDetails(); - final Message finished = obtainMessage(MSG_COMPLETED, details); + private void measureExactStorage(IMediaContainerService imcs) { + final UserManager userManager = mContext.getSystemService(UserManager.class); + final PackageManager packageManager = mContext.getPackageManager(); - details.totalSize = mTotalSize; - details.availSize = mAvailSize; + final List users = userManager.getUsers(); + final int currentUser = ActivityManager.getCurrentUser(); - final UserManager userManager = (UserManager) context.getSystemService( - Context.USER_SERVICE); - final List users = userManager.getUsers(); + final MeasurementDetails details = new MeasurementDetails(); + final Message finished = mMeasurementHandler.obtainMessage(MeasurementHandler.MSG_COMPLETED, + details); - final int currentUser = ActivityManager.getCurrentUser(); - final UserEnvironment currentEnv = new UserEnvironment(currentUser); + if (mSharedVolume != null && mSharedVolume.state == VolumeInfo.STATE_MOUNTED) { + final File basePath = mSharedVolume.getPathForUser(currentUser); // Measure media types for emulated storage, or for primary physical // external volume - final boolean measureMedia = (mIsInternal && Environment.isExternalStorageEmulated()) - || mIsPrimary; - if (measureMedia) { - for (String type : sMeasureMediaTypes) { - final File path = currentEnv.getExternalStoragePublicDirectory(type); - final long size = getDirectorySize(imcs, path); - details.mediaSize.put(type, size); - } + for (String type : sMeasureMediaTypes) { + final File path = new File(basePath, type); + final long size = getDirectorySize(imcs, path); + details.mediaSize.put(type, size); } // Measure misc files not counted under media - if (measureMedia) { - final File path = mIsInternal ? currentEnv.getExternalStorageDirectory() - : mVolume.getPathFile(); - details.miscSize = measureMisc(imcs, path); - } + details.miscSize = measureMisc(imcs, basePath); - // Measure total emulated storage of all users; internal apps data - // will be spliced in later - for (UserInfo user : users) { - final UserEnvironment userEnv = new UserEnvironment(user.id); - final long size = getDirectorySize(imcs, userEnv.getExternalStorageDirectory()); - addValue(details.usersSize, user.id, size); + if (mSharedVolume.type == VolumeInfo.TYPE_EMULATED) { + // Measure total emulated storage of all users; internal apps data + // will be spliced in later + for (UserInfo user : users) { + final File userPath = mSharedVolume.getPathForUser(user.id); + final long size = getDirectorySize(imcs, userPath); + addValue(details.usersSize, user.id, size); + } } + } - // Measure all apps for all users - final PackageManager pm = context.getPackageManager(); - if (mIsInternal || mIsPrimary) { - final List apps = pm.getInstalledApplications( - PackageManager.GET_UNINSTALLED_PACKAGES - | PackageManager.GET_DISABLED_COMPONENTS); - - final int count = users.size() * apps.size(); - final StatsObserver observer = new StatsObserver( - mIsInternal, details, currentUser, finished, count); + // Measure all apps hosted on this volume for all users + if (mVolume.type == VolumeInfo.TYPE_PRIVATE) { + final List apps = packageManager.getInstalledApplications( + PackageManager.GET_UNINSTALLED_PACKAGES + | PackageManager.GET_DISABLED_COMPONENTS); - for (UserInfo user : users) { - for (ApplicationInfo app : apps) { - pm.getPackageSizeInfo(app.packageName, user.id, observer); - } + final List volumeApps = new ArrayList<>(); + for (ApplicationInfo app : apps) { + if (Objects.equals(app.volumeUuid, mVolume.fsUuid)) { + volumeApps.add(app); } + } - } else { + final int count = users.size() * volumeApps.size(); + if (count == 0) { finished.sendToTarget(); + return; + } + + final StatsObserver observer = new StatsObserver( + true, details, currentUser, finished, count); + for (UserInfo user : users) { + for (ApplicationInfo app : volumeApps) { + packageManager.getPackageSizeInfo(app.packageName, user.id, observer); + } } + + } else { + finished.sendToTarget(); + return; } } @@ -471,64 +415,26 @@ public class StorageMeasurement { } private long measureMisc(IMediaContainerService imcs, File dir) { - mFileInfoForMisc = new ArrayList(); - final File[] files = dir.listFiles(); - if (files == null) return 0; + if (ArrayUtils.isEmpty(files)) return 0; // Get sizes of all top level nodes except the ones already computed - long counter = 0; long miscSize = 0; - for (File file : files) { - final String path = file.getAbsolutePath(); final String name = file.getName(); if (sMeasureMediaTypes.contains(name)) { continue; } if (file.isFile()) { - final long fileSize = file.length(); - mFileInfoForMisc.add(new FileInfo(path, fileSize, counter++)); - miscSize += fileSize; + miscSize += file.length(); } else if (file.isDirectory()) { - final long dirSize = getDirectorySize(imcs, file); - mFileInfoForMisc.add(new FileInfo(path, dirSize, counter++)); - miscSize += dirSize; - } else { - // Non directory, non file: not listed + miscSize += getDirectorySize(imcs, file); } } - - // sort the list of FileInfo objects collected above in descending order of their sizes - Collections.sort(mFileInfoForMisc); - return miscSize; } - static class FileInfo implements Comparable { - final String mFileName; - final long mSize; - final long mId; - - FileInfo(String fileName, long size, long id) { - mFileName = fileName; - mSize = size; - mId = id; - } - - @Override - public int compareTo(FileInfo that) { - if (this == that || mSize == that.mSize) return 0; - else return (mSize < that.mSize) ? 1 : -1; // for descending sort - } - - @Override - public String toString() { - return mFileName + " : " + mSize + ", id:" + mId; - } - } - private static void addValue(SparseLongArray array, int key, long value) { array.put(key, array.get(key) + value); } diff --git a/src/com/android/settings/deviceinfo/StorageSettings.java b/src/com/android/settings/deviceinfo/StorageSettings.java new file mode 100644 index 0000000000..001f00d896 --- /dev/null +++ b/src/com/android/settings/deviceinfo/StorageSettings.java @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2015 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.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.UserManager; +import android.os.storage.StorageEventListener; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.preference.PreferenceScreen; +import android.provider.DocumentsContract; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.Toast; + +import com.android.internal.logging.MetricsLogger; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settings.search.SearchIndexableRaw; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Panel showing both internal storage (both built-in storage and private + * volumes) and removable storage (public volumes). + */ +public class StorageSettings extends SettingsPreferenceFragment implements Indexable { + static final String TAG = "StorageSettings"; + + // TODO: badging to indicate devices running low on storage + // TODO: show currently ejected private volumes + + public static final String EXTRA_VOLUME_ID = "volume_id"; + + private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents"; + private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary"; + + /** + * Build an intent to browse the contents of given {@link VolumeInfo}. + */ + public static Intent buildBrowseIntent(VolumeInfo vol) { + final Uri uri; + if (vol.type == VolumeInfo.TYPE_PUBLIC) { + uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, vol.fsUuid); + } else if (VolumeInfo.ID_EMULATED_INTERNAL.equals(vol.id)) { + uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, + DOCUMENT_ROOT_PRIMARY_EMULATED); + } else if (vol.type == VolumeInfo.TYPE_EMULATED) { + // TODO: build intent once supported + uri = null; + } else { + throw new IllegalArgumentException(); + } + + final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setData(uri); + return intent; + } + + private UserManager mUserManager; + private StorageManager mStorageManager; + + private PreferenceCategory mInternalCategory; + private PreferenceCategory mExternalCategory; + + @Override + protected int getMetricsCategory() { + return MetricsLogger.DEVICEINFO_STORAGE; + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + final Context context = getActivity(); + + mUserManager = context.getSystemService(UserManager.class); + + mStorageManager = context.getSystemService(StorageManager.class); + mStorageManager.registerListener(mStorageListener); + + addPreferencesFromResource(R.xml.device_info_storage); + + mInternalCategory = (PreferenceCategory) findPreference("storage_internal"); + mExternalCategory = (PreferenceCategory) findPreference("storage_external"); + + // TODO: if only one volume visible, shortcut into it + + setHasOptionsMenu(true); + } + + private static final Comparator sVolumeComparator = new Comparator() { + @Override + public int compare(VolumeInfo lhs, VolumeInfo rhs) { + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.id)) { + return -1; + } else if (lhs.getDescription() == null) { + return 1; + } else { + return lhs.getDescription().compareTo(rhs.getDescription()); + } + } + }; + + private final StorageEventListener mStorageListener = new StorageEventListener() { + @Override + public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { + if (isInteresting(vol)) { + refresh(); + } + } + }; + + private static boolean isInteresting(VolumeInfo vol) { + return vol.type == VolumeInfo.TYPE_PRIVATE || vol.type == VolumeInfo.TYPE_PUBLIC; + } + + private void refresh() { + final Context context = getActivity(); + + getPreferenceScreen().removeAll(); + mInternalCategory.removeAll(); + mExternalCategory.removeAll(); + + final List volumes = mStorageManager.getVolumes(); + Collections.sort(volumes, sVolumeComparator); + + for (VolumeInfo vol : volumes) { + if (vol.type == VolumeInfo.TYPE_PRIVATE) { + mInternalCategory.addPreference(new StorageVolumePreference(context, vol)); + } else if (vol.type == VolumeInfo.TYPE_PUBLIC) { + mExternalCategory.addPreference(new StorageVolumePreference(context, vol)); + } + } + + if (mInternalCategory.getPreferenceCount() > 0) { + getPreferenceScreen().addPreference(mInternalCategory); + } + if (mExternalCategory.getPreferenceCount() > 0) { + getPreferenceScreen().addPreference(mExternalCategory); + } + } + + @Override + public void onResume() { + super.onResume(); + mStorageManager.registerListener(mStorageListener); + refresh(); + } + + @Override + public void onPause() { + super.onPause(); + mStorageManager.unregisterListener(mStorageListener); + } + + @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); + + usb.setVisible(!mUserManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.storage_usb: + startFragment(this, UsbSettings.class.getCanonicalName(), + R.string.storage_title_usb, 0, null); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference pref) { + final String volId = pref.getKey(); + final VolumeInfo vol = mStorageManager.findVolumeById(volId); + if (vol == null) { + return false; + + } else if (vol.type == VolumeInfo.TYPE_PRIVATE) { + final Bundle args = new Bundle(); + args.putString(EXTRA_VOLUME_ID, volId); + startFragment(this, PrivateVolumeSettings.class.getCanonicalName(), + -1, 0, args); + return true; + + } else if (vol.type == VolumeInfo.TYPE_PUBLIC) { + if (vol.state == VolumeInfo.STATE_MOUNTED) { + final Intent intent = buildBrowseIntent(vol); + startActivity(intent); + return true; + } else { + final Bundle args = new Bundle(); + args.putString(EXTRA_VOLUME_ID, volId); + startFragment(this, PublicVolumeSettings.class.getCanonicalName(), + -1, 0, args); + return true; + } + } + + return false; + } + + public static class MountTask extends AsyncTask { + private final Context mContext; + private final StorageManager mStorageManager; + private final String mVolumeId; + private final String mDescription; + + public MountTask(Context context, String volumeId) { + mContext = context.getApplicationContext(); + mStorageManager = mContext.getSystemService(StorageManager.class); + mVolumeId = volumeId; + mDescription = mStorageManager.getBestVolumeDescription(mVolumeId); + } + + @Override + protected Exception doInBackground(Void... params) { + try { + mStorageManager.mount(mVolumeId); + return null; + } catch (Exception e) { + return e; + } + } + + @Override + protected void onPostExecute(Exception e) { + if (e == null) { + Toast.makeText(mContext, mContext.getString(R.string.storage_mount_success, + mDescription), Toast.LENGTH_SHORT).show(); + } else { + Log.e(TAG, "Failed to mount " + mVolumeId, e); + Toast.makeText(mContext, mContext.getString(R.string.storage_mount_failure, + mDescription), Toast.LENGTH_SHORT).show(); + } + } + } + + public static class UnmountTask extends AsyncTask { + private final Context mContext; + private final StorageManager mStorageManager; + private final String mVolumeId; + private final String mDescription; + + public UnmountTask(Context context, String volumeId) { + mContext = context.getApplicationContext(); + mStorageManager = mContext.getSystemService(StorageManager.class); + mVolumeId = volumeId; + mDescription = mStorageManager.getBestVolumeDescription(mVolumeId); + } + + @Override + protected Exception doInBackground(Void... params) { + try { + mStorageManager.unmount(mVolumeId); + return null; + } catch (Exception e) { + return e; + } + } + + @Override + protected void onPostExecute(Exception e) { + if (e == null) { + Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_success, + mDescription), Toast.LENGTH_SHORT).show(); + } else { + Log.e(TAG, "Failed to unmount " + mVolumeId, e); + Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_failure, + mDescription), Toast.LENGTH_SHORT).show(); + } + } + } + + public static class FormatTask extends AsyncTask { + private final Context mContext; + private final StorageManager mStorageManager; + private final String mVolumeId; + private final String mDescription; + + public FormatTask(Context context, String volumeId) { + mContext = context.getApplicationContext(); + mStorageManager = mContext.getSystemService(StorageManager.class); + mVolumeId = volumeId; + mDescription = mStorageManager.getBestVolumeDescription(mVolumeId); + } + + @Override + protected Exception doInBackground(Void... params) { + try { + mStorageManager.format(mVolumeId); + mStorageManager.mount(mVolumeId); + return null; + } catch (Exception e) { + return e; + } + } + + @Override + protected void onPostExecute(Exception e) { + if (e == null) { + Toast.makeText(mContext, mContext.getString(R.string.storage_format_success, + mDescription), Toast.LENGTH_SHORT).show(); + } else { + Log.e(TAG, "Failed to format " + mVolumeId, e); + Toast.makeText(mContext, mContext.getString(R.string.storage_format_failure, + mDescription), Toast.LENGTH_SHORT).show(); + } + } + } + + /** + * Enable indexing of searchable data + */ + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getRawDataToIndex(Context context, boolean enabled) { + final List result = new ArrayList(); + + SearchIndexableRaw data = new SearchIndexableRaw(context); + data.title = context.getString(R.string.storage_settings); + data.screenTitle = context.getString(R.string.storage_settings); + result.add(data); + + data = new SearchIndexableRaw(context); + data.title = context.getString(R.string.internal_storage); + data.screenTitle = context.getString(R.string.storage_settings); + result.add(data); + + data = new SearchIndexableRaw(context); + final StorageManager storage = context.getSystemService(StorageManager.class); + final List vols = storage.getVolumes(); + for (VolumeInfo vol : vols) { + if (isInteresting(vol)) { + data.title = storage.getBestVolumeDescription(vol.id); + data.screenTitle = context.getString(R.string.storage_settings); + result.add(data); + } + } + + data = new SearchIndexableRaw(context); + data.title = context.getString(R.string.memory_size); + data.screenTitle = context.getString(R.string.storage_settings); + result.add(data); + + data = new SearchIndexableRaw(context); + data.title = context.getString(R.string.memory_available); + data.screenTitle = context.getString(R.string.storage_settings); + result.add(data); + + data = new SearchIndexableRaw(context); + data.title = context.getString(R.string.memory_apps_usage); + data.screenTitle = context.getString(R.string.storage_settings); + result.add(data); + + data = new SearchIndexableRaw(context); + data.title = context.getString(R.string.memory_dcim_usage); + data.screenTitle = context.getString(R.string.storage_settings); + result.add(data); + + data = new SearchIndexableRaw(context); + data.title = context.getString(R.string.memory_music_usage); + data.screenTitle = context.getString(R.string.storage_settings); + result.add(data); + + data = new SearchIndexableRaw(context); + data.title = context.getString(R.string.memory_downloads_usage); + data.screenTitle = context.getString(R.string.storage_settings); + result.add(data); + + data = new SearchIndexableRaw(context); + data.title = context.getString(R.string.memory_media_cache_usage); + data.screenTitle = context.getString(R.string.storage_settings); + result.add(data); + + data = new SearchIndexableRaw(context); + data.title = context.getString(R.string.memory_media_misc_usage); + data.screenTitle = context.getString(R.string.storage_settings); + result.add(data); + + return result; + } + }; +} diff --git a/src/com/android/settings/deviceinfo/StorageVolumePreference.java b/src/com/android/settings/deviceinfo/StorageVolumePreference.java new file mode 100644 index 0000000000..fbe34f6bdd --- /dev/null +++ b/src/com/android/settings/deviceinfo/StorageVolumePreference.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 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.content.Context; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.preference.Preference; +import android.text.format.Formatter; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.deviceinfo.StorageSettings.UnmountTask; + +import java.io.File; + +/** + * Preference line representing a single {@link VolumeInfo}, possibly including + * quick actions like unmounting. + */ +public class StorageVolumePreference extends Preference { + private final StorageManager mStorageManager; + private final VolumeInfo mVolume; + + public StorageVolumePreference(Context context, VolumeInfo volume) { + super(context); + + mStorageManager = context.getSystemService(StorageManager.class); + mVolume = volume; + + setKey(volume.id); + setTitle(mStorageManager.getBestVolumeDescription(volume.id)); + + switch (volume.state) { + case VolumeInfo.STATE_MOUNTED: + // TODO: move statfs() to background thread + final File path = new File(volume.path); + final String free = Formatter.formatFileSize(context, path.getFreeSpace()); + final String total = Formatter.formatFileSize(context, path.getTotalSpace()); + setSummary(context.getString(R.string.storage_volume_summary, free, total)); + break; + } + + // TODO: better icons + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(volume.id)) { + setIcon(context.getDrawable(R.drawable.ic_settings_storage)); + } else { + setIcon(context.getDrawable(R.drawable.ic_sim_sd)); + } + + if (volume.type == VolumeInfo.TYPE_PUBLIC && volume.state == VolumeInfo.STATE_MOUNTED) { + setWidgetLayoutResource(R.layout.preference_storage_action); + } + } + + @Override + protected void onBindView(View view) { + final TextView unmount = (TextView) view.findViewById(R.id.unmount); + if (unmount != null) { + unmount.setText("\u23CF"); + unmount.setOnClickListener(mUnmountListener); + } + + super.onBindView(view); + } + + private final View.OnClickListener mUnmountListener = new OnClickListener() { + @Override + public void onClick(View v) { + new UnmountTask(getContext(), mVolume.id).execute(); + } + }; +} diff --git a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java deleted file mode 100644 index a98f8d96d0..0000000000 --- a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright (C) 2011 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.ActivityManagerNative; -import android.app.ActivityThread; -import android.app.DownloadManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.IPackageManager; -import android.content.pm.UserInfo; -import android.content.res.Resources; -import android.hardware.usb.UsbManager; -import android.os.Environment; -import android.os.Handler; -import android.os.Message; -import android.os.RemoteException; -import android.os.UserManager; -import android.os.storage.StorageManager; -import android.os.storage.StorageVolume; -import android.preference.Preference; -import android.preference.PreferenceCategory; -import android.provider.MediaStore; -import android.text.format.Formatter; - -import com.android.settings.R; -import com.android.settings.Settings; -import com.android.settings.deviceinfo.StorageMeasurement.MeasurementDetails; -import com.android.settings.deviceinfo.StorageMeasurement.MeasurementReceiver; -import com.google.android.collect.Lists; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; - -public class StorageVolumePreferenceCategory extends PreferenceCategory { - public static final String KEY_CACHE = "cache"; - - private static final int ORDER_USAGE_BAR = -2; - private static final int ORDER_STORAGE_LOW = -1; - - /** Physical volume being measured, or {@code null} for internal. */ - private final StorageVolume mVolume; - private final StorageMeasurement mMeasure; - - private final Resources mResources; - private final StorageManager mStorageManager; - private final UserManager mUserManager; - - private UsageBarPreference mUsageBarPreference; - private Preference mMountTogglePreference; - private Preference mFormatPreference; - private Preference mStorageLow; - - private StorageItemPreference mItemTotal; - private StorageItemPreference mItemAvailable; - private StorageItemPreference mItemApps; - private StorageItemPreference mItemDcim; - private StorageItemPreference mItemMusic; - private StorageItemPreference mItemDownloads; - private StorageItemPreference mItemCache; - private StorageItemPreference mItemMisc; - private List mItemUsers = Lists.newArrayList(); - - private boolean mUsbConnected; - private String mUsbFunction; - - private long mTotalSize; - - private static final int MSG_UI_UPDATE_APPROXIMATE = 1; - private static final int MSG_UI_UPDATE_DETAILS = 2; - - private Handler mUpdateHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UI_UPDATE_APPROXIMATE: { - final long[] size = (long[]) msg.obj; - updateApproximate(size[0], size[1]); - break; - } - case MSG_UI_UPDATE_DETAILS: { - final MeasurementDetails details = (MeasurementDetails) msg.obj; - updateDetails(details); - break; - } - } - } - }; - - /** - * Build category to summarize internal storage, including any emulated - * {@link StorageVolume}. - */ - public static StorageVolumePreferenceCategory buildForInternal(Context context) { - return new StorageVolumePreferenceCategory(context, null); - } - - /** - * Build category to summarize specific physical {@link StorageVolume}. - */ - public static StorageVolumePreferenceCategory buildForPhysical( - Context context, StorageVolume volume) { - return new StorageVolumePreferenceCategory(context, volume); - } - - private StorageVolumePreferenceCategory(Context context, StorageVolume volume) { - super(context); - - mVolume = volume; - mMeasure = StorageMeasurement.getInstance(context, volume); - - mResources = context.getResources(); - mStorageManager = StorageManager.from(context); - mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); - - setTitle(volume != null ? volume.getDescription(context) - : context.getText(R.string.internal_storage)); - } - - private StorageItemPreference buildItem(int titleRes, int colorRes) { - return new StorageItemPreference(getContext(), titleRes, colorRes); - } - - public void init() { - final Context context = getContext(); - - removeAll(); - - final UserInfo currentUser; - try { - currentUser = ActivityManagerNative.getDefault().getCurrentUser(); - } catch (RemoteException e) { - throw new RuntimeException("Failed to get current user"); - } - - final List otherUsers = getUsersExcluding(currentUser); - final boolean showUsers = mVolume == null && otherUsers.size() > 0; - - mUsageBarPreference = new UsageBarPreference(context); - mUsageBarPreference.setOrder(ORDER_USAGE_BAR); - addPreference(mUsageBarPreference); - - mItemTotal = buildItem(R.string.memory_size, 0); - mItemAvailable = buildItem(R.string.memory_available, R.color.memory_avail); - addPreference(mItemTotal); - addPreference(mItemAvailable); - - mItemApps = buildItem(R.string.memory_apps_usage, R.color.memory_apps_usage); - mItemDcim = buildItem(R.string.memory_dcim_usage, R.color.memory_dcim); - mItemMusic = buildItem(R.string.memory_music_usage, R.color.memory_music); - mItemDownloads = buildItem(R.string.memory_downloads_usage, R.color.memory_downloads); - mItemCache = buildItem(R.string.memory_media_cache_usage, R.color.memory_cache); - mItemMisc = buildItem(R.string.memory_media_misc_usage, R.color.memory_misc); - - mItemCache.setKey(KEY_CACHE); - - final boolean showDetails = mVolume == null || mVolume.isPrimary(); - if (showDetails) { - if (showUsers) { - addPreference(new PreferenceHeader(context, currentUser.name)); - } - - addPreference(mItemApps); - addPreference(mItemDcim); - addPreference(mItemMusic); - addPreference(mItemDownloads); - addPreference(mItemCache); - addPreference(mItemMisc); - - if (showUsers) { - addPreference(new PreferenceHeader(context, R.string.storage_other_users)); - - int count = 0; - for (UserInfo info : otherUsers) { - final int colorRes = count++ % 2 == 0 ? R.color.memory_user_light - : R.color.memory_user_dark; - final StorageItemPreference userPref = new StorageItemPreference( - getContext(), info.name, colorRes, info.id); - mItemUsers.add(userPref); - addPreference(userPref); - } - } - } - - final boolean isRemovable = mVolume != null ? mVolume.isRemovable() : false; - // Always create the preference since many code rely on it existing - mMountTogglePreference = new Preference(context); - if (isRemovable) { - mMountTogglePreference.setTitle(R.string.sd_eject); - mMountTogglePreference.setSummary(R.string.sd_eject_summary); - addPreference(mMountTogglePreference); - } - - final boolean allowFormat = mVolume != null; - if (allowFormat) { - mFormatPreference = new Preference(context); - mFormatPreference.setTitle(R.string.sd_format); - mFormatPreference.setSummary(R.string.sd_format_summary); - addPreference(mFormatPreference); - } - - final IPackageManager pm = ActivityThread.getPackageManager(); - try { - if (pm.isStorageLow()) { - mStorageLow = new Preference(context); - mStorageLow.setOrder(ORDER_STORAGE_LOW); - mStorageLow.setTitle(R.string.storage_low_title); - mStorageLow.setSummary(R.string.storage_low_summary); - addPreference(mStorageLow); - } else if (mStorageLow != null) { - removePreference(mStorageLow); - mStorageLow = null; - } - } catch (RemoteException e) { - } - } - - public StorageVolume getStorageVolume() { - return mVolume; - } - - private void updatePreferencesFromState() { - // Only update for physical volumes - if (mVolume == null) return; - - mMountTogglePreference.setEnabled(true); - - final String state = mStorageManager.getVolumeState(mVolume.getPath()); - - if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { - mItemAvailable.setTitle(R.string.memory_available_read_only); - } else { - mItemAvailable.setTitle(R.string.memory_available); - } - - if (Environment.MEDIA_MOUNTED.equals(state) - || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { - mMountTogglePreference.setEnabled(true); - mMountTogglePreference.setTitle(mResources.getString(R.string.sd_eject)); - mMountTogglePreference.setSummary(mResources.getString(R.string.sd_eject_summary)); - addPreference(mUsageBarPreference); - addPreference(mItemTotal); - addPreference(mItemAvailable); - } else { - if (Environment.MEDIA_UNMOUNTED.equals(state) || Environment.MEDIA_NOFS.equals(state) - || Environment.MEDIA_UNMOUNTABLE.equals(state)) { - mMountTogglePreference.setEnabled(true); - mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount)); - mMountTogglePreference.setSummary(mResources.getString(R.string.sd_mount_summary)); - } else { - mMountTogglePreference.setEnabled(false); - mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount)); - mMountTogglePreference.setSummary(mResources.getString(R.string.sd_insert_summary)); - } - - removePreference(mUsageBarPreference); - removePreference(mItemTotal); - removePreference(mItemAvailable); - } - - if (mUsbConnected && (UsbManager.USB_FUNCTION_MTP.equals(mUsbFunction) || - UsbManager.USB_FUNCTION_PTP.equals(mUsbFunction))) { - mMountTogglePreference.setEnabled(false); - if (Environment.MEDIA_MOUNTED.equals(state) - || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { - mMountTogglePreference.setSummary( - mResources.getString(R.string.mtp_ptp_mode_summary)); - } - - if (mFormatPreference != null) { - mFormatPreference.setEnabled(false); - mFormatPreference.setSummary(mResources.getString(R.string.mtp_ptp_mode_summary)); - } - } else if (mFormatPreference != null) { - mFormatPreference.setEnabled(mMountTogglePreference.isEnabled()); - mFormatPreference.setSummary(mResources.getString(R.string.sd_format_summary)); - } - } - - public void updateApproximate(long totalSize, long availSize) { - mItemTotal.setSummary(formatSize(totalSize)); - mItemAvailable.setSummary(formatSize(availSize)); - - mTotalSize = totalSize; - - final long usedSize = totalSize - availSize; - - mUsageBarPreference.clear(); - mUsageBarPreference.addEntry(0, usedSize / (float) totalSize, android.graphics.Color.GRAY); - mUsageBarPreference.commit(); - - updatePreferencesFromState(); - } - - private static long totalValues(HashMap map, String... keys) { - long total = 0; - for (String key : keys) { - if (map.containsKey(key)) { - total += map.get(key); - } - } - return total; - } - - public void updateDetails(MeasurementDetails details) { - final boolean showDetails = mVolume == null || mVolume.isPrimary(); - if (!showDetails) return; - - // Count caches as available space, since system manages them - mItemTotal.setSummary(formatSize(details.totalSize)); - mItemAvailable.setSummary(formatSize(details.availSize)); - - mUsageBarPreference.clear(); - - updatePreference(mItemApps, details.appsSize); - - final long dcimSize = totalValues(details.mediaSize, Environment.DIRECTORY_DCIM, - Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES); - updatePreference(mItemDcim, dcimSize); - - final long musicSize = totalValues(details.mediaSize, Environment.DIRECTORY_MUSIC, - Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS, - Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS); - updatePreference(mItemMusic, musicSize); - - final long downloadsSize = totalValues(details.mediaSize, Environment.DIRECTORY_DOWNLOADS); - updatePreference(mItemDownloads, downloadsSize); - - updatePreference(mItemCache, details.cacheSize); - updatePreference(mItemMisc, details.miscSize); - - for (StorageItemPreference userPref : mItemUsers) { - final long userSize = details.usersSize.get(userPref.userHandle); - updatePreference(userPref, userSize); - } - - mUsageBarPreference.commit(); - } - - private void updatePreference(StorageItemPreference pref, long size) { - if (size > 0) { - pref.setSummary(formatSize(size)); - final int order = pref.getOrder(); - mUsageBarPreference.addEntry(order, size / (float) mTotalSize, pref.color); - } else { - removePreference(pref); - } - } - - private void measure() { - mMeasure.invalidate(); - mMeasure.measure(); - } - - public void onResume() { - mMeasure.setReceiver(mReceiver); - measure(); - } - - public void onStorageStateChanged() { - init(); - measure(); - } - - public void onUsbStateChanged(boolean isUsbConnected, String usbFunction) { - mUsbConnected = isUsbConnected; - mUsbFunction = usbFunction; - measure(); - } - - public void onMediaScannerFinished() { - measure(); - } - - public void onCacheCleared() { - measure(); - } - - public void onPause() { - mMeasure.cleanUp(); - } - - private String formatSize(long size) { - return Formatter.formatFileSize(getContext(), size); - } - - private MeasurementReceiver mReceiver = new MeasurementReceiver() { - @Override - public void updateApproximate(StorageMeasurement meas, long totalSize, long availSize) { - mUpdateHandler.obtainMessage(MSG_UI_UPDATE_APPROXIMATE, new long[] { - totalSize, availSize }).sendToTarget(); - } - - @Override - public void updateDetails(StorageMeasurement meas, MeasurementDetails details) { - mUpdateHandler.obtainMessage(MSG_UI_UPDATE_DETAILS, details).sendToTarget(); - } - }; - - public boolean mountToggleClicked(Preference preference) { - return preference == mMountTogglePreference; - } - - public Intent intentForClick(Preference pref) { - Intent intent = null; - - // TODO The current "delete" story is not fully handled by the respective applications. - // When it is done, make sure the intent types below are correct. - // If that cannot be done, remove these intents. - final String key = pref.getKey(); - if (pref == mFormatPreference) { - intent = new Intent(Intent.ACTION_VIEW); - intent.setClass(getContext(), com.android.settings.MediaFormat.class); - intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolume); - } else if (pref == mItemApps) { - intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE); - intent.setClass(getContext(), Settings.ManageApplicationsActivity.class); - } else if (pref == mItemDownloads) { - intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra( - DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true); - } else if (pref == mItemMusic) { - intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("audio/mp3"); - } else if (pref == mItemDcim) { - intent = new Intent(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); - // TODO Create a Videos category, MediaStore.Video.Media.EXTERNAL_CONTENT_URI - intent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI); - } else if (pref == mItemMisc) { - Context context = getContext().getApplicationContext(); - intent = new Intent(context, MiscFilesHandler.class); - intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolume); - } - - return intent; - } - - public static class PreferenceHeader extends Preference { - public PreferenceHeader(Context context, int titleRes) { - super(context, null, com.android.internal.R.attr.preferenceCategoryStyle); - setTitle(titleRes); - } - - public PreferenceHeader(Context context, CharSequence title) { - super(context, null, com.android.internal.R.attr.preferenceCategoryStyle); - setTitle(title); - } - - @Override - public boolean isEnabled() { - return false; - } - } - - /** - * Return list of other users, excluding the current user. - */ - private List getUsersExcluding(UserInfo excluding) { - final List users = mUserManager.getUsers(); - final Iterator i = users.iterator(); - while (i.hasNext()) { - if (i.next().id == excluding.id) { - i.remove(); - } - } - return users; - } -} diff --git a/src/com/android/settings/search/Ranking.java b/src/com/android/settings/search/Ranking.java index f37e1fcf97..2143d0dc9b 100644 --- a/src/com/android/settings/search/Ranking.java +++ b/src/com/android/settings/search/Ranking.java @@ -32,7 +32,7 @@ import com.android.settings.WirelessSettings; import com.android.settings.accessibility.AccessibilitySettings; import com.android.settings.applications.AdvancedAppSettings; import com.android.settings.bluetooth.BluetoothSettings; -import com.android.settings.deviceinfo.Memory; +import com.android.settings.deviceinfo.StorageSettings; import com.android.settings.deviceinfo.UsbSettings; import com.android.settings.fuelgauge.BatterySaverSettings; import com.android.settings.fuelgauge.PowerUsageSummary; @@ -129,7 +129,7 @@ public final class Ranking { sRankMap.put(ZenModeAutomationSettings.class.getName(), RANK_NOTIFICATIONS); // Storage - sRankMap.put(Memory.class.getName(), RANK_STORAGE); + sRankMap.put(StorageSettings.class.getName(), RANK_STORAGE); sRankMap.put(UsbSettings.class.getName(), RANK_STORAGE); // Battery diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java index dae8860d48..b7bb06213b 100644 --- a/src/com/android/settings/search/SearchIndexableResources.java +++ b/src/com/android/settings/search/SearchIndexableResources.java @@ -34,7 +34,7 @@ import com.android.settings.WirelessSettings; import com.android.settings.accessibility.AccessibilitySettings; import com.android.settings.applications.AdvancedAppSettings; import com.android.settings.bluetooth.BluetoothSettings; -import com.android.settings.deviceinfo.Memory; +import com.android.settings.deviceinfo.StorageSettings; import com.android.settings.deviceinfo.UsbSettings; import com.android.settings.fuelgauge.BatterySaverSettings; import com.android.settings.fuelgauge.PowerUsageSummary; @@ -170,11 +170,11 @@ public final class SearchIndexableResources { ZenModePrioritySettings.class.getName(), R.drawable.ic_settings_notifications)); - sResMap.put(Memory.class.getName(), + sResMap.put(StorageSettings.class.getName(), new SearchIndexableResource( - Ranking.getRankForClassName(Memory.class.getName()), + Ranking.getRankForClassName(StorageSettings.class.getName()), NO_DATA_RES_ID, - Memory.class.getName(), + StorageSettings.class.getName(), R.drawable.ic_settings_storage)); sResMap.put(UsbSettings.class.getName(), -- 2.11.0