1 /*
2  * Copyright (C) 2021 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.tv.settings.library.about;
18 
19 import static com.android.tv.settings.library.ManagerUtil.STATE_SYSTEM_ABOUT;
20 
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.ResolveInfo;
26 import android.os.Build;
27 import android.os.Bundle;
28 import android.os.PersistableBundle;
29 import android.os.SELinux;
30 import android.os.SystemClock;
31 import android.os.SystemProperties;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.provider.Settings;
35 import android.telephony.CarrierConfigManager;
36 import android.text.TextUtils;
37 import android.util.Log;
38 import android.widget.Toast;
39 
40 import androidx.annotation.Keep;
41 import androidx.annotation.Nullable;
42 
43 import com.android.tv.settings.library.PreferenceCompat;
44 import com.android.tv.settings.library.State;
45 import com.android.tv.settings.library.UIUpdateCallback;
46 import com.android.tv.settings.library.data.PreferenceCompatManager;
47 import com.android.tv.settings.library.settingslib.RestrictedLockUtils;
48 import com.android.tv.settings.library.settingslib.RestrictedLockUtilsInternal;
49 import com.android.tv.settings.library.util.LibUtils;
50 import com.android.tv.settings.library.util.PreferenceCompatUtils;
51 import com.android.tv.settings.library.util.ResourcesUtil;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 
56 /**
57  * The "About" screen in TV settings.
58  */
59 @Keep
60 public class AboutState implements State {
61     private static final String TAG = "AboutFragment";
62 
63     private static final String KEY_MANUAL = "manual";
64     private static final String KEY_REGULATORY_INFO = "regulatory_info";
65     private static final String KEY_SYSTEM_UPDATE_SETTINGS = "system_update_settings";
66     private static final String PROPERTY_URL_SAFETYLEGAL = "ro.url.safetylegal";
67     private static final String PROPERTY_SELINUX_STATUS = "ro.build.selinux";
68     private static final String KEY_KERNEL_VERSION = "kernel_version";
69     private static final String KEY_BUILD_NUMBER = "build_number";
70     private static final String KEY_DEVICE_MODEL = "device_model";
71     private static final String KEY_SELINUX_STATUS = "selinux_status";
72     private static final String KEY_BASEBAND_VERSION = "baseband_version";
73     private static final String KEY_FIRMWARE_VERSION = "firmware_version";
74     private static final String KEY_SECURITY_PATCH = "security_patch";
75     private static final String KEY_UPDATE_SETTING = "additional_system_update_settings";
76     private static final String KEY_EQUIPMENT_ID = "fcc_equipment_id";
77     private static final String PROPERTY_EQUIPMENT_ID = "ro.ril.fccid";
78     private static final String KEY_DEVICE_FEEDBACK = "device_feedback";
79     private static final String KEY_SAFETY_LEGAL = "safetylegal";
80     private static final String KEY_DEVICE_NAME = "device_name";
81     private static final String KEY_TUTORIALS = "tutorials";
82     private static final String KEY_RESET = "reset";
83     private static final String KEY_RESET_OPTIONS = "reset_options";
84 
85     static final int TAPS_TO_BE_A_DEVELOPER = 7;
86 
87     long[] mHits = new long[3];
88     int mDevHitCountdown;
89     Toast mDevHitToast;
90 
91     private UserManager mUm;
92 
93     private final BroadcastReceiver mDeviceNameReceiver = new BroadcastReceiver() {
94         @Override
95         public void onReceive(Context context, Intent intent) {
96             refreshDeviceName();
97         }
98     };
99 
100     private final Context context;
101     private final UIUpdateCallback uiUpdateCallback;
102     private PreferenceCompat mFirmwareVersionPref;
103     private PreferenceCompat mDeviceTutorialsPref;
104     private PreferenceCompat mMDeviceTutorialsPref;
105 
AboutState(Context context, UIUpdateCallback uiUpdateCallback)106     public AboutState(Context context, UIUpdateCallback uiUpdateCallback) {
107         this.context = context;
108         this.uiUpdateCallback = uiUpdateCallback;
109     }
110 
111     private PreferenceCompatManager mPreferenceCompatManager;
112 
113     @Override
onAttach()114     public void onAttach() {
115         // no-op
116     }
117 
118     @Override
onCreate(Bundle extra)119     public void onCreate(Bundle extra) {
120         mUm = UserManager.get(context);
121 
122         mPreferenceCompatManager = new PreferenceCompatManager();
123         refreshDeviceName();
124         final PreferenceCompat deviceNamePref = mPreferenceCompatManager.getOrCreatePrefCompat(
125                 KEY_DEVICE_NAME);
126         PreferenceCompatUtils.resolveSystemActivityOrRemove(context, preferenceCompats,
127                 deviceNamePref, 0);
128 
129         mFirmwareVersionPref = mPreferenceCompatManager.getOrCreatePrefCompat(KEY_FIRMWARE_VERSION);
130         mFirmwareVersionPref.setSummary(Build.VERSION.RELEASE_OR_CODENAME);
131         mFirmwareVersionPref.setEnabled(true);
132 
133         final PreferenceCompat securityPatchPref = mPreferenceCompatManager.getOrCreatePrefCompat(
134                 KEY_SECURITY_PATCH);
135         final String patch = DeviceInfoUtils.getSecurityPatch();
136         if (!TextUtils.isEmpty(patch)) {
137             securityPatchPref.setSummary(patch);
138         } else {
139             removePreference(securityPatchPref);
140         }
141 
142         mPreferenceCompatManager.getOrCreatePrefCompat(KEY_DEVICE_MODEL).setSummary(
143                 Build.MODEL + DeviceInfoUtils.getMsvSuffix());
144         mPreferenceCompatManager.getOrCreatePrefCompat(KEY_EQUIPMENT_ID)
145                 .setSummary(getSystemPropertySummary(PROPERTY_EQUIPMENT_ID));
146 
147         final PreferenceCompat buildNumberPref = mPreferenceCompatManager.getOrCreatePrefCompat(
148                 KEY_BUILD_NUMBER);
149         buildNumberPref.setSummary(Build.DISPLAY);
150         buildNumberPref.setEnabled(true);
151         mPreferenceCompatManager.getOrCreatePrefCompat(KEY_KERNEL_VERSION)
152                 .setSummary(DeviceInfoUtils.getFormattedKernelVersion(context));
153 
154         final PreferenceCompat selinuxPref = mPreferenceCompatManager.getOrCreatePrefCompat(
155                 KEY_SELINUX_STATUS);
156         if (!SELinux.isSELinuxEnabled()) {
157             selinuxPref.setSummary(ResourcesUtil.getString(context, "selinux_status_disabled"));
158         } else if (!SELinux.isSELinuxEnforced()) {
159             selinuxPref.setSummary(ResourcesUtil.getString(context, "selinux_status_permissive"));
160         }
161 
162         // Remove selinux information if property is not present
163         if (TextUtils.isEmpty(SystemProperties.get(PROPERTY_SELINUX_STATUS))) {
164             removePreference(selinuxPref);
165         }
166 
167         // Remove Safety information preference if PROPERTY_URL_SAFETYLEGAL is not set
168         if (TextUtils.isEmpty(SystemProperties.get(PROPERTY_URL_SAFETYLEGAL))) {
169             removePreference(mPreferenceCompatManager.getOrCreatePrefCompat(KEY_SAFETY_LEGAL));
170         }
171 
172         // Remove Equipment id preference if FCC ID is not set by RIL
173         if (TextUtils.isEmpty(SystemProperties.get(PROPERTY_EQUIPMENT_ID))) {
174             removePreference(mPreferenceCompatManager.getOrCreatePrefCompat(KEY_EQUIPMENT_ID));
175         }
176 
177         // Remove Baseband version if wifi-only device
178         if (Utils.isWifiOnly(context)) {
179             removePreference(mPreferenceCompatManager.getOrCreatePrefCompat(KEY_BASEBAND_VERSION));
180         }
181 
182         // Don't show feedback option if there is no reporter.
183         if (TextUtils.isEmpty(DeviceInfoUtils.getFeedbackReporterPackage(context))) {
184             removePreference(mPreferenceCompatManager.getOrCreatePrefCompat(KEY_DEVICE_FEEDBACK));
185         }
186 
187         final PreferenceCompat resetPreference = mPreferenceCompatManager.getOrCreatePrefCompat(
188                 KEY_RESET);
189         resetPreference.setContentDescription(
190                 ResourcesUtil.getString(context, "factory_reset_content_description"));
191 
192         // Don't show the reset options if factory reset is restricted
193         final PreferenceCompat resetOptionsPreference =
194                 mPreferenceCompatManager.getOrCreatePrefCompat(KEY_RESET_OPTIONS);
195         if (resetOptionsPreference != null
196                 && RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
197                 UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId()) != null) {
198             // TODO (b/194102677): Handle setFragment(null);
199 //            resetOptionsPreference.setFragment(null);
200         }
201 
202         final PreferenceCompat updateSettingsPref = mPreferenceCompatManager.getOrCreatePrefCompat(
203                 KEY_SYSTEM_UPDATE_SETTINGS);
204         updateSettingsPref.setContentDescription(
205                 ResourcesUtil.getString(context, "system_update_content_description"));
206 
207         if (mUm.isAdminUser()) {
208             final Intent systemUpdateIntent = new Intent(Settings.ACTION_SYSTEM_UPDATE_SETTINGS);
209             final ResolveInfo info =
210                     LibUtils.systemIntentIsHandled(context, systemUpdateIntent);
211             if (info == null) {
212                 removePreference(updateSettingsPref);
213             } else {
214                 updateSettingsPref.setTitle(info.loadLabel(context.getPackageManager()).toString());
215             }
216         } else if (updateSettingsPref != null) {
217             // Remove for secondary users
218             removePreference(updateSettingsPref);
219         }
220 
221         // Read platform settings for additional system update setting
222         if (!ResourcesUtil.getBoolean(context, "config_additional_system_update_setting_enable")) {
223             removePreference(mPreferenceCompatManager.getOrCreatePrefCompat(KEY_UPDATE_SETTING));
224         }
225 
226         // Remove manual entry if none present.
227         if (!ResourcesUtil.getBoolean(context, "config_show_manual")) {
228             removePreference(mPreferenceCompatManager.getOrCreatePrefCompat(KEY_MANUAL));
229         }
230 
231         // Remove regulatory information if none present.
232         final PreferenceCompat regulatoryPref = mPreferenceCompatManager.getOrCreatePrefCompat(
233                 KEY_REGULATORY_INFO);
234         PreferenceCompatUtils.resolveSystemActivityOrRemove(context, preferenceCompats,
235                 regulatoryPref, 0);
236 
237         if (uiUpdateCallback != null) {
238             uiUpdateCallback.notifyUpdateAll(getStateIdentifier(), preferenceCompats);
239         }
240     }
241 
242     List<PreferenceCompat> preferenceCompats = new ArrayList<>();
243 
removePreference(@ullable PreferenceCompat preference)244     private void removePreference(@Nullable PreferenceCompat preference) {
245         if (preference != null) {
246             preferenceCompats.remove(preference);
247         }
248     }
249 
250     @Override
onStart()251     public void onStart() {
252         refreshDeviceName();
253 
254         context.registerReceiver(mDeviceNameReceiver,
255                 new IntentFilter(DeviceManager.ACTION_DEVICE_NAME_UPDATE),
256                 Context.RECEIVER_EXPORTED_UNAUDITED);
257     }
258 
259     @Override
onResume()260     public void onResume() {
261         mDevHitCountdown = DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context)
262                 ? -1 : TAPS_TO_BE_A_DEVELOPER;
263         mDevHitToast = null;
264         updateTutorials();
265     }
266 
267     @Override
onPause()268     public void onPause() {
269 
270     }
271 
272     @Override
onStop()273     public void onStop() {
274         context.unregisterReceiver(mDeviceNameReceiver);
275     }
276 
277     @Override
onDestroy()278     public void onDestroy() {
279 
280     }
281 
282     @Override
onDetach()283     public void onDetach() {
284 
285     }
286 
refreshDeviceName()287     private void refreshDeviceName() {
288         final PreferenceCompat deviceNamePref = mPreferenceCompatManager.getOrCreatePrefCompat(
289                 KEY_DEVICE_NAME);
290         if (deviceNamePref != null) {
291             deviceNamePref.setSummary(DeviceManager.getDeviceName(context));
292         }
293     }
294 
295     @Override
onPreferenceTreeClick(String[] key, boolean status)296     public boolean onPreferenceTreeClick(String[] key, boolean status) {
297         boolean handled = true;
298         switch (key[0]) {
299             case KEY_FIRMWARE_VERSION:
300                 System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1);
301                 mHits[mHits.length - 1] = SystemClock.uptimeMillis();
302                 if (mHits[0] >= (SystemClock.uptimeMillis() - 500)) {
303                     if (mUm.hasUserRestriction(UserManager.DISALLOW_FUN)) {
304                         final RestrictedLockUtils.EnforcedAdmin admin = RestrictedLockUtilsInternal
305                                 .checkIfRestrictionEnforced(context, UserManager.DISALLOW_FUN,
306                                         UserHandle.myUserId());
307                         if (admin != null) {
308                             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context,
309                                     admin);
310                         }
311 
312                         Log.d(TAG, "Sorry, no fun for you!");
313                         return false;
314                     }
315 
316                     Intent intent = new Intent(Intent.ACTION_MAIN);
317                     intent.setClassName("android",
318                             "PlatLogoActivity");
319                     try {
320                         context.startActivity(intent);
321                     } catch (Exception e) {
322                         Log.e(TAG, "Unable to start activity " + intent.toString());
323                     }
324                 }
325                 break;
326             case KEY_BUILD_NUMBER:
327 //                logEntrySelected(TvSettingsEnums.SYSTEM_ABOUT_BUILD);
328                 // Don't enable developer options for secondary users.
329                 if (!mUm.isAdminUser()) {
330                     return true;
331                 }
332 
333                 if (mUm.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES)) {
334                     final RestrictedLockUtils.EnforcedAdmin admin = RestrictedLockUtilsInternal
335                             .checkIfRestrictionEnforced(context,
336                                     UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.myUserId());
337                     if (admin != null) {
338                         RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, admin);
339                     }
340                     return true;
341                 }
342 
343                 if (mDevHitCountdown > 0) {
344                     mDevHitCountdown--;
345                     if (mDevHitCountdown == 0) {
346                         DevelopmentSettingsEnabler
347                                 .setDevelopmentSettingsEnabled(context, true);
348                         if (mDevHitToast != null) {
349                             mDevHitToast.cancel();
350                         }
351                         mDevHitToast = Toast.makeText(context,
352                                 ResourcesUtil.getString(context, "show_dev_on"),
353                                 Toast.LENGTH_LONG);
354                         mDevHitToast.show();
355                         // This is good time to index the Developer Options
356 //                    Index.getInstance(
357 //                            getActivity().getApplicationContext()).updateFromClassNameResource(
358 //                            DevelopmentSettings.class.getName(), true, true);
359                     } else if (mDevHitCountdown > 0
360                             && mDevHitCountdown < (TAPS_TO_BE_A_DEVELOPER - 2)) {
361                         if (mDevHitToast != null) {
362                             mDevHitToast.cancel();
363                         }
364                         mDevHitToast = Toast
365                                 .makeText(context, ResourcesUtil.getQuantityString(
366                                         context, "show_dev_countdown", mDevHitCountdown,
367                                         mDevHitCountdown),
368                                         Toast.LENGTH_SHORT);
369                         mDevHitToast.show();
370                     }
371                 } else if (mDevHitCountdown < 0) {
372                     if (mDevHitToast != null) {
373                         mDevHitToast.cancel();
374                     }
375                     mDevHitToast = Toast.makeText(context,
376                             ResourcesUtil.getString(context, "show_dev_already"),
377                             Toast.LENGTH_LONG);
378                     mDevHitToast.show();
379                 }
380                 break;
381             case KEY_DEVICE_FEEDBACK:
382                 sendFeedback();
383                 break;
384             case KEY_SYSTEM_UPDATE_SETTINGS:
385 //                logEntrySelected(TvSettingsEnums.SYSTEM_ABOUT_SYSTEM_UPDATE);
386                 CarrierConfigManager configManager = (CarrierConfigManager)
387                         context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
388                 PersistableBundle b = configManager.getConfig();
389                 if (b != null &&
390                         b.getBoolean(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL)) {
391                     ciActionOnSysUpdate(b);
392                 }
393                 context.startActivity(new Intent(Settings.ACTION_SYSTEM_UPDATE_SETTINGS));
394                 break;
395             case KEY_DEVICE_NAME:
396 //                logEntrySelected(TvSettingsEnums.SYSTEM_ABOUT_DEVICE_NAME);
397                 break;
398             case KEY_RESET:
399 //                logEntrySelected(TvSettingsEnums.SYSTEM_ABOUT_FACTORY_RESET);
400                 Intent factoryResetIntent = new Intent();
401                 factoryResetIntent.setClassName(
402                         "com.android.tv.settings",
403                         "com.android.tv.settings.device.storage.ResetActivity");
404                 context.startActivity(factoryResetIntent);
405                 break;
406             default:
407                 handled = false;
408         }
409         return handled;
410     }
411 
412     @Override
onActivityResult(int requestCode, int resultCode, Intent data)413     public void onActivityResult(int requestCode, int resultCode, Intent data) {
414 
415     }
416 
417     @Override
onPreferenceChange(String[] key, Object newValue)418     public boolean onPreferenceChange(String[] key, Object newValue) {
419         return false;
420     }
421 
422     @Override
getStateIdentifier()423     public int getStateIdentifier() {
424         return STATE_SYSTEM_ABOUT;
425     }
426 
427     /**
428      * Trigger client initiated action (send intent) on system update
429      */
ciActionOnSysUpdate(PersistableBundle b)430     private void ciActionOnSysUpdate(PersistableBundle b) {
431         String intentStr = b.getString(CarrierConfigManager.
432                 KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING);
433         if (!TextUtils.isEmpty(intentStr)) {
434             String extra = b.getString(CarrierConfigManager.
435                     KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING);
436             String extraVal = b.getString(CarrierConfigManager.
437                     KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING);
438 
439             Intent intent = new Intent(intentStr);
440             if (!TextUtils.isEmpty(extra)) {
441                 intent.putExtra(extra, extraVal);
442             }
443             Log.d(TAG, "ciActionOnSysUpdate: broadcasting intent " + intentStr +
444                     " with extra " + extra + ", " + extraVal);
445             context.getApplicationContext().sendBroadcast(intent);
446         }
447     }
448 
getSystemPropertySummary(String property)449     private String getSystemPropertySummary(String property) {
450         return SystemProperties.get(property,
451                 ResourcesUtil.getString(context, "device_info_default"));
452     }
453 
sendFeedback()454     private void sendFeedback() {
455         String reporterPackage = DeviceInfoUtils.getFeedbackReporterPackage(context);
456         if (TextUtils.isEmpty(reporterPackage)) {
457             return;
458         }
459         Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
460         intent.setPackage(reporterPackage);
461         context.startActivity(intent);
462     }
463 
updateTutorials()464     private void updateTutorials() {
465         mMDeviceTutorialsPref = mPreferenceCompatManager.getOrCreatePrefCompat(KEY_TUTORIALS);
466         if (mMDeviceTutorialsPref != null) {
467             final ResolveInfo info = LibUtils.systemIntentIsHandled(context,
468                     mMDeviceTutorialsPref.getIntent());
469             mMDeviceTutorialsPref.setVisible(info != null);
470             if (info != null) {
471                 mMDeviceTutorialsPref.setTitle(
472                         info.loadLabel(context.getPackageManager()).toString());
473             }
474         }
475     }
476 
477     @Override
onDisplayDialogPreference(String[] key)478     public void onDisplayDialogPreference(String[] key) {
479 
480     }
481 }
482