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