diff options
author | Jeff Sharkey <jsharkey@android.com> | 2015-04-11 21:27:33 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2015-04-12 22:02:32 -0700 |
commit | 42833b2ff4d7a26dd9a609d2fd4436d9a26f28b5 (patch) | |
tree | 18ffcc0f4eea720d6a98de6c3549600e6f7d1b53 /src | |
parent | 09c0c1385a08c23b8063626d5f439088b27d9c52 (diff) | |
download | packages_apps_Settings-42833b2ff4d7a26dd9a609d2fd4436d9a26f28b5.tar.gz packages_apps_Settings-42833b2ff4d7a26dd9a609d2fd4436d9a26f28b5.tar.bz2 packages_apps_Settings-42833b2ff4d7a26dd9a609d2fd4436d9a26f28b5.zip |
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
Diffstat (limited to 'src')
13 files changed, 1382 insertions, 1484 deletions
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index b58159e05..fe0df59c7 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 24209b003..7bfd24952 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 a07f7c2cc..000000000 --- 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<StorageVolumePreferenceCategory> 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<ApplicationInfo> list = am.getRunningExternalApplications(); - if (list != null && list.size() > 0) { - return true; - } - */ - // Better safe than sorry. Assume the storage is used to ask for confirmation. - return true; - } - - private void unmount() { - // Check if external media is in use. - try { - if (hasAppsAccessingStorage()) { - // Present dialog to user - showDialogInner(DLG_CONFIRM_UNMOUNT); - } else { - doUnmount(); - } - } catch (RemoteException e) { - // Very unlikely. But present an error dialog anyway - Log.e(TAG, "Is MountService running?"); - showDialogInner(DLG_ERROR_UNMOUNT); - } - } - - private void mount() { - IMountService mountService = getMountService(); - try { - if (mountService != null) { - mountService.mountVolume(sClickedMountPoint); - } else { - Log.e(TAG, "Mount service is null, can't mount"); - } - } catch (RemoteException ex) { - // Not much can be done - } - } - - private void onCacheCleared() { - for (StorageVolumePreferenceCategory category : mCategories) { - category.onCacheCleared(); - } - } - - private static class ClearCacheObserver extends IPackageDataObserver.Stub { - private final Memory mTarget; - private int mRemaining; - - public ClearCacheObserver(Memory target, int remaining) { - mTarget = target; - mRemaining = remaining; - } - - @Override - public void onRemoveCompleted(final String packageName, final boolean succeeded) { - synchronized (this) { - if (--mRemaining == 0) { - mTarget.onCacheCleared(); - } - } - } - } - - /** - * Dialog to request user confirmation before clearing all cache data. - */ - public static class ConfirmClearCacheFragment extends DialogFragment { - public static void show(Memory parent) { - if (!parent.isAdded()) return; - - final ConfirmClearCacheFragment dialog = new ConfirmClearCacheFragment(); - dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_CONFIRM_CLEAR_CACHE); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.memory_clear_cache_title); - builder.setMessage(getString(R.string.memory_clear_cache_message)); - - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final Memory target = (Memory) getTargetFragment(); - final PackageManager pm = context.getPackageManager(); - final List<PackageInfo> infos = pm.getInstalledPackages(0); - final ClearCacheObserver observer = new ClearCacheObserver( - target, infos.size()); - for (PackageInfo info : infos) { - pm.deleteApplicationCacheFiles(info.packageName, observer); - } - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - - return builder.create(); - } - } - - /** - * Enable indexing of searchable data - */ - public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider() { - @Override - public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { - final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(); - - 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 93e352b2e..000000000 --- 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<Object> toRemove = new ArrayList<Object>(); - 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<StorageMeasurement.FileInfo> 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<StorageMeasurement.FileInfo>) 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<Object> 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 000000000..efb9a07d2 --- /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<StorageItemPreference> 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<UserInfo> 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<UserInfo> getUsersExcluding(UserInfo excluding) { + final List<UserInfo> users = mUserManager.getUsers(); + final Iterator<UserInfo> i = users.iterator(); + while (i.hasNext()) { + if (i.next().id == excluding.id) { + i.remove(); + } + } + return users; + } + + private static long totalValues(HashMap<String, Long> 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<PackageInfo> infos = pm.getInstalledPackages(0); + final ClearCacheObserver observer = new ClearCacheObserver( + target, infos.size()); + for (PackageInfo info : infos) { + pm.deleteApplicationCacheFiles(info.packageName, observer); + } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + + return builder.create(); + } + } + + 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 000000000..6edfc924c --- /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 87e827e71..8d48cf09f 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 34ef62b17..db91fdb1d 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<StorageVolume, StorageMeasurement> 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. * <p> @@ -128,7 +105,7 @@ public class StorageMeasurement { * When measuring a physical {@link StorageVolume}, this reflects media * on that volume. */ - public HashMap<String, Long> mediaSize = Maps.newHashMap(); + public HashMap<String, Long> 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<MeasurementReceiver> mReceiver; - - /** Physical volume being measured, or {@code null} for internal. */ - private final StorageVolume mVolume; + private WeakReference<MeasurementReceiver> 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<FileInfo> 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<Context> 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>(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<UserInfo> users = userManager.getUsers(); + final int currentUser = ActivityManager.getCurrentUser(); - final UserManager userManager = (UserManager) context.getSystemService( - Context.USER_SERVICE); - final List<UserInfo> 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<ApplicationInfo> 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<ApplicationInfo> 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<ApplicationInfo> 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<FileInfo>(); - 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<FileInfo> { - 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 000000000..001f00d89 --- /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<VolumeInfo> sVolumeComparator = new Comparator<VolumeInfo>() { + @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<VolumeInfo> 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<Void, Void, Exception> { + 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<Void, Void, Exception> { + 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<Void, Void, Exception> { + 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<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { + final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(); + + 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<VolumeInfo> 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 000000000..fbe34f6bd --- /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 a98f8d96d..000000000 --- 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<StorageItemPreference> 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<UserInfo> 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<String, Long> 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<UserInfo> getUsersExcluding(UserInfo excluding) { - final List<UserInfo> users = mUserManager.getUsers(); - final Iterator<UserInfo> 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 f37e1fcf9..2143d0dc9 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 dae8860d4..b7bb06213 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(), |