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