• 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 static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
20 
21 import android.app.Dialog;
22 import android.app.settings.SettingsEnums;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.os.AsyncTask;
27 import android.os.Bundle;
28 import android.os.UserHandle;
29 import android.os.UserManager;
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.text.TextUtils;
36 import android.text.format.Formatter;
37 import android.text.format.Formatter.BytesResult;
38 import android.util.Log;
39 import android.widget.Toast;
40 
41 import androidx.annotation.NonNull;
42 import androidx.annotation.VisibleForTesting;
43 import androidx.appcompat.app.AlertDialog;
44 import androidx.fragment.app.Fragment;
45 import androidx.preference.Preference;
46 import androidx.preference.PreferenceCategory;
47 
48 import com.android.settings.R;
49 import com.android.settings.SettingsPreferenceFragment;
50 import com.android.settings.core.SubSettingLauncher;
51 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
52 import com.android.settings.search.BaseSearchIndexProvider;
53 import com.android.settingslib.RestrictedLockUtils;
54 import com.android.settingslib.RestrictedLockUtilsInternal;
55 import com.android.settingslib.deviceinfo.PrivateStorageInfo;
56 import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
57 import com.android.settingslib.search.Indexable;
58 import com.android.settingslib.search.SearchIndexable;
59 import com.android.settingslib.search.SearchIndexableRaw;
60 
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.List;
64 
65 /**
66  * Panel showing both internal storage (both built-in storage and private
67  * volumes) and removable storage (public volumes).
68  */
69 @SearchIndexable
70 public class StorageSettings extends SettingsPreferenceFragment implements Indexable {
71     static final String TAG = "StorageSettings";
72 
73     private static final String KEY_STORAGE_SETTINGS = "storage_settings";
74     private static final String KEY_INTERNAL_STORAGE = "storage_settings_internal_storage";
75     private static final String KEY_STORAGE_SETTINGS_VOLUME = "storage_settings_volume_";
76     private static final String KEY_STORAGE_SETTINGS_MEMORY_SIZE = "storage_settings_memory_size";
77     private static final String KEY_STORAGE_SETTINGS_MEMORY = "storage_settings_memory_available";
78     private static final String KEY_STORAGE_SETTINGS_DCIM = "storage_settings_dcim_space";
79     private static final String KEY_STORAGE_SETTINGS_MUSIC = "storage_settings_music_space";
80     private static final String KEY_STORAGE_SETTINGS_MISC = "storage_settings_misc_space";
81     private static final String KEY_STORAGE_SETTINGS_FREE_SPACE = "storage_settings_free_space";
82 
83     private static final String TAG_VOLUME_UNMOUNTED = "volume_unmounted";
84     private static final String TAG_DISK_INIT = "disk_init";
85     private static final int METRICS_CATEGORY = SettingsEnums.DEVICEINFO_STORAGE;
86 
87     private StorageManager mStorageManager;
88 
89     private PreferenceCategory mInternalCategory;
90     private PreferenceCategory mExternalCategory;
91 
92     private StorageSummaryPreference mInternalSummary;
93     private static long sTotalInternalStorage;
94 
95     private boolean mHasLaunchedPrivateVolumeSettings = false;
96 
97     @Override
getMetricsCategory()98     public int getMetricsCategory() {
99         return METRICS_CATEGORY;
100     }
101 
102     @Override
getHelpResource()103     public int getHelpResource() {
104         return R.string.help_uri_storage;
105     }
106 
107     @Override
onCreate(Bundle icicle)108     public void onCreate(Bundle icicle) {
109         super.onCreate(icicle);
110 
111         final Context context = getActivity();
112 
113         mStorageManager = context.getSystemService(StorageManager.class);
114 
115         if (sTotalInternalStorage <= 0) {
116             sTotalInternalStorage = mStorageManager.getPrimaryStorageSize();
117         }
118 
119         addPreferencesFromResource(R.xml.device_info_storage);
120 
121         mInternalCategory = (PreferenceCategory) findPreference("storage_internal");
122         mExternalCategory = (PreferenceCategory) findPreference("storage_external");
123 
124         mInternalSummary = new StorageSummaryPreference(getPrefContext());
125 
126         setHasOptionsMenu(true);
127     }
128 
129     private final StorageEventListener mStorageListener = new StorageEventListener() {
130         @Override
131         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
132             if (isInteresting(vol)) {
133                 refresh();
134             }
135         }
136 
137         @Override
138         public void onDiskDestroyed(DiskInfo disk) {
139             refresh();
140         }
141     };
142 
isInteresting(VolumeInfo vol)143     private static boolean isInteresting(VolumeInfo vol) {
144         switch (vol.getType()) {
145             case VolumeInfo.TYPE_PRIVATE:
146             case VolumeInfo.TYPE_PUBLIC:
147             case VolumeInfo.TYPE_STUB:
148                 return true;
149             default:
150                 return false;
151         }
152     }
153 
refresh()154     private synchronized void refresh() {
155         final Context context = getPrefContext();
156 
157         getPreferenceScreen().removeAll();
158         mInternalCategory.removeAll();
159         mExternalCategory.removeAll();
160 
161         mInternalCategory.addPreference(mInternalSummary);
162 
163         final StorageManagerVolumeProvider smvp = new StorageManagerVolumeProvider(mStorageManager);
164         final PrivateStorageInfo info = PrivateStorageInfo.getPrivateStorageInfo(smvp);
165         final long privateTotalBytes = info.totalBytes;
166         final long privateUsedBytes = info.totalBytes - info.freeBytes;
167 
168         final List<VolumeInfo> volumes = mStorageManager.getVolumes();
169         Collections.sort(volumes, VolumeInfo.getDescriptionComparator());
170 
171         for (VolumeInfo vol : volumes) {
172             if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
173 
174                 if (vol.getState() == VolumeInfo.STATE_UNMOUNTABLE) {
175                     mInternalCategory.addPreference(
176                             new StorageVolumePreference(context, vol, 0));
177                 } else {
178                     final long volumeTotalBytes = PrivateStorageInfo.getTotalSize(vol,
179                             sTotalInternalStorage);
180                     mInternalCategory.addPreference(
181                             new StorageVolumePreference(context, vol, volumeTotalBytes));
182                 }
183             } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC
184                     || vol.getType() == VolumeInfo.TYPE_STUB) {
185                 mExternalCategory.addPreference(
186                         new StorageVolumePreference(context, vol, 0));
187             }
188         }
189 
190         // Show missing private volumes
191         final List<VolumeRecord> recs = mStorageManager.getVolumeRecords();
192         for (VolumeRecord rec : recs) {
193             if (rec.getType() == VolumeInfo.TYPE_PRIVATE
194                     && mStorageManager.findVolumeByUuid(rec.getFsUuid()) == null) {
195                 // TODO: add actual storage type to record
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(R.drawable.ic_sim_sd);
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             if (!mHasLaunchedPrivateVolumeSettings) {
234                 mHasLaunchedPrivateVolumeSettings = true;
235                 final Bundle args = new Bundle();
236                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, VolumeInfo.ID_PRIVATE_INTERNAL);
237                 new SubSettingLauncher(getActivity())
238                         .setDestination(StorageDashboardFragment.class.getName())
239                         .setArguments(args)
240                         .setTitleRes(R.string.storage_settings)
241                         .setSourceMetricsCategory(getMetricsCategory())
242                         .launch();
243                 finish();
244             }
245         }
246     }
247 
248     @Override
onResume()249     public void onResume() {
250         super.onResume();
251         mStorageManager.registerListener(mStorageListener);
252         refresh();
253     }
254 
255     @Override
onPause()256     public void onPause() {
257         super.onPause();
258         mStorageManager.unregisterListener(mStorageListener);
259     }
260 
261     @Override
onPreferenceTreeClick(Preference pref)262     public boolean onPreferenceTreeClick(Preference pref) {
263         final String key = pref.getKey();
264         if (pref instanceof StorageVolumePreference) {
265             // Picked a normal volume
266             final VolumeInfo vol = mStorageManager.findVolumeById(key);
267 
268             if (vol == null) {
269                 return false;
270             }
271 
272             if (vol.getState() == VolumeInfo.STATE_UNMOUNTED) {
273                 VolumeUnmountedFragment.show(this, vol.getId());
274                 return true;
275             } else if (vol.getState() == VolumeInfo.STATE_UNMOUNTABLE) {
276                 DiskInitFragment.show(this, R.string.storage_dialog_unmountable, vol.getDiskId());
277                 return true;
278             }
279 
280             if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
281                 final Bundle args = new Bundle();
282                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
283 
284                 if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
285                     new SubSettingLauncher(getContext())
286                             .setDestination(StorageDashboardFragment.class.getCanonicalName())
287                             .setTitleRes(R.string.storage_settings)
288                             .setSourceMetricsCategory(getMetricsCategory())
289                             .setArguments(args)
290                             .launch();
291                 } else {
292                     // TODO: Go to the StorageDashboardFragment once it fully handles all of the
293                     //       SD card cases and other private internal storage cases.
294                     PrivateVolumeSettings.setVolumeSize(args, PrivateStorageInfo.getTotalSize(vol,
295                             sTotalInternalStorage));
296                     new SubSettingLauncher(getContext())
297                             .setDestination(PrivateVolumeSettings.class.getCanonicalName())
298                             .setTitleRes(-1)
299                             .setSourceMetricsCategory(getMetricsCategory())
300                             .setArguments(args)
301                             .launch();
302                 }
303 
304                 return true;
305 
306             } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
307                 return handlePublicVolumeClick(getContext(), vol);
308             } else if (vol.getType() == VolumeInfo.TYPE_STUB) {
309                 return handleStubVolumeClick(getContext(), vol);
310             }
311 
312         } else if (key.startsWith("disk:")) {
313             // Picked an unsupported disk
314             DiskInitFragment.show(this, R.string.storage_dialog_unsupported, key);
315             return true;
316 
317         } else {
318             // Picked a missing private volume
319             final Bundle args = new Bundle();
320             args.putString(VolumeRecord.EXTRA_FS_UUID, key);
321             new SubSettingLauncher(getContext())
322                     .setDestination(PrivateVolumeForget.class.getCanonicalName())
323                     .setTitleRes(R.string.storage_menu_forget)
324                     .setSourceMetricsCategory(getMetricsCategory())
325                     .setArguments(args)
326                     .launch();
327             return true;
328         }
329 
330         return false;
331     }
332 
333     @VisibleForTesting
handleStubVolumeClick(Context context, VolumeInfo vol)334     static boolean handleStubVolumeClick(Context context, VolumeInfo vol) {
335         final Intent intent = vol.buildBrowseIntent();
336         if (vol.isMountedReadable() && intent != null) {
337             context.startActivity(intent);
338             return true;
339         }
340         return false;
341     }
342 
343     @VisibleForTesting
handlePublicVolumeClick(Context context, VolumeInfo vol)344     static boolean handlePublicVolumeClick(Context context, VolumeInfo vol) {
345         final Intent intent = vol.buildBrowseIntent();
346         if (vol.isMountedReadable() && intent != null) {
347             context.startActivity(intent);
348             return true;
349         } else {
350             final Bundle args = new Bundle();
351             args.putString(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
352             new SubSettingLauncher(context)
353                     .setDestination(PublicVolumeSettings.class.getCanonicalName())
354                     .setTitleRes(-1)
355                     .setSourceMetricsCategory(METRICS_CATEGORY)
356                     .setArguments(args)
357                     .launch();
358             return true;
359         }
360     }
361 
362     public static class MountTask extends AsyncTask<Void, Void, Exception> {
363         private final Context mContext;
364         private final StorageManager mStorageManager;
365         private final String mVolumeId;
366         private final String mDescription;
367 
MountTask(Context context, VolumeInfo volume)368         public MountTask(Context context, VolumeInfo volume) {
369             mContext = context.getApplicationContext();
370             mStorageManager = mContext.getSystemService(StorageManager.class);
371             mVolumeId = volume.getId();
372             mDescription = mStorageManager.getBestVolumeDescription(volume);
373         }
374 
375         @Override
doInBackground(Void... params)376         protected Exception doInBackground(Void... params) {
377             try {
378                 mStorageManager.mount(mVolumeId);
379                 return null;
380             } catch (Exception e) {
381                 return e;
382             }
383         }
384 
385         @Override
onPostExecute(Exception e)386         protected void onPostExecute(Exception e) {
387             if (e == null) {
388                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_success,
389                         mDescription), Toast.LENGTH_SHORT).show();
390             } else {
391                 Log.e(TAG, "Failed to mount " + mVolumeId, e);
392                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_failure,
393                         mDescription), Toast.LENGTH_SHORT).show();
394             }
395         }
396     }
397 
398     public static class UnmountTask extends AsyncTask<Void, Void, Exception> {
399         private final Context mContext;
400         private final StorageManager mStorageManager;
401         private final String mVolumeId;
402         private final String mDescription;
403 
UnmountTask(Context context, VolumeInfo volume)404         public UnmountTask(Context context, VolumeInfo volume) {
405             mContext = context.getApplicationContext();
406             mStorageManager = mContext.getSystemService(StorageManager.class);
407             mVolumeId = volume.getId();
408             mDescription = mStorageManager.getBestVolumeDescription(volume);
409         }
410 
411         @Override
doInBackground(Void... params)412         protected Exception doInBackground(Void... params) {
413             try {
414                 mStorageManager.unmount(mVolumeId);
415                 return null;
416             } catch (Exception e) {
417                 return e;
418             }
419         }
420 
421         @Override
onPostExecute(Exception e)422         protected void onPostExecute(Exception e) {
423             if (e == null) {
424                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_success,
425                         mDescription), Toast.LENGTH_SHORT).show();
426             } else {
427                 Log.e(TAG, "Failed to unmount " + mVolumeId, e);
428                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_failure,
429                         mDescription), Toast.LENGTH_SHORT).show();
430             }
431         }
432     }
433 
434     public static class VolumeUnmountedFragment extends InstrumentedDialogFragment {
show(Fragment parent, String volumeId)435         public static void show(Fragment parent, String volumeId) {
436             final Bundle args = new Bundle();
437             args.putString(VolumeInfo.EXTRA_VOLUME_ID, volumeId);
438 
439             final VolumeUnmountedFragment dialog = new VolumeUnmountedFragment();
440             dialog.setArguments(args);
441             dialog.setTargetFragment(parent, 0);
442             dialog.show(parent.getFragmentManager(), TAG_VOLUME_UNMOUNTED);
443         }
444 
445         @Override
getMetricsCategory()446         public int getMetricsCategory() {
447             return SettingsEnums.DIALOG_VOLUME_UNMOUNT;
448         }
449 
450         @Override
onCreateDialog(Bundle savedInstanceState)451         public Dialog onCreateDialog(Bundle savedInstanceState) {
452             final Context context = getActivity();
453             final StorageManager sm = context.getSystemService(StorageManager.class);
454 
455             final String volumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
456             final VolumeInfo vol = sm.findVolumeById(volumeId);
457 
458             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
459             builder.setMessage(TextUtils.expandTemplate(
460                     getText(R.string.storage_dialog_unmounted), vol.getDisk().getDescription()));
461 
462             builder.setPositiveButton(R.string.storage_menu_mount,
463                     new DialogInterface.OnClickListener() {
464                         /**
465                          * Check if an {@link
466                          * RestrictedLockUtils#sendShowAdminSupportDetailsIntent admin
467                          * details intent} should be shown for the restriction and show it.
468                          *
469                          * @param restriction The restriction to check
470                          * @return {@code true} iff a intent was shown.
471                          */
472                         private boolean wasAdminSupportIntentShown(@NonNull String restriction) {
473                             EnforcedAdmin admin = RestrictedLockUtilsInternal
474                                     .checkIfRestrictionEnforced(getActivity(), restriction,
475                                             UserHandle.myUserId());
476                             boolean hasBaseUserRestriction =
477                                     RestrictedLockUtilsInternal.hasBaseUserRestriction(
478                                             getActivity(), restriction, UserHandle.myUserId());
479                             if (admin != null && !hasBaseUserRestriction) {
480                                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
481                                         admin);
482                                 return true;
483                             }
484 
485                             return false;
486                         }
487 
488                         @Override
489                         public void onClick(DialogInterface dialog, int which) {
490                             if (wasAdminSupportIntentShown(
491                                     UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA)) {
492                                 return;
493                             }
494 
495                             if (vol.disk != null && vol.disk.isUsb() &&
496                                     wasAdminSupportIntentShown(
497                                             UserManager.DISALLOW_USB_FILE_TRANSFER)) {
498                                 return;
499                             }
500 
501                             new MountTask(context, vol).execute();
502                         }
503                     });
504             builder.setNegativeButton(R.string.cancel, null);
505 
506             return builder.create();
507         }
508     }
509 
510     public static class DiskInitFragment extends InstrumentedDialogFragment {
511         @Override
getMetricsCategory()512         public int getMetricsCategory() {
513             return SettingsEnums.DIALOG_VOLUME_INIT;
514         }
515 
show(Fragment parent, int resId, String diskId)516         public static void show(Fragment parent, int resId, String diskId) {
517             final Bundle args = new Bundle();
518             args.putInt(Intent.EXTRA_TEXT, resId);
519             args.putString(DiskInfo.EXTRA_DISK_ID, diskId);
520 
521             final DiskInitFragment dialog = new DiskInitFragment();
522             dialog.setArguments(args);
523             dialog.setTargetFragment(parent, 0);
524             dialog.show(parent.getFragmentManager(), TAG_DISK_INIT);
525         }
526 
527         @Override
onCreateDialog(Bundle savedInstanceState)528         public Dialog onCreateDialog(Bundle savedInstanceState) {
529             final Context context = getActivity();
530             final StorageManager sm = context.getSystemService(StorageManager.class);
531 
532             final int resId = getArguments().getInt(Intent.EXTRA_TEXT);
533             final String diskId = getArguments().getString(DiskInfo.EXTRA_DISK_ID);
534             final DiskInfo disk = sm.findDiskById(diskId);
535 
536             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
537             builder.setMessage(TextUtils.expandTemplate(getText(resId), disk.getDescription()));
538 
539             builder.setPositiveButton(R.string.storage_menu_set_up,
540                     new DialogInterface.OnClickListener() {
541                         @Override
542                         public void onClick(DialogInterface dialog, int which) {
543                             final Intent intent = new Intent(context, StorageWizardInit.class);
544                             intent.putExtra(DiskInfo.EXTRA_DISK_ID, diskId);
545                             startActivity(intent);
546                         }
547                     });
548             builder.setNegativeButton(R.string.cancel, null);
549 
550             return builder.create();
551         }
552     }
553 
554     /** Enable indexing of searchable data */
555     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
556             new BaseSearchIndexProvider() {
557                 @Override
558                 public List<SearchIndexableRaw> getRawDataToIndex(
559                         Context context, boolean enabled) {
560                     final List<SearchIndexableRaw> result = new ArrayList<>();
561 
562                     SearchIndexableRaw data = new SearchIndexableRaw(context);
563                     data.title = context.getString(R.string.storage_settings);
564                     data.key = KEY_STORAGE_SETTINGS;
565                     data.screenTitle = context.getString(R.string.storage_settings);
566                     data.keywords = context.getString(R.string.keywords_storage_settings);
567                     result.add(data);
568 
569                     data = new SearchIndexableRaw(context);
570                     data.title = context.getString(R.string.internal_storage);
571                     data.key = KEY_INTERNAL_STORAGE;
572                     data.screenTitle = context.getString(R.string.storage_settings);
573                     result.add(data);
574 
575                     data = new SearchIndexableRaw(context);
576                     final StorageManager storage = context.getSystemService(StorageManager.class);
577                     final List<VolumeInfo> vols = storage.getVolumes();
578                     for (VolumeInfo vol : vols) {
579                         if (isInteresting(vol)) {
580                             data.title = storage.getBestVolumeDescription(vol);
581                             data.key = KEY_STORAGE_SETTINGS_VOLUME + vol.id;
582                             data.screenTitle = context.getString(R.string.storage_settings);
583                             result.add(data);
584                         }
585                     }
586 
587                     data = new SearchIndexableRaw(context);
588                     data.title = context.getString(R.string.memory_size);
589                     data.key = KEY_STORAGE_SETTINGS_MEMORY_SIZE;
590                     data.screenTitle = context.getString(R.string.storage_settings);
591                     result.add(data);
592 
593                     data = new SearchIndexableRaw(context);
594                     data.title = context.getString(R.string.memory_available);
595                     data.key = KEY_STORAGE_SETTINGS_MEMORY;
596                     data.screenTitle = context.getString(R.string.storage_settings);
597                     result.add(data);
598 
599                     data = new SearchIndexableRaw(context);
600                     data.title = context.getString(R.string.memory_dcim_usage);
601                     data.key = KEY_STORAGE_SETTINGS_DCIM;
602                     data.screenTitle = context.getString(R.string.storage_settings);
603                     result.add(data);
604 
605                     data = new SearchIndexableRaw(context);
606                     data.title = context.getString(R.string.memory_music_usage);
607                     data.key = KEY_STORAGE_SETTINGS_MUSIC;
608                     data.screenTitle = context.getString(R.string.storage_settings);
609                     result.add(data);
610 
611                     data = new SearchIndexableRaw(context);
612                     data.title = context.getString(R.string.memory_media_misc_usage);
613                     data.key = KEY_STORAGE_SETTINGS_MISC;
614                     data.screenTitle = context.getString(R.string.storage_settings);
615                     result.add(data);
616 
617                     data = new SearchIndexableRaw(context);
618                     data.title = context.getString(R.string.storage_menu_free);
619                     data.key = KEY_STORAGE_SETTINGS_FREE_SPACE;
620                     data.screenTitle = context.getString(R.string.storage_menu_free);
621                     data.intentAction = StorageManager.ACTION_MANAGE_STORAGE;
622                     data.keywords = context.getString(R.string.keywords_storage_menu_free);
623                     result.add(data);
624 
625                     return result;
626                 }
627 
628                 @Override
629                 public List<String> getNonIndexableKeys(Context context) {
630                     final List<String> niks = super.getNonIndexableKeys(context);
631                     if (isExternalExist(context)) {
632                         niks.add(KEY_STORAGE_SETTINGS);
633                         niks.add(KEY_INTERNAL_STORAGE);
634                         niks.add(KEY_STORAGE_SETTINGS_MEMORY_SIZE);
635                         niks.add(KEY_STORAGE_SETTINGS_MEMORY);
636                         niks.add(KEY_STORAGE_SETTINGS_DCIM);
637                         niks.add(KEY_STORAGE_SETTINGS_MUSIC);
638                         niks.add(KEY_STORAGE_SETTINGS_MISC);
639                         niks.add(KEY_STORAGE_SETTINGS_FREE_SPACE);
640 
641                         final StorageManager storage = context.getSystemService(
642                                 StorageManager.class);
643                         final List<VolumeInfo> vols = storage.getVolumes();
644                         for (VolumeInfo vol : vols) {
645                             if (isInteresting(vol)) {
646                                 niks.add(KEY_STORAGE_SETTINGS_VOLUME + vol.id);
647                             }
648                         }
649                     }
650                     return niks;
651                 }
652 
653                 @Override
654                 protected boolean isPageSearchEnabled(Context context) {
655                     return !isExternalExist(context);
656                 }
657 
658                 private boolean isExternalExist(Context context) {
659                     int internalCount = 0;
660                     StorageManager storageManager = context.getSystemService(StorageManager.class);
661                     final List<VolumeInfo> volumes = storageManager.getVolumes();
662                     for (VolumeInfo vol : volumes) {
663                         //External storage
664                         if (vol.getType() == VolumeInfo.TYPE_PUBLIC
665                                 || vol.getType() == VolumeInfo.TYPE_STUB) {
666                             return true;
667                         } else if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
668                             internalCount++;
669                         }
670                     }
671 
672                     // Unsupported disks
673                     final List<DiskInfo> disks = storageManager.getDisks();
674                     for (DiskInfo disk : disks) {
675                         if (disk.volumeCount == 0 && disk.size > 0) {
676                             return true;
677                         }
678                     }
679 
680                     // Missing private volumes
681                     final List<VolumeRecord> recs = storageManager.getVolumeRecords();
682                     for (VolumeRecord rec : recs) {
683                         if (rec.getType() == VolumeInfo.TYPE_PRIVATE
684                                 && storageManager.findVolumeByUuid(rec.getFsUuid()) == null) {
685                             internalCount++;
686                         }
687                     }
688 
689                     return (internalCount != 1);
690                 }
691             };
692 }
693