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