1 /* 2 * Copyright (C) 2008 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; 18 19 import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER; 20 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER; 21 22 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 23 24 import android.accounts.Account; 25 import android.accounts.AccountManager; 26 import android.accounts.AuthenticatorDescription; 27 import android.app.ActionBar; 28 import android.app.Activity; 29 import android.app.admin.DevicePolicyManager; 30 import android.app.settings.SettingsEnums; 31 import android.content.ComponentName; 32 import android.content.ContentResolver; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.pm.PackageManager; 36 import android.content.pm.ResolveInfo; 37 import android.content.pm.UserInfo; 38 import android.content.res.Resources; 39 import android.graphics.Color; 40 import android.graphics.drawable.Drawable; 41 import android.os.Bundle; 42 import android.os.Environment; 43 import android.os.SystemProperties; 44 import android.os.UserHandle; 45 import android.os.UserManager; 46 import android.provider.Settings; 47 import android.sysprop.VoldProperties; 48 import android.telephony.euicc.EuiccManager; 49 import android.text.TextUtils; 50 import android.util.Log; 51 import android.view.LayoutInflater; 52 import android.view.View; 53 import android.view.View.OnScrollChangeListener; 54 import android.view.ViewGroup; 55 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 56 import android.widget.Button; 57 import android.widget.CheckBox; 58 import android.widget.ImageView; 59 import android.widget.LinearLayout; 60 import android.widget.ScrollView; 61 import android.widget.TextView; 62 63 import androidx.annotation.VisibleForTesting; 64 65 import com.android.settings.core.InstrumentedFragment; 66 import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper; 67 import com.android.settings.network.SubscriptionUtil; 68 import com.android.settings.password.ChooseLockSettingsHelper; 69 import com.android.settings.password.ConfirmLockPattern; 70 import com.android.settingslib.RestrictedLockUtilsInternal; 71 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 72 import com.android.settingslib.development.DevelopmentSettingsEnabler; 73 74 import com.google.android.setupcompat.template.FooterBarMixin; 75 import com.google.android.setupcompat.template.FooterButton; 76 import com.google.android.setupcompat.template.FooterButton.ButtonType; 77 import com.google.android.setupdesign.GlifLayout; 78 79 import java.util.List; 80 81 /** 82 * Confirm and execute a reset of the device to a clean "just out of the box" 83 * state. Multiple confirmations are required: first, a general "are you sure 84 * you want to do this?" prompt, followed by a keyguard pattern trace if the user 85 * has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING 86 * ON THE PHONE" prompt. If at any time the phone is allowed to go to sleep, is 87 * locked, et cetera, then the confirmation sequence is abandoned. 88 * 89 * This is the initial screen. 90 */ 91 public class MainClear extends InstrumentedFragment implements OnGlobalLayoutListener { 92 private static final String TAG = "MainClear"; 93 94 @VisibleForTesting 95 static final int KEYGUARD_REQUEST = 55; 96 @VisibleForTesting 97 static final int CREDENTIAL_CONFIRM_REQUEST = 56; 98 99 private static final String KEY_SHOW_ESIM_RESET_CHECKBOX = 100 "masterclear.allow_retain_esim_profiles_after_fdr"; 101 102 static final String ERASE_EXTERNAL_EXTRA = "erase_sd"; 103 static final String ERASE_ESIMS_EXTRA = "erase_esim"; 104 105 private View mContentView; 106 @VisibleForTesting 107 FooterButton mInitiateButton; 108 private View mExternalStorageContainer; 109 @VisibleForTesting 110 CheckBox mExternalStorage; 111 @VisibleForTesting 112 View mEsimStorageContainer; 113 @VisibleForTesting 114 CheckBox mEsimStorage; 115 @VisibleForTesting 116 ScrollView mScrollView; 117 118 @Override onGlobalLayout()119 public void onGlobalLayout() { 120 mInitiateButton.setEnabled(hasReachedBottom(mScrollView)); 121 } 122 setUpActionBarAndTitle()123 private void setUpActionBarAndTitle() { 124 final Activity activity = getActivity(); 125 if (activity == null) { 126 Log.e(TAG, "No activity attached, skipping setUpActionBarAndTitle"); 127 return; 128 } 129 final ActionBar actionBar = activity.getActionBar(); 130 if (actionBar == null) { 131 Log.e(TAG, "No actionbar, skipping setUpActionBarAndTitle"); 132 return; 133 } 134 actionBar.hide(); 135 activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 136 } 137 138 /** 139 * Keyguard validation is run using the standard {@link ConfirmLockPattern} 140 * component as a subactivity 141 * 142 * @param request the request code to be returned once confirmation finishes 143 * @return true if confirmation launched 144 */ runKeyguardConfirmation(int request)145 private boolean runKeyguardConfirmation(int request) { 146 Resources res = getActivity().getResources(); 147 final ChooseLockSettingsHelper.Builder builder = 148 new ChooseLockSettingsHelper.Builder(getActivity(), this); 149 return builder.setRequestCode(request) 150 .setTitle(res.getText(R.string.main_clear_short_title)) 151 .show(); 152 } 153 154 @VisibleForTesting isValidRequestCode(int requestCode)155 boolean isValidRequestCode(int requestCode) { 156 return !((requestCode != KEYGUARD_REQUEST) && (requestCode != CREDENTIAL_CONFIRM_REQUEST)); 157 } 158 159 @Override onActivityResult(int requestCode, int resultCode, Intent data)160 public void onActivityResult(int requestCode, int resultCode, Intent data) { 161 super.onActivityResult(requestCode, resultCode, data); 162 onActivityResultInternal(requestCode, resultCode, data); 163 } 164 165 /* 166 * Internal method that allows easy testing without dealing with super references. 167 */ 168 @VisibleForTesting onActivityResultInternal(int requestCode, int resultCode, Intent data)169 void onActivityResultInternal(int requestCode, int resultCode, Intent data) { 170 if (!isValidRequestCode(requestCode)) { 171 return; 172 } 173 174 if (resultCode != Activity.RESULT_OK) { 175 establishInitialState(); 176 return; 177 } 178 179 Intent intent = null; 180 // If returning from a Keyguard request, try to show an account confirmation request if 181 // applciable. 182 if (CREDENTIAL_CONFIRM_REQUEST != requestCode 183 && (intent = getAccountConfirmationIntent()) != null) { 184 showAccountCredentialConfirmation(intent); 185 } else { 186 showFinalConfirmation(); 187 } 188 } 189 190 @VisibleForTesting showFinalConfirmation()191 void showFinalConfirmation() { 192 final Bundle args = new Bundle(); 193 args.putBoolean(ERASE_EXTERNAL_EXTRA, mExternalStorage.isChecked()); 194 args.putBoolean(ERASE_ESIMS_EXTRA, mEsimStorage.isChecked()); 195 final Intent intent = new Intent(); 196 intent.setClass(getContext(), 197 com.android.settings.Settings.FactoryResetConfirmActivity.class); 198 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, MainClearConfirm.class.getName()); 199 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 200 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 201 R.string.main_clear_confirm_title); 202 intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, getMetricsCategory()); 203 getContext().startActivity(intent); 204 } 205 206 @VisibleForTesting showAccountCredentialConfirmation(Intent intent)207 void showAccountCredentialConfirmation(Intent intent) { 208 startActivityForResult(intent, CREDENTIAL_CONFIRM_REQUEST); 209 } 210 211 @VisibleForTesting getAccountConfirmationIntent()212 Intent getAccountConfirmationIntent() { 213 final Context context = getActivity(); 214 final String accountType = context.getString(R.string.account_type); 215 final String packageName = context.getString(R.string.account_confirmation_package); 216 final String className = context.getString(R.string.account_confirmation_class); 217 if (TextUtils.isEmpty(accountType) 218 || TextUtils.isEmpty(packageName) 219 || TextUtils.isEmpty(className)) { 220 Log.i(TAG, "Resources not set for account confirmation."); 221 return null; 222 } 223 final AccountManager am = AccountManager.get(context); 224 Account[] accounts = am.getAccountsByType(accountType); 225 if (accounts != null && accounts.length > 0) { 226 final Intent requestAccountConfirmation = new Intent() 227 .setPackage(packageName) 228 .setComponent(new ComponentName(packageName, className)); 229 // Check to make sure that the intent is supported. 230 final PackageManager pm = context.getPackageManager(); 231 final ResolveInfo resolution = pm.resolveActivity(requestAccountConfirmation, 0); 232 if (resolution != null 233 && resolution.activityInfo != null 234 && packageName.equals(resolution.activityInfo.packageName)) { 235 // Note that we need to check the packagename to make sure that an Activity resolver 236 // wasn't returned. 237 return requestAccountConfirmation; 238 } else { 239 Log.i(TAG, "Unable to resolve Activity: " + packageName + "/" + className); 240 } 241 } else { 242 Log.d(TAG, "No " + accountType + " accounts installed!"); 243 } 244 return null; 245 } 246 247 /** 248 * If the user clicks to begin the reset sequence, we next require a 249 * keyguard confirmation if the user has currently enabled one. If there 250 * is no keyguard available, we simply go to the final confirmation prompt. 251 * 252 * If the user is in demo mode, route to the demo mode app for confirmation. 253 */ 254 @VisibleForTesting 255 protected final Button.OnClickListener mInitiateListener = new Button.OnClickListener() { 256 257 public void onClick(View view) { 258 final Context context = view.getContext(); 259 if (Utils.isDemoUser(context)) { 260 final ComponentName componentName = Utils.getDeviceOwnerComponent(context); 261 if (componentName != null) { 262 final Intent requestFactoryReset = new Intent() 263 .setPackage(componentName.getPackageName()) 264 .setAction(Intent.ACTION_FACTORY_RESET); 265 context.startActivity(requestFactoryReset); 266 } 267 return; 268 } 269 270 if (runKeyguardConfirmation(KEYGUARD_REQUEST)) { 271 return; 272 } 273 274 Intent intent = getAccountConfirmationIntent(); 275 if (intent != null) { 276 showAccountCredentialConfirmation(intent); 277 } else { 278 showFinalConfirmation(); 279 } 280 } 281 }; 282 283 /** 284 * In its initial state, the activity presents a button for the user to 285 * click in order to initiate a confirmation sequence. This method is 286 * called from various other points in the code to reset the activity to 287 * this base state. 288 * 289 * <p>Reinflating views from resources is expensive and prevents us from 290 * caching widget pointers, so we use a single-inflate pattern: we lazy- 291 * inflate each view, caching all of the widget pointers we'll need at the 292 * time, then simply reuse the inflated views directly whenever we need 293 * to change contents. 294 */ 295 @VisibleForTesting establishInitialState()296 void establishInitialState() { 297 setUpActionBarAndTitle(); 298 setUpInitiateButton(); 299 300 mExternalStorageContainer = mContentView.findViewById(R.id.erase_external_container); 301 mExternalStorage = mContentView.findViewById(R.id.erase_external); 302 mEsimStorageContainer = mContentView.findViewById(R.id.erase_esim_container); 303 mEsimStorage = mContentView.findViewById(R.id.erase_esim); 304 if (mScrollView != null) { 305 mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 306 } 307 mScrollView = mContentView.findViewById(R.id.main_clear_scrollview); 308 309 /* 310 * If the external storage is emulated, it will be erased with a factory 311 * reset at any rate. There is no need to have a separate option until 312 * we have a factory reset that only erases some directories and not 313 * others. Likewise, if it's non-removable storage, it could potentially have been 314 * encrypted, and will also need to be wiped. 315 */ 316 boolean isExtStorageEmulated = Environment.isExternalStorageEmulated(); 317 if (isExtStorageEmulated 318 || (!Environment.isExternalStorageRemovable() && isExtStorageEncrypted())) { 319 mExternalStorageContainer.setVisibility(View.GONE); 320 321 final View externalOption = mContentView.findViewById(R.id.erase_external_option_text); 322 externalOption.setVisibility(View.GONE); 323 324 final View externalAlsoErased = mContentView.findViewById(R.id.also_erases_external); 325 externalAlsoErased.setVisibility(View.VISIBLE); 326 327 // If it's not emulated, it is on a separate partition but it means we're doing 328 // a force wipe due to encryption. 329 mExternalStorage.setChecked(!isExtStorageEmulated); 330 } else { 331 mExternalStorageContainer.setOnClickListener(new View.OnClickListener() { 332 333 @Override 334 public void onClick(View v) { 335 mExternalStorage.toggle(); 336 } 337 }); 338 } 339 340 if (showWipeEuicc()) { 341 if (showWipeEuiccCheckbox()) { 342 mEsimStorageContainer.setVisibility(View.VISIBLE); 343 mEsimStorageContainer.setOnClickListener(new View.OnClickListener() { 344 @Override 345 public void onClick(View v) { 346 mEsimStorage.toggle(); 347 } 348 }); 349 } else { 350 final View esimAlsoErased = mContentView.findViewById(R.id.also_erases_esim); 351 esimAlsoErased.setVisibility(View.VISIBLE); 352 353 final View noCancelMobilePlan = mContentView.findViewById( 354 R.id.no_cancel_mobile_plan); 355 noCancelMobilePlan.setVisibility(View.VISIBLE); 356 mEsimStorage.setChecked(true /* checked */); 357 } 358 } else { 359 mEsimStorage.setChecked(false /* checked */); 360 } 361 362 final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); 363 loadAccountList(um); 364 final StringBuffer contentDescription = new StringBuffer(); 365 final View mainClearContainer = mContentView.findViewById(R.id.main_clear_container); 366 getContentDescription(mainClearContainer, contentDescription); 367 mainClearContainer.setContentDescription(contentDescription); 368 369 // Set the status of initiateButton based on scrollview 370 mScrollView.setOnScrollChangeListener(new OnScrollChangeListener() { 371 @Override 372 public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, 373 int oldScrollY) { 374 if (v instanceof ScrollView && hasReachedBottom((ScrollView) v)) { 375 mInitiateButton.setEnabled(true); 376 mScrollView.setOnScrollChangeListener(null); 377 } 378 } 379 }); 380 381 // Set the initial state of the initiateButton 382 mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(this); 383 } 384 385 /** 386 * Whether to show any UI which is SIM related. 387 */ 388 @VisibleForTesting showAnySubscriptionInfo(Context context)389 boolean showAnySubscriptionInfo(Context context) { 390 return (context != null) && SubscriptionUtil.isSimHardwareVisible(context); 391 } 392 393 /** 394 * Whether to show strings indicating that the eUICC will be wiped. 395 * 396 * <p>We show the strings on any device which supports eUICC as long as the eUICC was ever 397 * provisioned (that is, at least one profile was ever downloaded onto it). 398 */ 399 @VisibleForTesting showWipeEuicc()400 boolean showWipeEuicc() { 401 Context context = getContext(); 402 if (!showAnySubscriptionInfo(context) || !isEuiccEnabled(context)) { 403 return false; 404 } 405 ContentResolver cr = context.getContentResolver(); 406 return Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) != 0 407 || DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context); 408 } 409 410 @VisibleForTesting showWipeEuiccCheckbox()411 boolean showWipeEuiccCheckbox() { 412 return SystemProperties 413 .getBoolean(KEY_SHOW_ESIM_RESET_CHECKBOX, false /* def */); 414 } 415 416 @VisibleForTesting isEuiccEnabled(Context context)417 protected boolean isEuiccEnabled(Context context) { 418 EuiccManager euiccManager = (EuiccManager) context.getSystemService(Context.EUICC_SERVICE); 419 return euiccManager.isEnabled(); 420 } 421 422 @VisibleForTesting hasReachedBottom(final ScrollView scrollView)423 boolean hasReachedBottom(final ScrollView scrollView) { 424 if (scrollView.getChildCount() < 1) { 425 return true; 426 } 427 428 final View view = scrollView.getChildAt(0); 429 final int diff = view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()); 430 431 return diff <= 0; 432 } 433 setUpInitiateButton()434 private void setUpInitiateButton() { 435 if (mInitiateButton != null) { 436 return; 437 } 438 439 final GlifLayout layout = mContentView.findViewById(R.id.setup_wizard_layout); 440 final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); 441 mixin.setPrimaryButton( 442 new FooterButton.Builder(getActivity()) 443 .setText(R.string.main_clear_button_text) 444 .setListener(mInitiateListener) 445 .setButtonType(ButtonType.OTHER) 446 .setTheme(R.style.SudGlifButton_Primary) 447 .build() 448 ); 449 mInitiateButton = mixin.getPrimaryButton(); 450 } 451 getContentDescription(View v, StringBuffer description)452 private void getContentDescription(View v, StringBuffer description) { 453 if (v.getVisibility() != View.VISIBLE) { 454 return; 455 } 456 if (v instanceof ViewGroup) { 457 ViewGroup vGroup = (ViewGroup) v; 458 for (int i = 0; i < vGroup.getChildCount(); i++) { 459 View nextChild = vGroup.getChildAt(i); 460 getContentDescription(nextChild, description); 461 } 462 } else if (v instanceof TextView) { 463 TextView vText = (TextView) v; 464 description.append(vText.getText()); 465 description.append(","); // Allow Talkback to pause between sections. 466 } 467 } 468 isExtStorageEncrypted()469 private boolean isExtStorageEncrypted() { 470 String state = VoldProperties.decrypt().orElse(""); 471 return !"".equals(state); 472 } 473 loadAccountList(final UserManager um)474 private void loadAccountList(final UserManager um) { 475 View accountsLabel = mContentView.findViewById(R.id.accounts_label); 476 LinearLayout contents = (LinearLayout) mContentView.findViewById(R.id.accounts); 477 contents.removeAllViews(); 478 479 Context context = getActivity(); 480 final List<UserInfo> profiles = um.getProfiles(UserHandle.myUserId()); 481 final int profilesSize = profiles.size(); 482 483 AccountManager mgr = AccountManager.get(context); 484 485 LayoutInflater inflater = (LayoutInflater) context.getSystemService( 486 Context.LAYOUT_INFLATER_SERVICE); 487 488 int accountsCount = 0; 489 for (int profileIndex = 0; profileIndex < profilesSize; profileIndex++) { 490 final UserInfo userInfo = profiles.get(profileIndex); 491 final int profileId = userInfo.id; 492 final UserHandle userHandle = new UserHandle(profileId); 493 Account[] accounts = mgr.getAccountsAsUser(profileId); 494 final int accountLength = accounts.length; 495 if (accountLength == 0) { 496 continue; 497 } 498 accountsCount += accountLength; 499 500 AuthenticatorDescription[] descs = AccountManager.get(context) 501 .getAuthenticatorTypesAsUser(profileId); 502 final int descLength = descs.length; 503 504 if (profilesSize > 1) { 505 View titleView = Utils.inflateCategoryHeader(inflater, contents); 506 titleView.setPadding(0 /* left */, titleView.getPaddingTop(), 507 0 /* right */, titleView.getPaddingBottom()); 508 final TextView titleText = (TextView) titleView.findViewById(android.R.id.title); 509 510 DevicePolicyManager devicePolicyManager = 511 context.getSystemService(DevicePolicyManager.class); 512 513 if (userInfo.isManagedProfile()) { 514 titleText.setText(devicePolicyManager.getResources().getString( 515 WORK_CATEGORY_HEADER, () -> getString(R.string.category_work))); 516 } else { 517 titleText.setText(devicePolicyManager.getResources().getString( 518 PERSONAL_CATEGORY_HEADER, () -> getString(R.string.category_personal))); 519 } 520 contents.addView(titleView); 521 } 522 523 for (int i = 0; i < accountLength; i++) { 524 Account account = accounts[i]; 525 AuthenticatorDescription desc = null; 526 for (int j = 0; j < descLength; j++) { 527 if (account.type.equals(descs[j].type)) { 528 desc = descs[j]; 529 break; 530 } 531 } 532 if (desc == null) { 533 Log.w(TAG, "No descriptor for account name=" + account.name 534 + " type=" + account.type); 535 continue; 536 } 537 Drawable icon = null; 538 try { 539 if (desc.iconId != 0) { 540 Context authContext = context.createPackageContextAsUser(desc.packageName, 541 0, userHandle); 542 icon = context.getPackageManager().getUserBadgedIcon( 543 authContext.getDrawable(desc.iconId), userHandle); 544 } 545 } catch (PackageManager.NameNotFoundException e) { 546 Log.w(TAG, "Bad package name for account type " + desc.type); 547 } catch (Resources.NotFoundException e) { 548 Log.w(TAG, "Invalid icon id for account type " + desc.type, e); 549 } 550 if (icon == null) { 551 icon = context.getPackageManager().getDefaultActivityIcon(); 552 } 553 554 View child = inflater.inflate(R.layout.main_clear_account, contents, false); 555 ((ImageView) child.findViewById(android.R.id.icon)).setImageDrawable(icon); 556 ((TextView) child.findViewById(android.R.id.title)).setText(account.name); 557 contents.addView(child); 558 } 559 } 560 561 if (accountsCount > 0) { 562 accountsLabel.setVisibility(View.VISIBLE); 563 contents.setVisibility(View.VISIBLE); 564 } 565 // Checking for all other users and their profiles if any. 566 View otherUsers = mContentView.findViewById(R.id.other_users_present); 567 final boolean hasOtherUsers = (um.getUserCount() - profilesSize) > 0; 568 otherUsers.setVisibility(hasOtherUsers ? View.VISIBLE : View.GONE); 569 } 570 571 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)572 public View onCreateView(LayoutInflater inflater, ViewGroup container, 573 Bundle savedInstanceState) { 574 final Context context = getContext(); 575 final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context, 576 UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId()); 577 final UserManager um = UserManager.get(context); 578 final boolean disallow = !um.isAdminUser() || RestrictedLockUtilsInternal 579 .hasBaseUserRestriction(context, UserManager.DISALLOW_FACTORY_RESET, 580 UserHandle.myUserId()); 581 if (disallow && !Utils.isDemoUser(context)) { 582 return inflater.inflate(R.layout.main_clear_disallowed_screen, null); 583 } else if (admin != null) { 584 new ActionDisabledByAdminDialogHelper(getActivity()) 585 .prepareDialogBuilder(UserManager.DISALLOW_FACTORY_RESET, admin) 586 .setOnDismissListener(__ -> getActivity().finish()) 587 .show(); 588 return new View(getContext()); 589 } 590 591 mContentView = inflater.inflate(R.layout.main_clear, null); 592 593 establishInitialState(); 594 return mContentView; 595 } 596 597 @Override getMetricsCategory()598 public int getMetricsCategory() { 599 return SettingsEnums.MASTER_CLEAR; 600 } 601 } 602