• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Activity;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.app.DialogFragment;
23 import android.app.Fragment;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.graphics.Color;
28 import android.graphics.drawable.Drawable;
29 import android.os.AsyncTask;
30 import android.os.Bundle;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.os.storage.DiskInfo;
34 import android.os.storage.StorageEventListener;
35 import android.os.storage.StorageManager;
36 import android.os.storage.VolumeInfo;
37 import android.os.storage.VolumeRecord;
38 import android.support.v7.preference.Preference;
39 import android.support.v7.preference.PreferenceCategory;
40 import android.text.TextUtils;
41 import android.text.format.Formatter;
42 import android.text.format.Formatter.BytesResult;
43 import android.util.Log;
44 import android.widget.Toast;
45 
46 import com.android.internal.logging.MetricsProto.MetricsEvent;
47 import com.android.settings.R;
48 import com.android.settings.SettingsPreferenceFragment;
49 import com.android.settings.Utils;
50 import com.android.settings.dashboard.SummaryLoader;
51 import com.android.settings.search.BaseSearchIndexProvider;
52 import com.android.settings.search.Indexable;
53 import com.android.settings.search.SearchIndexableRaw;
54 import com.android.settingslib.RestrictedLockUtils;
55 import com.android.settingslib.drawer.SettingsDrawerActivity;
56 
57 import java.io.File;
58 import java.util.ArrayList;
59 import java.util.Collections;
60 import java.util.List;
61 
62 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
63 
64 /**
65  * Panel showing both internal storage (both built-in storage and private
66  * volumes) and removable storage (public volumes).
67  */
68 public class StorageSettings extends SettingsPreferenceFragment implements Indexable {
69     static final String TAG = "StorageSettings";
70 
71     private static final String TAG_VOLUME_UNMOUNTED = "volume_unmounted";
72     private static final String TAG_DISK_INIT = "disk_init";
73 
74     static final int COLOR_PUBLIC = Color.parseColor("#ff9e9e9e");
75     static final int COLOR_WARNING = Color.parseColor("#fff4511e");
76 
77     static final int[] COLOR_PRIVATE = new int[] {
78             Color.parseColor("#ff26a69a"),
79             Color.parseColor("#ffab47bc"),
80             Color.parseColor("#fff2a600"),
81             Color.parseColor("#ffec407a"),
82             Color.parseColor("#ffc0ca33"),
83     };
84 
85     private StorageManager mStorageManager;
86 
87     private PreferenceCategory mInternalCategory;
88     private PreferenceCategory mExternalCategory;
89 
90     private StorageSummaryPreference mInternalSummary;
91     private static long sTotalInternalStorage;
92 
93     @Override
getMetricsCategory()94     protected int getMetricsCategory() {
95         return MetricsEvent.DEVICEINFO_STORAGE;
96     }
97 
98     @Override
getHelpResource()99     protected int getHelpResource() {
100         return R.string.help_uri_storage;
101     }
102 
103     @Override
onCreate(Bundle icicle)104     public void onCreate(Bundle icicle) {
105         super.onCreate(icicle);
106 
107         final Context context = getActivity();
108 
109         mStorageManager = context.getSystemService(StorageManager.class);
110         mStorageManager.registerListener(mStorageListener);
111 
112         if (sTotalInternalStorage <= 0) {
113             sTotalInternalStorage = mStorageManager.getPrimaryStorageSize();
114         }
115 
116         addPreferencesFromResource(R.xml.device_info_storage);
117 
118         mInternalCategory = (PreferenceCategory) findPreference("storage_internal");
119         mExternalCategory = (PreferenceCategory) findPreference("storage_external");
120 
121         mInternalSummary = new StorageSummaryPreference(getPrefContext());
122 
123         setHasOptionsMenu(true);
124     }
125 
126     private final StorageEventListener mStorageListener = new StorageEventListener() {
127         @Override
128         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
129             if (isInteresting(vol)) {
130                 refresh();
131             }
132         }
133 
134         @Override
135         public void onDiskDestroyed(DiskInfo disk) {
136             refresh();
137         }
138     };
139 
isInteresting(VolumeInfo vol)140     private static boolean isInteresting(VolumeInfo vol) {
141         switch(vol.getType()) {
142             case VolumeInfo.TYPE_PRIVATE:
143             case VolumeInfo.TYPE_PUBLIC:
144                 return true;
145             default:
146                 return false;
147         }
148     }
149 
refresh()150     private void refresh() {
151         final Context context = getPrefContext();
152 
153         getPreferenceScreen().removeAll();
154         mInternalCategory.removeAll();
155         mExternalCategory.removeAll();
156 
157         mInternalCategory.addPreference(mInternalSummary);
158 
159         int privateCount = 0;
160         long privateUsedBytes = 0;
161         long privateTotalBytes = 0;
162 
163         final List<VolumeInfo> volumes = mStorageManager.getVolumes();
164         Collections.sort(volumes, VolumeInfo.getDescriptionComparator());
165 
166         for (VolumeInfo vol : volumes) {
167             if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
168                 final int color = COLOR_PRIVATE[privateCount++ % COLOR_PRIVATE.length];
169                 mInternalCategory.addPreference(
170                         new StorageVolumePreference(context, vol, color, sTotalInternalStorage));
171                 if (vol.isMountedReadable()) {
172                     final File path = vol.getPath();
173                     privateUsedBytes += path.getTotalSpace() - path.getFreeSpace();
174                     if (sTotalInternalStorage > 0) {
175                         privateTotalBytes = sTotalInternalStorage;
176                     } else {
177                         privateTotalBytes += path.getTotalSpace();
178                     }
179                 }
180             } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
181                 mExternalCategory.addPreference(
182                         new StorageVolumePreference(context, vol, COLOR_PUBLIC, 0));
183             }
184         }
185 
186         // Show missing private volumes
187         final List<VolumeRecord> recs = mStorageManager.getVolumeRecords();
188         for (VolumeRecord rec : recs) {
189             if (rec.getType() == VolumeInfo.TYPE_PRIVATE
190                     && mStorageManager.findVolumeByUuid(rec.getFsUuid()) == null) {
191                 // TODO: add actual storage type to record
192                 final Drawable icon = context.getDrawable(R.drawable.ic_sim_sd);
193                 icon.mutate();
194                 icon.setTint(COLOR_PUBLIC);
195 
196                 final Preference pref = new Preference(context);
197                 pref.setKey(rec.getFsUuid());
198                 pref.setTitle(rec.getNickname());
199                 pref.setSummary(com.android.internal.R.string.ext_media_status_missing);
200                 pref.setIcon(icon);
201                 mInternalCategory.addPreference(pref);
202             }
203         }
204 
205         // Show unsupported disks to give a chance to init
206         final List<DiskInfo> disks = mStorageManager.getDisks();
207         for (DiskInfo disk : disks) {
208             if (disk.volumeCount == 0 && disk.size > 0) {
209                 final Preference pref = new Preference(context);
210                 pref.setKey(disk.getId());
211                 pref.setTitle(disk.getDescription());
212                 pref.setSummary(com.android.internal.R.string.ext_media_status_unsupported);
213                 pref.setIcon(R.drawable.ic_sim_sd);
214                 mExternalCategory.addPreference(pref);
215             }
216         }
217 
218         final BytesResult result = Formatter.formatBytes(getResources(), privateUsedBytes, 0);
219         mInternalSummary.setTitle(TextUtils.expandTemplate(getText(R.string.storage_size_large),
220                 result.value, result.units));
221         mInternalSummary.setSummary(getString(R.string.storage_volume_used_total,
222                 Formatter.formatFileSize(context, privateTotalBytes)));
223         if (mInternalCategory.getPreferenceCount() > 0) {
224             getPreferenceScreen().addPreference(mInternalCategory);
225         }
226         if (mExternalCategory.getPreferenceCount() > 0) {
227             getPreferenceScreen().addPreference(mExternalCategory);
228         }
229 
230         if (mInternalCategory.getPreferenceCount() == 2
231                 && mExternalCategory.getPreferenceCount() == 0) {
232             // Only showing primary internal storage, so just shortcut
233             final Bundle args = new Bundle();
234             args.putString(VolumeInfo.EXTRA_VOLUME_ID, VolumeInfo.ID_PRIVATE_INTERNAL);
235             PrivateVolumeSettings.setVolumeSize(args, sTotalInternalStorage);
236             Intent intent = Utils.onBuildStartFragmentIntent(getActivity(),
237                     PrivateVolumeSettings.class.getName(), args, null, R.string.apps_storage, null,
238                     false);
239             intent.putExtra(SettingsDrawerActivity.EXTRA_SHOW_MENU, true);
240             getActivity().startActivity(intent);
241             finish();
242         }
243     }
244 
245     @Override
onResume()246     public void onResume() {
247         super.onResume();
248         mStorageManager.registerListener(mStorageListener);
249         refresh();
250     }
251 
252     @Override
onPause()253     public void onPause() {
254         super.onPause();
255         mStorageManager.unregisterListener(mStorageListener);
256     }
257 
258     @Override
onPreferenceTreeClick(Preference pref)259     public boolean onPreferenceTreeClick(Preference pref) {
260         final String key = pref.getKey();
261         if (pref instanceof StorageVolumePreference) {
262             // Picked a normal volume
263             final VolumeInfo vol = mStorageManager.findVolumeById(key);
264 
265             if (vol == null) {
266                 return false;
267             }
268 
269             if (vol.getState() == VolumeInfo.STATE_UNMOUNTED) {
270                 VolumeUnmountedFragment.show(this, vol.getId());
271                 return true;
272             } else if (vol.getState() == VolumeInfo.STATE_UNMOUNTABLE) {
273                 DiskInitFragment.show(this, R.string.storage_dialog_unmountable, vol.getDiskId());
274                 return true;
275             }
276 
277             if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
278                 final Bundle args = new Bundle();
279                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
280                 PrivateVolumeSettings.setVolumeSize(args, sTotalInternalStorage);
281                 startFragment(this, PrivateVolumeSettings.class.getCanonicalName(),
282                         -1, 0, args);
283                 return true;
284 
285             } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
286                 if (vol.isMountedReadable()) {
287                     startActivity(vol.buildBrowseIntent());
288                     return true;
289                 } else {
290                     final Bundle args = new Bundle();
291                     args.putString(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
292                     startFragment(this, PublicVolumeSettings.class.getCanonicalName(),
293                             -1, 0, args);
294                     return true;
295                 }
296             }
297 
298         } else if (key.startsWith("disk:")) {
299             // Picked an unsupported disk
300             DiskInitFragment.show(this, R.string.storage_dialog_unsupported, key);
301             return true;
302 
303         } else {
304             // Picked a missing private volume
305             final Bundle args = new Bundle();
306             args.putString(VolumeRecord.EXTRA_FS_UUID, key);
307             startFragment(this, PrivateVolumeForget.class.getCanonicalName(),
308                     R.string.storage_menu_forget, 0, args);
309             return true;
310         }
311 
312         return false;
313     }
314 
315     public static class MountTask extends AsyncTask<Void, Void, Exception> {
316         private final Context mContext;
317         private final StorageManager mStorageManager;
318         private final String mVolumeId;
319         private final String mDescription;
320 
MountTask(Context context, VolumeInfo volume)321         public MountTask(Context context, VolumeInfo volume) {
322             mContext = context.getApplicationContext();
323             mStorageManager = mContext.getSystemService(StorageManager.class);
324             mVolumeId = volume.getId();
325             mDescription = mStorageManager.getBestVolumeDescription(volume);
326         }
327 
328         @Override
doInBackground(Void... params)329         protected Exception doInBackground(Void... params) {
330             try {
331                 mStorageManager.mount(mVolumeId);
332                 return null;
333             } catch (Exception e) {
334                 return e;
335             }
336         }
337 
338         @Override
onPostExecute(Exception e)339         protected void onPostExecute(Exception e) {
340             if (e == null) {
341                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_success,
342                         mDescription), Toast.LENGTH_SHORT).show();
343             } else {
344                 Log.e(TAG, "Failed to mount " + mVolumeId, e);
345                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_failure,
346                         mDescription), Toast.LENGTH_SHORT).show();
347             }
348         }
349     }
350 
351     public static class UnmountTask extends AsyncTask<Void, Void, Exception> {
352         private final Context mContext;
353         private final StorageManager mStorageManager;
354         private final String mVolumeId;
355         private final String mDescription;
356 
UnmountTask(Context context, VolumeInfo volume)357         public UnmountTask(Context context, VolumeInfo volume) {
358             mContext = context.getApplicationContext();
359             mStorageManager = mContext.getSystemService(StorageManager.class);
360             mVolumeId = volume.getId();
361             mDescription = mStorageManager.getBestVolumeDescription(volume);
362         }
363 
364         @Override
doInBackground(Void... params)365         protected Exception doInBackground(Void... params) {
366             try {
367                 mStorageManager.unmount(mVolumeId);
368                 return null;
369             } catch (Exception e) {
370                 return e;
371             }
372         }
373 
374         @Override
onPostExecute(Exception e)375         protected void onPostExecute(Exception e) {
376             if (e == null) {
377                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_success,
378                         mDescription), Toast.LENGTH_SHORT).show();
379             } else {
380                 Log.e(TAG, "Failed to unmount " + mVolumeId, e);
381                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_failure,
382                         mDescription), Toast.LENGTH_SHORT).show();
383             }
384         }
385     }
386 
387     public static class VolumeUnmountedFragment extends DialogFragment {
show(Fragment parent, String volumeId)388         public static void show(Fragment parent, String volumeId) {
389             final Bundle args = new Bundle();
390             args.putString(VolumeInfo.EXTRA_VOLUME_ID, volumeId);
391 
392             final VolumeUnmountedFragment dialog = new VolumeUnmountedFragment();
393             dialog.setArguments(args);
394             dialog.setTargetFragment(parent, 0);
395             dialog.show(parent.getFragmentManager(), TAG_VOLUME_UNMOUNTED);
396         }
397 
398         @Override
onCreateDialog(Bundle savedInstanceState)399         public Dialog onCreateDialog(Bundle savedInstanceState) {
400             final Context context = getActivity();
401             final StorageManager sm = context.getSystemService(StorageManager.class);
402 
403             final String volumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
404             final VolumeInfo vol = sm.findVolumeById(volumeId);
405 
406             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
407             builder.setMessage(TextUtils.expandTemplate(
408                     getText(R.string.storage_dialog_unmounted), vol.getDisk().getDescription()));
409 
410             builder.setPositiveButton(R.string.storage_menu_mount,
411                     new DialogInterface.OnClickListener() {
412                 @Override
413                 public void onClick(DialogInterface dialog, int which) {
414                     EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(
415                             getActivity(), UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
416                             UserHandle.myUserId());
417                     boolean hasBaseUserRestriction = RestrictedLockUtils.hasBaseUserRestriction(
418                             getActivity(), UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
419                             UserHandle.myUserId());
420                     if (admin != null && !hasBaseUserRestriction) {
421                         RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), admin);
422                         return;
423                     }
424                     new MountTask(context, vol).execute();
425                 }
426             });
427             builder.setNegativeButton(R.string.cancel, null);
428 
429             return builder.create();
430         }
431     }
432 
433     public static class DiskInitFragment extends DialogFragment {
show(Fragment parent, int resId, String diskId)434         public static void show(Fragment parent, int resId, String diskId) {
435             final Bundle args = new Bundle();
436             args.putInt(Intent.EXTRA_TEXT, resId);
437             args.putString(DiskInfo.EXTRA_DISK_ID, diskId);
438 
439             final DiskInitFragment dialog = new DiskInitFragment();
440             dialog.setArguments(args);
441             dialog.setTargetFragment(parent, 0);
442             dialog.show(parent.getFragmentManager(), TAG_DISK_INIT);
443         }
444 
445         @Override
onCreateDialog(Bundle savedInstanceState)446         public Dialog onCreateDialog(Bundle savedInstanceState) {
447             final Context context = getActivity();
448             final StorageManager sm = context.getSystemService(StorageManager.class);
449 
450             final int resId = getArguments().getInt(Intent.EXTRA_TEXT);
451             final String diskId = getArguments().getString(DiskInfo.EXTRA_DISK_ID);
452             final DiskInfo disk = sm.findDiskById(diskId);
453 
454             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
455             builder.setMessage(TextUtils.expandTemplate(getText(resId), disk.getDescription()));
456 
457             builder.setPositiveButton(R.string.storage_menu_set_up,
458                     new DialogInterface.OnClickListener() {
459                 @Override
460                 public void onClick(DialogInterface dialog, int which) {
461                     final Intent intent = new Intent(context, StorageWizardInit.class);
462                     intent.putExtra(DiskInfo.EXTRA_DISK_ID, diskId);
463                     startActivity(intent);
464                 }
465             });
466             builder.setNegativeButton(R.string.cancel, null);
467 
468             return builder.create();
469         }
470     }
471 
472     private static class SummaryProvider implements SummaryLoader.SummaryProvider {
473         private final Context mContext;
474         private final SummaryLoader mLoader;
475 
SummaryProvider(Context context, SummaryLoader loader)476         private SummaryProvider(Context context, SummaryLoader loader) {
477             mContext = context;
478             mLoader = loader;
479         }
480 
481         @Override
setListening(boolean listening)482         public void setListening(boolean listening) {
483             if (listening) {
484                 updateSummary();
485             }
486         }
487 
updateSummary()488         private void updateSummary() {
489             // TODO: Register listener.
490             final StorageManager storageManager = mContext.getSystemService(StorageManager.class);
491             if (sTotalInternalStorage <= 0) {
492                 sTotalInternalStorage = storageManager.getPrimaryStorageSize();
493             }
494             final List<VolumeInfo> volumes = storageManager.getVolumes();
495             long privateFreeBytes = 0;
496             long privateTotalBytes = 0;
497             for (VolumeInfo info : volumes) {
498                 if (info.getType() != VolumeInfo.TYPE_PUBLIC
499                         && info.getType() != VolumeInfo.TYPE_PRIVATE) {
500                     continue;
501                 }
502                 final File path = info.getPath();
503                 if (path == null) {
504                     continue;
505                 }
506                 if (info.getType() == VolumeInfo.TYPE_PRIVATE && sTotalInternalStorage > 0) {
507                     privateTotalBytes = sTotalInternalStorage;
508                 } else {
509                     privateTotalBytes += path.getTotalSpace();
510                 }
511                 privateFreeBytes += path.getFreeSpace();
512             }
513             long privateUsedBytes = privateTotalBytes - privateFreeBytes;
514             mLoader.setSummary(this, mContext.getString(R.string.storage_summary,
515                     Formatter.formatFileSize(mContext, privateUsedBytes),
516                     Formatter.formatFileSize(mContext, privateTotalBytes)));
517         }
518     }
519 
520     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
521             = new SummaryLoader.SummaryProviderFactory() {
522         @Override
523         public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
524                                                                    SummaryLoader summaryLoader) {
525             return new SummaryProvider(activity, summaryLoader);
526         }
527     };
528 
529     /**
530      * Enable indexing of searchable data
531      */
532     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
533         new BaseSearchIndexProvider() {
534             @Override
535             public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
536                 final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
537 
538                 SearchIndexableRaw data = new SearchIndexableRaw(context);
539                 data.title = context.getString(R.string.storage_settings);
540                 data.screenTitle = context.getString(R.string.storage_settings);
541                 result.add(data);
542 
543                 data = new SearchIndexableRaw(context);
544                 data.title = context.getString(R.string.internal_storage);
545                 data.screenTitle = context.getString(R.string.storage_settings);
546                 result.add(data);
547 
548                 data = new SearchIndexableRaw(context);
549                 final StorageManager storage = context.getSystemService(StorageManager.class);
550                 final List<VolumeInfo> vols = storage.getVolumes();
551                 for (VolumeInfo vol : vols) {
552                     if (isInteresting(vol)) {
553                         data.title = storage.getBestVolumeDescription(vol);
554                         data.screenTitle = context.getString(R.string.storage_settings);
555                         result.add(data);
556                     }
557                 }
558 
559                 data = new SearchIndexableRaw(context);
560                 data.title = context.getString(R.string.memory_size);
561                 data.screenTitle = context.getString(R.string.storage_settings);
562                 result.add(data);
563 
564                 data = new SearchIndexableRaw(context);
565                 data.title = context.getString(R.string.memory_available);
566                 data.screenTitle = context.getString(R.string.storage_settings);
567                 result.add(data);
568 
569                 data = new SearchIndexableRaw(context);
570                 data.title = context.getString(R.string.memory_apps_usage);
571                 data.screenTitle = context.getString(R.string.storage_settings);
572                 result.add(data);
573 
574                 data = new SearchIndexableRaw(context);
575                 data.title = context.getString(R.string.memory_dcim_usage);
576                 data.screenTitle = context.getString(R.string.storage_settings);
577                 result.add(data);
578 
579                 data = new SearchIndexableRaw(context);
580                 data.title = context.getString(R.string.memory_music_usage);
581                 data.screenTitle = context.getString(R.string.storage_settings);
582                 result.add(data);
583 
584                 data = new SearchIndexableRaw(context);
585                 data.title = context.getString(R.string.memory_downloads_usage);
586                 data.screenTitle = context.getString(R.string.storage_settings);
587                 result.add(data);
588 
589                 data = new SearchIndexableRaw(context);
590                 data.title = context.getString(R.string.memory_media_cache_usage);
591                 data.screenTitle = context.getString(R.string.storage_settings);
592                 result.add(data);
593 
594                 data = new SearchIndexableRaw(context);
595                 data.title = context.getString(R.string.memory_media_misc_usage);
596                 data.screenTitle = context.getString(R.string.storage_settings);
597                 result.add(data);
598 
599                 return result;
600             }
601         };
602 }
603