Home | History | Annotate | Download | only in deviceinfo
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.deviceinfo;
     18 
     19 import android.app.AlertDialog;
     20 import android.app.Dialog;
     21 import android.app.DialogFragment;
     22 import android.app.Fragment;
     23 import android.content.Context;
     24 import android.content.DialogInterface;
     25 import android.content.Intent;
     26 import android.graphics.Color;
     27 import android.graphics.drawable.Drawable;
     28 import android.os.AsyncTask;
     29 import android.os.Bundle;
     30 import android.os.storage.DiskInfo;
     31 import android.os.storage.StorageEventListener;
     32 import android.os.storage.StorageManager;
     33 import android.os.storage.VolumeInfo;
     34 import android.os.storage.VolumeRecord;
     35 import android.preference.Preference;
     36 import android.preference.PreferenceCategory;
     37 import android.preference.PreferenceScreen;
     38 import android.text.TextUtils;
     39 import android.text.format.Formatter;
     40 import android.text.format.Formatter.BytesResult;
     41 import android.util.Log;
     42 import android.widget.Toast;
     43 
     44 import com.android.internal.logging.MetricsLogger;
     45 import com.android.settings.R;
     46 import com.android.settings.SettingsPreferenceFragment;
     47 import com.android.settings.search.BaseSearchIndexProvider;
     48 import com.android.settings.search.Indexable;
     49 import com.android.settings.search.SearchIndexableRaw;
     50 
     51 import java.io.File;
     52 import java.util.ArrayList;
     53 import java.util.Collections;
     54 import java.util.List;
     55 
     56 /**
     57  * Panel showing both internal storage (both built-in storage and private
     58  * volumes) and removable storage (public volumes).
     59  */
     60 public class StorageSettings extends SettingsPreferenceFragment implements Indexable {
     61     static final String TAG = "StorageSettings";
     62 
     63     private static final String TAG_VOLUME_UNMOUNTED = "volume_unmounted";
     64     private static final String TAG_DISK_INIT = "disk_init";
     65 
     66     static final int COLOR_PUBLIC = Color.parseColor("#ff9e9e9e");
     67     static final int COLOR_WARNING = Color.parseColor("#fff4511e");
     68 
     69     static final int[] COLOR_PRIVATE = new int[] {
     70             Color.parseColor("#ff26a69a"),
     71             Color.parseColor("#ffab47bc"),
     72             Color.parseColor("#fff2a600"),
     73             Color.parseColor("#ffec407a"),
     74             Color.parseColor("#ffc0ca33"),
     75     };
     76 
     77     private StorageManager mStorageManager;
     78 
     79     private PreferenceCategory mInternalCategory;
     80     private PreferenceCategory mExternalCategory;
     81 
     82     private StorageSummaryPreference mInternalSummary;
     83 
     84     @Override
     85     protected int getMetricsCategory() {
     86         return MetricsLogger.DEVICEINFO_STORAGE;
     87     }
     88 
     89     @Override
     90     protected int getHelpResource() {
     91         return R.string.help_uri_storage;
     92     }
     93 
     94     @Override
     95     public void onCreate(Bundle icicle) {
     96         super.onCreate(icicle);
     97 
     98         final Context context = getActivity();
     99 
    100         mStorageManager = context.getSystemService(StorageManager.class);
    101         mStorageManager.registerListener(mStorageListener);
    102 
    103         addPreferencesFromResource(R.xml.device_info_storage);
    104 
    105         mInternalCategory = (PreferenceCategory) findPreference("storage_internal");
    106         mExternalCategory = (PreferenceCategory) findPreference("storage_external");
    107 
    108         mInternalSummary = new StorageSummaryPreference(context);
    109 
    110         setHasOptionsMenu(true);
    111     }
    112 
    113     private final StorageEventListener mStorageListener = new StorageEventListener() {
    114         @Override
    115         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
    116             if (isInteresting(vol)) {
    117                 refresh();
    118             }
    119         }
    120     };
    121 
    122     private static boolean isInteresting(VolumeInfo vol) {
    123         switch(vol.getType()) {
    124             case VolumeInfo.TYPE_PRIVATE:
    125             case VolumeInfo.TYPE_PUBLIC:
    126                 return true;
    127             default:
    128                 return false;
    129         }
    130     }
    131 
    132     private void refresh() {
    133         final Context context = getActivity();
    134 
    135         getPreferenceScreen().removeAll();
    136         mInternalCategory.removeAll();
    137         mExternalCategory.removeAll();
    138 
    139         mInternalCategory.addPreference(mInternalSummary);
    140 
    141         int privateCount = 0;
    142         long privateUsedBytes = 0;
    143         long privateTotalBytes = 0;
    144 
    145         final List<VolumeInfo> volumes = mStorageManager.getVolumes();
    146         Collections.sort(volumes, VolumeInfo.getDescriptionComparator());
    147 
    148         for (VolumeInfo vol : volumes) {
    149             if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
    150                 final int color = COLOR_PRIVATE[privateCount++ % COLOR_PRIVATE.length];
    151                 mInternalCategory.addPreference(
    152                         new StorageVolumePreference(context, vol, color));
    153                 if (vol.isMountedReadable()) {
    154                     final File path = vol.getPath();
    155                     privateUsedBytes += path.getTotalSpace() - path.getFreeSpace();
    156                     privateTotalBytes += path.getTotalSpace();
    157                 }
    158             } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
    159                 mExternalCategory.addPreference(
    160                         new StorageVolumePreference(context, vol, COLOR_PUBLIC));
    161             }
    162         }
    163 
    164         // Show missing private volumes
    165         final List<VolumeRecord> recs = mStorageManager.getVolumeRecords();
    166         for (VolumeRecord rec : recs) {
    167             if (rec.getType() == VolumeInfo.TYPE_PRIVATE
    168                     && mStorageManager.findVolumeByUuid(rec.getFsUuid()) == null) {
    169                 // TODO: add actual storage type to record
    170                 final Drawable icon = context.getDrawable(R.drawable.ic_sim_sd);
    171                 icon.mutate();
    172                 icon.setTint(COLOR_PUBLIC);
    173 
    174                 final Preference pref = new Preference(context);
    175                 pref.setKey(rec.getFsUuid());
    176                 pref.setTitle(rec.getNickname());
    177                 pref.setSummary(com.android.internal.R.string.ext_media_status_missing);
    178                 pref.setIcon(icon);
    179                 mInternalCategory.addPreference(pref);
    180             }
    181         }
    182 
    183         // Show unsupported disks to give a chance to init
    184         final List<DiskInfo> disks = mStorageManager.getDisks();
    185         for (DiskInfo disk : disks) {
    186             if (disk.volumeCount == 0 && disk.size > 0) {
    187                 final Preference pref = new Preference(context);
    188                 pref.setKey(disk.getId());
    189                 pref.setTitle(disk.getDescription());
    190                 pref.setSummary(com.android.internal.R.string.ext_media_status_unsupported);
    191                 pref.setIcon(R.drawable.ic_sim_sd);
    192                 mExternalCategory.addPreference(pref);
    193             }
    194         }
    195 
    196         final BytesResult result = Formatter.formatBytes(getResources(), privateUsedBytes, 0);
    197         mInternalSummary.setTitle(TextUtils.expandTemplate(getText(R.string.storage_size_large),
    198                 result.value, result.units));
    199         mInternalSummary.setSummary(getString(R.string.storage_volume_used_total,
    200                 Formatter.formatFileSize(context, privateTotalBytes)));
    201 
    202         if (mInternalCategory.getPreferenceCount() > 0) {
    203             getPreferenceScreen().addPreference(mInternalCategory);
    204         }
    205         if (mExternalCategory.getPreferenceCount() > 0) {
    206             getPreferenceScreen().addPreference(mExternalCategory);
    207         }
    208 
    209         if (mInternalCategory.getPreferenceCount() == 2
    210                 && mExternalCategory.getPreferenceCount() == 0) {
    211             // Only showing primary internal storage, so just shortcut
    212             final Bundle args = new Bundle();
    213             args.putString(VolumeInfo.EXTRA_VOLUME_ID, VolumeInfo.ID_PRIVATE_INTERNAL);
    214             startFragment(this, PrivateVolumeSettings.class.getCanonicalName(),
    215                     -1, 0, args);
    216             finish();
    217         }
    218     }
    219 
    220     @Override
    221     public void onResume() {
    222         super.onResume();
    223         mStorageManager.registerListener(mStorageListener);
    224         refresh();
    225     }
    226 
    227     @Override
    228     public void onPause() {
    229         super.onPause();
    230         mStorageManager.unregisterListener(mStorageListener);
    231     }
    232 
    233     @Override
    234     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference pref) {
    235         final String key = pref.getKey();
    236         if (pref instanceof StorageVolumePreference) {
    237             // Picked a normal volume
    238             final VolumeInfo vol = mStorageManager.findVolumeById(key);
    239 
    240             if (vol.getState() == VolumeInfo.STATE_UNMOUNTED) {
    241                 VolumeUnmountedFragment.show(this, vol.getId());
    242                 return true;
    243             } else if (vol.getState() == VolumeInfo.STATE_UNMOUNTABLE) {
    244                 DiskInitFragment.show(this, R.string.storage_dialog_unmountable, vol.getDiskId());
    245                 return true;
    246             }
    247 
    248             if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
    249                 final Bundle args = new Bundle();
    250                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
    251                 startFragment(this, PrivateVolumeSettings.class.getCanonicalName(),
    252                         -1, 0, args);
    253                 return true;
    254 
    255             } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
    256                 if (vol.isMountedReadable()) {
    257                     startActivity(vol.buildBrowseIntent());
    258                     return true;
    259                 } else {
    260                     final Bundle args = new Bundle();
    261                     args.putString(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
    262                     startFragment(this, PublicVolumeSettings.class.getCanonicalName(),
    263                             -1, 0, args);
    264                     return true;
    265                 }
    266             }
    267 
    268         } else if (key.startsWith("disk:")) {
    269             // Picked an unsupported disk
    270             DiskInitFragment.show(this, R.string.storage_dialog_unsupported, key);
    271             return true;
    272 
    273         } else {
    274             // Picked a missing private volume
    275             final Bundle args = new Bundle();
    276             args.putString(VolumeRecord.EXTRA_FS_UUID, key);
    277             startFragment(this, PrivateVolumeForget.class.getCanonicalName(),
    278                     R.string.storage_menu_forget, 0, args);
    279             return true;
    280         }
    281 
    282         return false;
    283     }
    284 
    285     public static class MountTask extends AsyncTask<Void, Void, Exception> {
    286         private final Context mContext;
    287         private final StorageManager mStorageManager;
    288         private final String mVolumeId;
    289         private final String mDescription;
    290 
    291         public MountTask(Context context, VolumeInfo volume) {
    292             mContext = context.getApplicationContext();
    293             mStorageManager = mContext.getSystemService(StorageManager.class);
    294             mVolumeId = volume.getId();
    295             mDescription = mStorageManager.getBestVolumeDescription(volume);
    296         }
    297 
    298         @Override
    299         protected Exception doInBackground(Void... params) {
    300             try {
    301                 mStorageManager.mount(mVolumeId);
    302                 return null;
    303             } catch (Exception e) {
    304                 return e;
    305             }
    306         }
    307 
    308         @Override
    309         protected void onPostExecute(Exception e) {
    310             if (e == null) {
    311                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_success,
    312                         mDescription), Toast.LENGTH_SHORT).show();
    313             } else {
    314                 Log.e(TAG, "Failed to mount " + mVolumeId, e);
    315                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_failure,
    316                         mDescription), Toast.LENGTH_SHORT).show();
    317             }
    318         }
    319     }
    320 
    321     public static class UnmountTask extends AsyncTask<Void, Void, Exception> {
    322         private final Context mContext;
    323         private final StorageManager mStorageManager;
    324         private final String mVolumeId;
    325         private final String mDescription;
    326 
    327         public UnmountTask(Context context, VolumeInfo volume) {
    328             mContext = context.getApplicationContext();
    329             mStorageManager = mContext.getSystemService(StorageManager.class);
    330             mVolumeId = volume.getId();
    331             mDescription = mStorageManager.getBestVolumeDescription(volume);
    332         }
    333 
    334         @Override
    335         protected Exception doInBackground(Void... params) {
    336             try {
    337                 mStorageManager.unmount(mVolumeId);
    338                 return null;
    339             } catch (Exception e) {
    340                 return e;
    341             }
    342         }
    343 
    344         @Override
    345         protected void onPostExecute(Exception e) {
    346             if (e == null) {
    347                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_success,
    348                         mDescription), Toast.LENGTH_SHORT).show();
    349             } else {
    350                 Log.e(TAG, "Failed to unmount " + mVolumeId, e);
    351                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_failure,
    352                         mDescription), Toast.LENGTH_SHORT).show();
    353             }
    354         }
    355     }
    356 
    357     public static class VolumeUnmountedFragment extends DialogFragment {
    358         public static void show(Fragment parent, String volumeId) {
    359             final Bundle args = new Bundle();
    360             args.putString(VolumeInfo.EXTRA_VOLUME_ID, volumeId);
    361 
    362             final VolumeUnmountedFragment dialog = new VolumeUnmountedFragment();
    363             dialog.setArguments(args);
    364             dialog.setTargetFragment(parent, 0);
    365             dialog.show(parent.getFragmentManager(), TAG_VOLUME_UNMOUNTED);
    366         }
    367 
    368         @Override
    369         public Dialog onCreateDialog(Bundle savedInstanceState) {
    370             final Context context = getActivity();
    371             final StorageManager sm = context.getSystemService(StorageManager.class);
    372 
    373             final String volumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
    374             final VolumeInfo vol = sm.findVolumeById(volumeId);
    375 
    376             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
    377             builder.setMessage(TextUtils.expandTemplate(
    378                     getText(R.string.storage_dialog_unmounted), vol.getDisk().getDescription()));
    379 
    380             builder.setPositiveButton(R.string.storage_menu_mount,
    381                     new DialogInterface.OnClickListener() {
    382                 @Override
    383                 public void onClick(DialogInterface dialog, int which) {
    384                     new MountTask(context, vol).execute();
    385                 }
    386             });
    387             builder.setNegativeButton(R.string.cancel, null);
    388 
    389             return builder.create();
    390         }
    391     }
    392 
    393     public static class DiskInitFragment extends DialogFragment {
    394         public static void show(Fragment parent, int resId, String diskId) {
    395             final Bundle args = new Bundle();
    396             args.putInt(Intent.EXTRA_TEXT, resId);
    397             args.putString(DiskInfo.EXTRA_DISK_ID, diskId);
    398 
    399             final DiskInitFragment dialog = new DiskInitFragment();
    400             dialog.setArguments(args);
    401             dialog.setTargetFragment(parent, 0);
    402             dialog.show(parent.getFragmentManager(), TAG_DISK_INIT);
    403         }
    404 
    405         @Override
    406         public Dialog onCreateDialog(Bundle savedInstanceState) {
    407             final Context context = getActivity();
    408             final StorageManager sm = context.getSystemService(StorageManager.class);
    409 
    410             final int resId = getArguments().getInt(Intent.EXTRA_TEXT);
    411             final String diskId = getArguments().getString(DiskInfo.EXTRA_DISK_ID);
    412             final DiskInfo disk = sm.findDiskById(diskId);
    413 
    414             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
    415             builder.setMessage(TextUtils.expandTemplate(getText(resId), disk.getDescription()));
    416 
    417             builder.setPositiveButton(R.string.storage_menu_set_up,
    418                     new DialogInterface.OnClickListener() {
    419                 @Override
    420                 public void onClick(DialogInterface dialog, int which) {
    421                     final Intent intent = new Intent(context, StorageWizardInit.class);
    422                     intent.putExtra(DiskInfo.EXTRA_DISK_ID, diskId);
    423                     startActivity(intent);
    424                 }
    425             });
    426             builder.setNegativeButton(R.string.cancel, null);
    427 
    428             return builder.create();
    429         }
    430     }
    431 
    432     /**
    433      * Enable indexing of searchable data
    434      */
    435     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
    436         new BaseSearchIndexProvider() {
    437             @Override
    438             public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
    439                 final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
    440 
    441                 SearchIndexableRaw data = new SearchIndexableRaw(context);
    442                 data.title = context.getString(R.string.storage_settings);
    443                 data.screenTitle = context.getString(R.string.storage_settings);
    444                 result.add(data);
    445 
    446                 data = new SearchIndexableRaw(context);
    447                 data.title = context.getString(R.string.internal_storage);
    448                 data.screenTitle = context.getString(R.string.storage_settings);
    449                 result.add(data);
    450 
    451                 data = new SearchIndexableRaw(context);
    452                 final StorageManager storage = context.getSystemService(StorageManager.class);
    453                 final List<VolumeInfo> vols = storage.getVolumes();
    454                 for (VolumeInfo vol : vols) {
    455                     if (isInteresting(vol)) {
    456                         data.title = storage.getBestVolumeDescription(vol);
    457                         data.screenTitle = context.getString(R.string.storage_settings);
    458                         result.add(data);
    459                     }
    460                 }
    461 
    462                 data = new SearchIndexableRaw(context);
    463                 data.title = context.getString(R.string.memory_size);
    464                 data.screenTitle = context.getString(R.string.storage_settings);
    465                 result.add(data);
    466 
    467                 data = new SearchIndexableRaw(context);
    468                 data.title = context.getString(R.string.memory_available);
    469                 data.screenTitle = context.getString(R.string.storage_settings);
    470                 result.add(data);
    471 
    472                 data = new SearchIndexableRaw(context);
    473                 data.title = context.getString(R.string.memory_apps_usage);
    474                 data.screenTitle = context.getString(R.string.storage_settings);
    475                 result.add(data);
    476 
    477                 data = new SearchIndexableRaw(context);
    478                 data.title = context.getString(R.string.memory_dcim_usage);
    479                 data.screenTitle = context.getString(R.string.storage_settings);
    480                 result.add(data);
    481 
    482                 data = new SearchIndexableRaw(context);
    483                 data.title = context.getString(R.string.memory_music_usage);
    484                 data.screenTitle = context.getString(R.string.storage_settings);
    485                 result.add(data);
    486 
    487                 data = new SearchIndexableRaw(context);
    488                 data.title = context.getString(R.string.memory_downloads_usage);
    489                 data.screenTitle = context.getString(R.string.storage_settings);
    490                 result.add(data);
    491 
    492                 data = new SearchIndexableRaw(context);
    493                 data.title = context.getString(R.string.memory_media_cache_usage);
    494                 data.screenTitle = context.getString(R.string.storage_settings);
    495                 result.add(data);
    496 
    497                 data = new SearchIndexableRaw(context);
    498                 data.title = context.getString(R.string.memory_media_misc_usage);
    499                 data.screenTitle = context.getString(R.string.storage_settings);
    500                 result.add(data);
    501 
    502                 return result;
    503             }
    504         };
    505 }
    506