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.settings.search.Indexable;
54 import com.android.settings.search.SearchIndexableRaw;
55 import com.android.settingslib.RestrictedLockUtils;
56 import com.android.settingslib.RestrictedLockUtilsInternal;
57 import com.android.settingslib.deviceinfo.PrivateStorageInfo;
58 import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
59 import com.android.settingslib.search.SearchIndexable;
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 TAG_VOLUME_UNMOUNTED = "volume_unmounted";
74     private static final String TAG_DISK_INIT = "disk_init";
75     private static final int METRICS_CATEGORY = SettingsEnums.DEVICEINFO_STORAGE;
76 
77     private StorageManager mStorageManager;
78 
79     private PreferenceCategory mInternalCategory;
80     private PreferenceCategory mExternalCategory;
81 
82     private StorageSummaryPreference mInternalSummary;
83     private static long sTotalInternalStorage;
84 
85     private boolean mHasLaunchedPrivateVolumeSettings = false;
86 
87     @Override
getMetricsCategory()88     public int getMetricsCategory() {
89         return METRICS_CATEGORY;
90     }
91 
92     @Override
getHelpResource()93     public int getHelpResource() {
94         return R.string.help_uri_storage;
95     }
96 
97     @Override
onCreate(Bundle icicle)98     public void onCreate(Bundle icicle) {
99         super.onCreate(icicle);
100 
101         final Context context = getActivity();
102 
103         mStorageManager = context.getSystemService(StorageManager.class);
104 
105         if (sTotalInternalStorage <= 0) {
106             sTotalInternalStorage = mStorageManager.getPrimaryStorageSize();
107         }
108 
109         addPreferencesFromResource(R.xml.device_info_storage);
110 
111         mInternalCategory = (PreferenceCategory) findPreference("storage_internal");
112         mExternalCategory = (PreferenceCategory) findPreference("storage_external");
113 
114         mInternalSummary = new StorageSummaryPreference(getPrefContext());
115 
116         setHasOptionsMenu(true);
117     }
118 
119     private final StorageEventListener mStorageListener = new StorageEventListener() {
120         @Override
121         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
122             if (isInteresting(vol)) {
123                 refresh();
124             }
125         }
126 
127         @Override
128         public void onDiskDestroyed(DiskInfo disk) {
129             refresh();
130         }
131     };
132 
isInteresting(VolumeInfo vol)133     private static boolean isInteresting(VolumeInfo vol) {
134         switch (vol.getType()) {
135             case VolumeInfo.TYPE_PRIVATE:
136             case VolumeInfo.TYPE_PUBLIC:
137             case VolumeInfo.TYPE_STUB:
138                 return true;
139             default:
140                 return false;
141         }
142     }
143 
refresh()144     private synchronized void refresh() {
145         final Context context = getPrefContext();
146 
147         getPreferenceScreen().removeAll();
148         mInternalCategory.removeAll();
149         mExternalCategory.removeAll();
150 
151         mInternalCategory.addPreference(mInternalSummary);
152 
153         final StorageManagerVolumeProvider smvp = new StorageManagerVolumeProvider(mStorageManager);
154         final PrivateStorageInfo info = PrivateStorageInfo.getPrivateStorageInfo(smvp);
155         final long privateTotalBytes = info.totalBytes;
156         final long privateUsedBytes = info.totalBytes - info.freeBytes;
157 
158         final List<VolumeInfo> volumes = mStorageManager.getVolumes();
159         Collections.sort(volumes, VolumeInfo.getDescriptionComparator());
160 
161         for (VolumeInfo vol : volumes) {
162             if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
163 
164                 if (vol.getState() == VolumeInfo.STATE_UNMOUNTABLE) {
165                     mInternalCategory.addPreference(
166                             new StorageVolumePreference(context, vol, 0));
167                 } else {
168                     final long volumeTotalBytes = PrivateStorageInfo.getTotalSize(vol,
169                             sTotalInternalStorage);
170                     mInternalCategory.addPreference(
171                             new StorageVolumePreference(context, vol, volumeTotalBytes));
172                 }
173             } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC
174                     || vol.getType() == VolumeInfo.TYPE_STUB) {
175                 mExternalCategory.addPreference(
176                         new StorageVolumePreference(context, vol, 0));
177             }
178         }
179 
180         // Show missing private volumes
181         final List<VolumeRecord> recs = mStorageManager.getVolumeRecords();
182         for (VolumeRecord rec : recs) {
183             if (rec.getType() == VolumeInfo.TYPE_PRIVATE
184                     && mStorageManager.findVolumeByUuid(rec.getFsUuid()) == null) {
185                 // TODO: add actual storage type to record
186                 final Preference pref = new Preference(context);
187                 pref.setKey(rec.getFsUuid());
188                 pref.setTitle(rec.getNickname());
189                 pref.setSummary(com.android.internal.R.string.ext_media_status_missing);
190                 pref.setIcon(R.drawable.ic_sim_sd);
191                 mInternalCategory.addPreference(pref);
192             }
193         }
194 
195         // Show unsupported disks to give a chance to init
196         final List<DiskInfo> disks = mStorageManager.getDisks();
197         for (DiskInfo disk : disks) {
198             if (disk.volumeCount == 0 && disk.size > 0) {
199                 final Preference pref = new Preference(context);
200                 pref.setKey(disk.getId());
201                 pref.setTitle(disk.getDescription());
202                 pref.setSummary(com.android.internal.R.string.ext_media_status_unsupported);
203                 pref.setIcon(R.drawable.ic_sim_sd);
204                 mExternalCategory.addPreference(pref);
205             }
206         }
207 
208         final BytesResult result = Formatter.formatBytes(getResources(), privateUsedBytes, 0);
209         mInternalSummary.setTitle(TextUtils.expandTemplate(getText(R.string.storage_size_large),
210                 result.value, result.units));
211         mInternalSummary.setSummary(getString(R.string.storage_volume_used_total,
212                 Formatter.formatFileSize(context, privateTotalBytes)));
213         if (mInternalCategory.getPreferenceCount() > 0) {
214             getPreferenceScreen().addPreference(mInternalCategory);
215         }
216         if (mExternalCategory.getPreferenceCount() > 0) {
217             getPreferenceScreen().addPreference(mExternalCategory);
218         }
219 
220         if (mInternalCategory.getPreferenceCount() == 2
221                 && mExternalCategory.getPreferenceCount() == 0) {
222             // Only showing primary internal storage, so just shortcut
223             if (!mHasLaunchedPrivateVolumeSettings) {
224                 mHasLaunchedPrivateVolumeSettings = true;
225                 final Bundle args = new Bundle();
226                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, VolumeInfo.ID_PRIVATE_INTERNAL);
227                 new SubSettingLauncher(getActivity())
228                         .setDestination(StorageDashboardFragment.class.getName())
229                         .setArguments(args)
230                         .setTitleRes(R.string.storage_settings)
231                         .setSourceMetricsCategory(getMetricsCategory())
232                         .launch();
233                 finish();
234             }
235         }
236     }
237 
238     @Override
onResume()239     public void onResume() {
240         super.onResume();
241         mStorageManager.registerListener(mStorageListener);
242         refresh();
243     }
244 
245     @Override
onPause()246     public void onPause() {
247         super.onPause();
248         mStorageManager.unregisterListener(mStorageListener);
249     }
250 
251     @Override
onPreferenceTreeClick(Preference pref)252     public boolean onPreferenceTreeClick(Preference pref) {
253         final String key = pref.getKey();
254         if (pref instanceof StorageVolumePreference) {
255             // Picked a normal volume
256             final VolumeInfo vol = mStorageManager.findVolumeById(key);
257 
258             if (vol == null) {
259                 return false;
260             }
261 
262             if (vol.getState() == VolumeInfo.STATE_UNMOUNTED) {
263                 VolumeUnmountedFragment.show(this, vol.getId());
264                 return true;
265             } else if (vol.getState() == VolumeInfo.STATE_UNMOUNTABLE) {
266                 DiskInitFragment.show(this, R.string.storage_dialog_unmountable, vol.getDiskId());
267                 return true;
268             }
269 
270             if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
271                 final Bundle args = new Bundle();
272                 args.putString(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
273 
274                 if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
275                     new SubSettingLauncher(getContext())
276                             .setDestination(StorageDashboardFragment.class.getCanonicalName())
277                             .setTitleRes(R.string.storage_settings)
278                             .setSourceMetricsCategory(getMetricsCategory())
279                             .setArguments(args)
280                             .launch();
281                 } else {
282                     // TODO: Go to the StorageDashboardFragment once it fully handles all of the
283                     //       SD card cases and other private internal storage cases.
284                     PrivateVolumeSettings.setVolumeSize(args, PrivateStorageInfo.getTotalSize(vol,
285                             sTotalInternalStorage));
286                     new SubSettingLauncher(getContext())
287                             .setDestination(PrivateVolumeSettings.class.getCanonicalName())
288                             .setTitleRes(-1)
289                             .setSourceMetricsCategory(getMetricsCategory())
290                             .setArguments(args)
291                             .launch();
292                 }
293 
294                 return true;
295 
296             } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
297                 return handlePublicVolumeClick(getContext(), vol);
298             } else if (vol.getType() == VolumeInfo.TYPE_STUB) {
299                 return handleStubVolumeClick(getContext(), vol);
300             }
301 
302         } else if (key.startsWith("disk:")) {
303             // Picked an unsupported disk
304             DiskInitFragment.show(this, R.string.storage_dialog_unsupported, key);
305             return true;
306 
307         } else {
308             // Picked a missing private volume
309             final Bundle args = new Bundle();
310             args.putString(VolumeRecord.EXTRA_FS_UUID, key);
311             new SubSettingLauncher(getContext())
312                     .setDestination(PrivateVolumeForget.class.getCanonicalName())
313                     .setTitleRes(R.string.storage_menu_forget)
314                     .setSourceMetricsCategory(getMetricsCategory())
315                     .setArguments(args)
316                     .launch();
317             return true;
318         }
319 
320         return false;
321     }
322 
323     @VisibleForTesting
handleStubVolumeClick(Context context, VolumeInfo vol)324     static boolean handleStubVolumeClick(Context context, VolumeInfo vol) {
325         final Intent intent = vol.buildBrowseIntent();
326         if (vol.isMountedReadable() && intent != null) {
327             context.startActivity(intent);
328             return true;
329         }
330         return false;
331     }
332 
333     @VisibleForTesting
handlePublicVolumeClick(Context context, VolumeInfo vol)334     static boolean handlePublicVolumeClick(Context context, VolumeInfo vol) {
335         final Intent intent = vol.buildBrowseIntent();
336         if (vol.isMountedReadable() && intent != null) {
337             context.startActivity(intent);
338             return true;
339         } else {
340             final Bundle args = new Bundle();
341             args.putString(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
342             new SubSettingLauncher(context)
343                     .setDestination(PublicVolumeSettings.class.getCanonicalName())
344                     .setTitleRes(-1)
345                     .setSourceMetricsCategory(METRICS_CATEGORY)
346                     .setArguments(args)
347                     .launch();
348             return true;
349         }
350     }
351 
352     public static class MountTask extends AsyncTask<Void, Void, Exception> {
353         private final Context mContext;
354         private final StorageManager mStorageManager;
355         private final String mVolumeId;
356         private final String mDescription;
357 
MountTask(Context context, VolumeInfo volume)358         public MountTask(Context context, VolumeInfo volume) {
359             mContext = context.getApplicationContext();
360             mStorageManager = mContext.getSystemService(StorageManager.class);
361             mVolumeId = volume.getId();
362             mDescription = mStorageManager.getBestVolumeDescription(volume);
363         }
364 
365         @Override
doInBackground(Void... params)366         protected Exception doInBackground(Void... params) {
367             try {
368                 mStorageManager.mount(mVolumeId);
369                 return null;
370             } catch (Exception e) {
371                 return e;
372             }
373         }
374 
375         @Override
onPostExecute(Exception e)376         protected void onPostExecute(Exception e) {
377             if (e == null) {
378                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_success,
379                         mDescription), Toast.LENGTH_SHORT).show();
380             } else {
381                 Log.e(TAG, "Failed to mount " + mVolumeId, e);
382                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_failure,
383                         mDescription), Toast.LENGTH_SHORT).show();
384             }
385         }
386     }
387 
388     public static class UnmountTask extends AsyncTask<Void, Void, Exception> {
389         private final Context mContext;
390         private final StorageManager mStorageManager;
391         private final String mVolumeId;
392         private final String mDescription;
393 
UnmountTask(Context context, VolumeInfo volume)394         public UnmountTask(Context context, VolumeInfo volume) {
395             mContext = context.getApplicationContext();
396             mStorageManager = mContext.getSystemService(StorageManager.class);
397             mVolumeId = volume.getId();
398             mDescription = mStorageManager.getBestVolumeDescription(volume);
399         }
400 
401         @Override
doInBackground(Void... params)402         protected Exception doInBackground(Void... params) {
403             try {
404                 mStorageManager.unmount(mVolumeId);
405                 return null;
406             } catch (Exception e) {
407                 return e;
408             }
409         }
410 
411         @Override
onPostExecute(Exception e)412         protected void onPostExecute(Exception e) {
413             if (e == null) {
414                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_success,
415                         mDescription), Toast.LENGTH_SHORT).show();
416             } else {
417                 Log.e(TAG, "Failed to unmount " + mVolumeId, e);
418                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_failure,
419                         mDescription), Toast.LENGTH_SHORT).show();
420             }
421         }
422     }
423 
424     public static class VolumeUnmountedFragment extends InstrumentedDialogFragment {
show(Fragment parent, String volumeId)425         public static void show(Fragment parent, String volumeId) {
426             final Bundle args = new Bundle();
427             args.putString(VolumeInfo.EXTRA_VOLUME_ID, volumeId);
428 
429             final VolumeUnmountedFragment dialog = new VolumeUnmountedFragment();
430             dialog.setArguments(args);
431             dialog.setTargetFragment(parent, 0);
432             dialog.show(parent.getFragmentManager(), TAG_VOLUME_UNMOUNTED);
433         }
434 
435         @Override
getMetricsCategory()436         public int getMetricsCategory() {
437             return SettingsEnums.DIALOG_VOLUME_UNMOUNT;
438         }
439 
440         @Override
onCreateDialog(Bundle savedInstanceState)441         public Dialog onCreateDialog(Bundle savedInstanceState) {
442             final Context context = getActivity();
443             final StorageManager sm = context.getSystemService(StorageManager.class);
444 
445             final String volumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
446             final VolumeInfo vol = sm.findVolumeById(volumeId);
447 
448             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
449             builder.setMessage(TextUtils.expandTemplate(
450                     getText(R.string.storage_dialog_unmounted), vol.getDisk().getDescription()));
451 
452             builder.setPositiveButton(R.string.storage_menu_mount,
453                     new DialogInterface.OnClickListener() {
454                         /**
455                          * Check if an {@link
456                          * RestrictedLockUtils#sendShowAdminSupportDetailsIntent admin
457                          * details intent} should be shown for the restriction and show it.
458                          *
459                          * @param restriction The restriction to check
460                          * @return {@code true} iff a intent was shown.
461                          */
462                         private boolean wasAdminSupportIntentShown(@NonNull String restriction) {
463                             EnforcedAdmin admin = RestrictedLockUtilsInternal
464                                     .checkIfRestrictionEnforced(getActivity(), restriction,
465                                             UserHandle.myUserId());
466                             boolean hasBaseUserRestriction =
467                                     RestrictedLockUtilsInternal.hasBaseUserRestriction(
468                                             getActivity(), restriction, UserHandle.myUserId());
469                             if (admin != null && !hasBaseUserRestriction) {
470                                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
471                                         admin);
472                                 return true;
473                             }
474 
475                             return false;
476                         }
477 
478                         @Override
479                         public void onClick(DialogInterface dialog, int which) {
480                             if (wasAdminSupportIntentShown(
481                                     UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA)) {
482                                 return;
483                             }
484 
485                             if (vol.disk != null && vol.disk.isUsb() &&
486                                     wasAdminSupportIntentShown(
487                                             UserManager.DISALLOW_USB_FILE_TRANSFER)) {
488                                 return;
489                             }
490 
491                             new MountTask(context, vol).execute();
492                         }
493                     });
494             builder.setNegativeButton(R.string.cancel, null);
495 
496             return builder.create();
497         }
498     }
499 
500     public static class DiskInitFragment extends InstrumentedDialogFragment {
501         @Override
getMetricsCategory()502         public int getMetricsCategory() {
503             return SettingsEnums.DIALOG_VOLUME_INIT;
504         }
505 
show(Fragment parent, int resId, String diskId)506         public static void show(Fragment parent, int resId, String diskId) {
507             final Bundle args = new Bundle();
508             args.putInt(Intent.EXTRA_TEXT, resId);
509             args.putString(DiskInfo.EXTRA_DISK_ID, diskId);
510 
511             final DiskInitFragment dialog = new DiskInitFragment();
512             dialog.setArguments(args);
513             dialog.setTargetFragment(parent, 0);
514             dialog.show(parent.getFragmentManager(), TAG_DISK_INIT);
515         }
516 
517         @Override
onCreateDialog(Bundle savedInstanceState)518         public Dialog onCreateDialog(Bundle savedInstanceState) {
519             final Context context = getActivity();
520             final StorageManager sm = context.getSystemService(StorageManager.class);
521 
522             final int resId = getArguments().getInt(Intent.EXTRA_TEXT);
523             final String diskId = getArguments().getString(DiskInfo.EXTRA_DISK_ID);
524             final DiskInfo disk = sm.findDiskById(diskId);
525 
526             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
527             builder.setMessage(TextUtils.expandTemplate(getText(resId), disk.getDescription()));
528 
529             builder.setPositiveButton(R.string.storage_menu_set_up,
530                     new DialogInterface.OnClickListener() {
531                         @Override
532                         public void onClick(DialogInterface dialog, int which) {
533                             final Intent intent = new Intent(context, StorageWizardInit.class);
534                             intent.putExtra(DiskInfo.EXTRA_DISK_ID, diskId);
535                             startActivity(intent);
536                         }
537                     });
538             builder.setNegativeButton(R.string.cancel, null);
539 
540             return builder.create();
541         }
542     }
543 
544     /** Enable indexing of searchable data */
545     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
546             new BaseSearchIndexProvider() {
547                 @Override
548                 public List<SearchIndexableRaw> getRawDataToIndex(
549                         Context context, boolean enabled) {
550                     final List<SearchIndexableRaw> result = new ArrayList<>();
551 
552                     SearchIndexableRaw data = new SearchIndexableRaw(context);
553                     data.title = context.getString(R.string.storage_settings);
554                     data.key = "storage_settings";
555                     data.screenTitle = context.getString(R.string.storage_settings);
556                     data.keywords = context.getString(R.string.keywords_storage_settings);
557                     result.add(data);
558 
559                     data = new SearchIndexableRaw(context);
560                     data.title = context.getString(R.string.internal_storage);
561                     data.key = "storage_settings_internal_storage";
562                     data.screenTitle = context.getString(R.string.storage_settings);
563                     result.add(data);
564 
565                     data = new SearchIndexableRaw(context);
566                     final StorageManager storage = context.getSystemService(StorageManager.class);
567                     final List<VolumeInfo> vols = storage.getVolumes();
568                     for (VolumeInfo vol : vols) {
569                         if (isInteresting(vol)) {
570                             data.title = storage.getBestVolumeDescription(vol);
571                             data.key = "storage_settings_volume_" + vol.id;
572                             data.screenTitle = context.getString(R.string.storage_settings);
573                             result.add(data);
574                         }
575                     }
576 
577                     data = new SearchIndexableRaw(context);
578                     data.title = context.getString(R.string.memory_size);
579                     data.key = "storage_settings_memory_size";
580                     data.screenTitle = context.getString(R.string.storage_settings);
581                     result.add(data);
582 
583                     data = new SearchIndexableRaw(context);
584                     data.title = context.getString(R.string.memory_available);
585                     data.key = "storage_settings_memory_available";
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_apps_usage);
591                     data.key = "storage_settings_apps_space";
592                     data.screenTitle = context.getString(R.string.storage_settings);
593                     result.add(data);
594 
595                     data = new SearchIndexableRaw(context);
596                     data.title = context.getString(R.string.memory_dcim_usage);
597                     data.key = "storage_settings_dcim_space";
598                     data.screenTitle = context.getString(R.string.storage_settings);
599                     result.add(data);
600 
601                     data = new SearchIndexableRaw(context);
602                     data.title = context.getString(R.string.memory_music_usage);
603                     data.key = "storage_settings_music_space";
604                     data.screenTitle = context.getString(R.string.storage_settings);
605                     result.add(data);
606 
607                     data = new SearchIndexableRaw(context);
608                     data.title = context.getString(R.string.memory_media_misc_usage);
609                     data.key = "storage_settings_misc_space";
610                     data.screenTitle = context.getString(R.string.storage_settings);
611                     result.add(data);
612 
613                     data = new SearchIndexableRaw(context);
614                     data.title = context.getString(R.string.storage_menu_free);
615                     data.key = "storage_settings_free_space";
616                     data.screenTitle = context.getString(R.string.storage_menu_free);
617                     // We need to define all three in order for this to trigger properly.
618                     data.intentAction = StorageManager.ACTION_MANAGE_STORAGE;
619                     data.intentTargetPackage =
620                             context.getString(R.string.config_deletion_helper_package);
621                     data.intentTargetClass =
622                             context.getString(R.string.config_deletion_helper_class);
623                     result.add(data);
624 
625                     return result;
626                 }
627             };
628 }
629