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