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