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