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 import com.google.android.setupcompat.util.WizardManagerHelper;
50 
51 public class BuildNumberPreferenceController extends BasePreferenceController implements
52         LifecycleObserver, OnStart {
53 
54     static final int TAPS_TO_BE_A_DEVELOPER = 7;
55     static final int REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF = 100;
56 
57     private Activity mActivity;
58     private InstrumentedPreferenceFragment 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, String key)68     public BuildNumberPreferenceController(Context context, String key) {
69         super(context, key);
70         mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
71         mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
72     }
73 
setHost(InstrumentedPreferenceFragment fragment)74     public void setHost(InstrumentedPreferenceFragment fragment) {
75         mFragment = fragment;
76         mActivity = fragment.getActivity();
77     }
78 
79     @Override
getSummary()80     public CharSequence getSummary() {
81         return BidiFormatter.getInstance().unicodeWrap(Build.DISPLAY);
82     }
83 
84     @Override
onStart()85     public void onStart() {
86         mDebuggingFeaturesDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
87                 mContext, UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.myUserId());
88         mDebuggingFeaturesDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction(
89                 mContext, UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.myUserId());
90         mDevHitCountdown = DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)
91                 ? -1 : TAPS_TO_BE_A_DEVELOPER;
92         mDevHitToast = null;
93     }
94 
95     @Override
getAvailabilityStatus()96     public int getAvailabilityStatus() {
97         return AVAILABLE;
98     }
99 
100     @Override
useDynamicSliceSummary()101     public boolean useDynamicSliceSummary() {
102         return true;
103     }
104 
105     @Override
copy()106     public void copy() {
107         Sliceable.setCopyContent(mContext, getSummary(), mContext.getText(R.string.build_number));
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 (Utils.isMonkeyRunning()) {
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                         mContext.getResources().getQuantityString(
189                                 R.plurals.show_dev_countdown, mDevHitCountdown,
190                                 mDevHitCountdown),
191                         Toast.LENGTH_SHORT);
192                 mDevHitToast.show();
193             }
194 
195             mMetricsFeatureProvider.action(
196                     mMetricsFeatureProvider.getAttribution(mActivity),
197                     MetricsEvent.FIELD_SETTINGS_BUILD_NUMBER_DEVELOPER_MODE_ENABLED,
198                     mFragment.getMetricsCategory(),
199                     null,
200                     0);
201         } else if (mDevHitCountdown < 0) {
202             if (mDevHitToast != null) {
203                 mDevHitToast.cancel();
204             }
205             mDevHitToast = Toast.makeText(mContext, R.string.show_dev_already,
206                     Toast.LENGTH_LONG);
207             mDevHitToast.show();
208             mMetricsFeatureProvider.action(
209                     mMetricsFeatureProvider.getAttribution(mActivity),
210                     MetricsEvent.FIELD_SETTINGS_BUILD_NUMBER_DEVELOPER_MODE_ENABLED,
211                     mFragment.getMetricsCategory(),
212                     null,
213                     1);
214         }
215         return true;
216     }
217 
218     /**
219      * Handles password confirmation result.
220      *
221      * @return if activity result is handled.
222      */
onActivityResult(int requestCode, int resultCode, Intent data)223     public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
224         if (requestCode != REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF) {
225             return false;
226         }
227         if (resultCode == Activity.RESULT_OK) {
228             enableDevelopmentSettings();
229         }
230         mProcessingLastDevHit = false;
231         return true;
232     }
233 
234     /**
235      * Enables development settings. Only call this after confirming password.
236      */
enableDevelopmentSettings()237     private void enableDevelopmentSettings() {
238         mDevHitCountdown = 0;
239         mProcessingLastDevHit = false;
240         DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(mContext, true);
241         if (mDevHitToast != null) {
242             mDevHitToast.cancel();
243         }
244         mDevHitToast = Toast.makeText(mContext, R.string.show_dev_on,
245                 Toast.LENGTH_LONG);
246         mDevHitToast.show();
247     }
248 }
249