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