1 /* 2 * Copyright (C) 2016 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 android.app.Activity; 20 import android.app.ActivityManager; 21 import android.app.settings.SettingsEnums; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.ResolveInfo; 26 import android.os.Build; 27 import android.os.UserHandle; 28 import android.os.UserManager; 29 import android.text.BidiFormatter; 30 import android.text.TextUtils; 31 import android.widget.Toast; 32 33 import androidx.annotation.VisibleForTesting; 34 import androidx.preference.Preference; 35 36 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 37 import com.android.settings.R; 38 import com.android.settings.Utils; 39 import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog; 40 import com.android.settings.core.BasePreferenceController; 41 import com.android.settings.core.InstrumentedPreferenceFragment; 42 import com.android.settings.overlay.FeatureFactory; 43 import com.android.settings.password.ChooseLockSettingsHelper; 44 import com.android.settings.password.ConfirmDeviceCredentialActivity; 45 import com.android.settingslib.RestrictedLockUtils; 46 import com.android.settingslib.RestrictedLockUtilsInternal; 47 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 48 import com.android.settingslib.core.lifecycle.LifecycleObserver; 49 import com.android.settingslib.core.lifecycle.events.OnStart; 50 import com.android.settingslib.development.DevelopmentSettingsEnabler; 51 import com.android.settingslib.utils.StringUtil; 52 53 import com.google.android.setupcompat.util.WizardManagerHelper; 54 55 public class BuildNumberPreferenceController extends BasePreferenceController implements 56 LifecycleObserver, OnStart { 57 58 static final int TAPS_TO_BE_A_DEVELOPER = 7; 59 static final int REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF = 100; 60 static final int REQUEST_IDENTITY_CHECK_FOR_DEV_PREF = 101; 61 62 private Activity mActivity; 63 private InstrumentedPreferenceFragment mFragment; 64 private final UserManager mUm; 65 private final MetricsFeatureProvider mMetricsFeatureProvider; 66 67 private Toast mDevHitToast; 68 private RestrictedLockUtils.EnforcedAdmin mDebuggingFeaturesDisallowedAdmin; 69 private boolean mDebuggingFeaturesDisallowedBySystem; 70 private int mDevHitCountdown; 71 private boolean mProcessingLastDevHit; 72 BuildNumberPreferenceController(Context context, String key)73 public BuildNumberPreferenceController(Context context, String key) { 74 super(context, key); 75 mUm = (UserManager) context.getSystemService(Context.USER_SERVICE); 76 mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); 77 } 78 setHost(InstrumentedPreferenceFragment fragment)79 public void setHost(InstrumentedPreferenceFragment fragment) { 80 mFragment = fragment; 81 mActivity = fragment.getActivity(); 82 } 83 84 @Override getSummary()85 public CharSequence getSummary() { 86 return BidiFormatter.getInstance().unicodeWrap(Build.DISPLAY); 87 } 88 89 @Override onStart()90 public void onStart() { 91 mDebuggingFeaturesDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced( 92 mContext, UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.myUserId()); 93 mDebuggingFeaturesDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction( 94 mContext, UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.myUserId()); 95 mDevHitCountdown = DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext) 96 ? -1 : TAPS_TO_BE_A_DEVELOPER; 97 mDevHitToast = null; 98 } 99 100 @Override getAvailabilityStatus()101 public int getAvailabilityStatus() { 102 return AVAILABLE; 103 } 104 105 @Override useDynamicSliceSummary()106 public boolean useDynamicSliceSummary() { 107 return true; 108 } 109 110 @Override handlePreferenceTreeClick(Preference preference)111 public boolean handlePreferenceTreeClick(Preference preference) { 112 if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { 113 return false; 114 } 115 if (isUserAMonkey()) { 116 return false; 117 } 118 // Don't enable developer options for secondary non-demo users. 119 if (!(mUm.isAdminUser() || mUm.isDemoUser())) { 120 mMetricsFeatureProvider.action( 121 mContext, SettingsEnums.ACTION_SETTINGS_BUILD_NUMBER_PREF); 122 return false; 123 } 124 125 // Don't enable developer options until device has been provisioned 126 if (!WizardManagerHelper.isDeviceProvisioned(mContext)) { 127 mMetricsFeatureProvider.action( 128 mContext, SettingsEnums.ACTION_SETTINGS_BUILD_NUMBER_PREF); 129 return false; 130 } 131 132 if (mUm.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES)) { 133 if (mUm.isDemoUser()) { 134 // Route to demo device owner to lift the debugging restriction. 135 final ComponentName componentName = Utils.getDeviceOwnerComponent(mContext); 136 if (componentName != null) { 137 final Intent requestDebugFeatures = new Intent() 138 .setPackage(componentName.getPackageName()) 139 .setAction("com.android.settings.action.REQUEST_DEBUG_FEATURES"); 140 final ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity( 141 requestDebugFeatures, 0); 142 if (resolveInfo != null) { 143 mContext.startActivity(requestDebugFeatures); 144 return false; 145 } 146 } 147 } 148 if (mDebuggingFeaturesDisallowedAdmin != null && 149 !mDebuggingFeaturesDisallowedBySystem) { 150 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, 151 mDebuggingFeaturesDisallowedAdmin); 152 } 153 mMetricsFeatureProvider.action( 154 mContext, SettingsEnums.ACTION_SETTINGS_BUILD_NUMBER_PREF); 155 return false; 156 } 157 158 if (mDevHitCountdown > 0) { 159 mDevHitCountdown--; 160 if (mDevHitCountdown == 0 && !mProcessingLastDevHit) { 161 // Add 1 count back, then start password confirmation flow. 162 mDevHitCountdown++; 163 164 final String title = mContext 165 .getString(R.string.unlock_set_unlock_launch_picker_title); 166 final ChooseLockSettingsHelper.Builder builder = 167 new ChooseLockSettingsHelper.Builder(mActivity, mFragment); 168 mProcessingLastDevHit = builder 169 .setRequestCode(REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF) 170 .setTitle(title) 171 .show(); 172 173 if (!mProcessingLastDevHit) { 174 enableDevelopmentSettings(); 175 } 176 mMetricsFeatureProvider.action( 177 mMetricsFeatureProvider.getAttribution(mActivity), 178 MetricsEvent.FIELD_SETTINGS_BUILD_NUMBER_DEVELOPER_MODE_ENABLED, 179 mFragment.getMetricsCategory(), 180 null, 181 mProcessingLastDevHit ? 0 : 1); 182 } else if (mDevHitCountdown > 0 183 && mDevHitCountdown < (TAPS_TO_BE_A_DEVELOPER - 2)) { 184 if (mDevHitToast != null) { 185 mDevHitToast.cancel(); 186 } 187 mDevHitToast = Toast.makeText(mContext, 188 StringUtil.getIcuPluralsString(mContext, mDevHitCountdown, 189 R.string.show_dev_countdown), 190 Toast.LENGTH_SHORT); 191 mDevHitToast.show(); 192 } 193 194 mMetricsFeatureProvider.action( 195 mMetricsFeatureProvider.getAttribution(mActivity), 196 MetricsEvent.FIELD_SETTINGS_BUILD_NUMBER_DEVELOPER_MODE_ENABLED, 197 mFragment.getMetricsCategory(), 198 null, 199 0); 200 } else if (mDevHitCountdown < 0) { 201 if (mDevHitToast != null) { 202 mDevHitToast.cancel(); 203 } 204 mDevHitToast = Toast.makeText(mContext, R.string.show_dev_already, 205 Toast.LENGTH_LONG); 206 mDevHitToast.show(); 207 mMetricsFeatureProvider.action( 208 mMetricsFeatureProvider.getAttribution(mActivity), 209 MetricsEvent.FIELD_SETTINGS_BUILD_NUMBER_DEVELOPER_MODE_ENABLED, 210 mFragment.getMetricsCategory(), 211 null, 212 1); 213 } 214 return true; 215 } 216 217 /** 218 * Handles password confirmation result. 219 * 220 * @return if activity result is handled. 221 */ onActivityResult(int requestCode, int resultCode, Intent data)222 public boolean onActivityResult(int requestCode, int resultCode, Intent data) { 223 if (requestCode != REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF 224 && requestCode != REQUEST_IDENTITY_CHECK_FOR_DEV_PREF) { 225 return false; 226 } 227 if (requestCode == REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF 228 && resultCode == Activity.RESULT_OK) { 229 final int userId = mContext.getUserId(); 230 final Utils.BiometricStatus biometricAuthStatus = 231 Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext, 232 false /* biometricsAuthenticationRequested */, 233 userId); 234 if (biometricAuthStatus == Utils.BiometricStatus.OK) { 235 Utils.launchBiometricPromptForMandatoryBiometrics(mFragment, 236 REQUEST_IDENTITY_CHECK_FOR_DEV_PREF, 237 userId, false /* hideBackground */); 238 } else if (biometricAuthStatus == Utils.BiometricStatus.NOT_ACTIVE) { 239 enableDevelopmentSettings(); 240 } else { 241 IdentityCheckBiometricErrorDialog.showBiometricErrorDialog(mFragment.getActivity(), 242 biometricAuthStatus, true /* twoFactorAuthentication */); 243 } 244 } else if (requestCode == REQUEST_IDENTITY_CHECK_FOR_DEV_PREF) { 245 if (resultCode == Activity.RESULT_OK) { 246 enableDevelopmentSettings(); 247 } else if (resultCode 248 == ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) { 249 IdentityCheckBiometricErrorDialog.showBiometricErrorDialog(mFragment.getActivity(), 250 Utils.BiometricStatus.LOCKOUT, true /* twoFactorAuthentication */); 251 } 252 } 253 mProcessingLastDevHit = false; 254 return true; 255 } 256 257 /** 258 * Enables development settings. Only call this after confirming password. 259 */ enableDevelopmentSettings()260 private void enableDevelopmentSettings() { 261 mDevHitCountdown = 0; 262 mProcessingLastDevHit = false; 263 DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(mContext, true); 264 if (mDevHitToast != null) { 265 mDevHitToast.cancel(); 266 } 267 mDevHitToast = Toast.makeText(mContext, R.string.show_dev_on, 268 Toast.LENGTH_LONG); 269 mDevHitToast.show(); 270 271 FeatureFactory.getFeatureFactory().getSearchFeatureProvider().sendPreIndexIntent(mContext); 272 } 273 274 @VisibleForTesting isUserAMonkey()275 protected boolean isUserAMonkey() { 276 return ActivityManager.isUserAMonkey(); 277 } 278 } 279