• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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