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                 final ChooseLockSettingsHelper helper =
164                         new ChooseLockSettingsHelper(mActivity, mFragment);
165                 mProcessingLastDevHit = helper.launchConfirmationActivity(
166                         REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF,
167                         mContext.getString(R.string.unlock_set_unlock_launch_picker_title));
168                 if (!mProcessingLastDevHit) {
169                     enableDevelopmentSettings();
170                 }
171                 mMetricsFeatureProvider.action(
172                         mMetricsFeatureProvider.getAttribution(mActivity),
173                         MetricsEvent.FIELD_SETTINGS_BUILD_NUMBER_DEVELOPER_MODE_ENABLED,
174                         mFragment.getMetricsCategory(),
175                         null,
176                         mProcessingLastDevHit ? 0 : 1);
177             } else if (mDevHitCountdown > 0
178                     && mDevHitCountdown < (TAPS_TO_BE_A_DEVELOPER - 2)) {
179                 if (mDevHitToast != null) {
180                     mDevHitToast.cancel();
181                 }
182                 mDevHitToast = Toast.makeText(mContext,
183                         mContext.getResources().getQuantityString(
184                                 R.plurals.show_dev_countdown, mDevHitCountdown,
185                                 mDevHitCountdown),
186                         Toast.LENGTH_SHORT);
187                 mDevHitToast.show();
188             }
189 
190             mMetricsFeatureProvider.action(
191                     mMetricsFeatureProvider.getAttribution(mActivity),
192                     MetricsEvent.FIELD_SETTINGS_BUILD_NUMBER_DEVELOPER_MODE_ENABLED,
193                     mFragment.getMetricsCategory(),
194                     null,
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                     mMetricsFeatureProvider.getAttribution(mActivity),
205                     MetricsEvent.FIELD_SETTINGS_BUILD_NUMBER_DEVELOPER_MODE_ENABLED,
206                     mFragment.getMetricsCategory(),
207                     null,
208                     1);
209         }
210         return true;
211     }
212 
213     /**
214      * Handles password confirmation result.
215      *
216      * @return if activity result is handled.
217      */
onActivityResult(int requestCode, int resultCode, Intent data)218     public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
219         if (requestCode != REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF) {
220             return false;
221         }
222         if (resultCode == Activity.RESULT_OK) {
223             enableDevelopmentSettings();
224         }
225         mProcessingLastDevHit = false;
226         return true;
227     }
228 
229     /**
230      * Enables development settings. Only call this after confirming password.
231      */
enableDevelopmentSettings()232     private void enableDevelopmentSettings() {
233         mDevHitCountdown = 0;
234         mProcessingLastDevHit = false;
235         DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(mContext, true);
236         if (mDevHitToast != null) {
237             mDevHitToast.cancel();
238         }
239         mDevHitToast = Toast.makeText(mContext, R.string.show_dev_on,
240                 Toast.LENGTH_LONG);
241         mDevHitToast.show();
242     }
243 }
244