1 /*
2  * Copyright (C) 2018 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.device;
18 
19 import static com.android.tv.settings.overlay.FlavorUtils.FLAVOR_CLASSIC;
20 import static com.android.tv.settings.overlay.FlavorUtils.FLAVOR_TWO_PANEL;
21 import static com.android.tv.settings.overlay.FlavorUtils.FLAVOR_VENDOR;
22 import static com.android.tv.settings.overlay.FlavorUtils.FLAVOR_X;
23 import static com.android.tv.settings.util.InstrumentationUtils.logEntrySelected;
24 import static com.android.tv.settings.util.InstrumentationUtils.logToggleInteracted;
25 
26 import android.app.tvsettings.TvSettingsEnums;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.content.res.Resources;
32 import android.media.AudioManager;
33 import android.media.tv.TvInputInfo;
34 import android.media.tv.TvInputManager;
35 import android.os.Bundle;
36 import android.os.UserHandle;
37 import android.provider.Settings;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.view.LayoutInflater;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.view.inputmethod.InputMethodInfo;
44 
45 import androidx.annotation.Keep;
46 import androidx.annotation.VisibleForTesting;
47 import androidx.fragment.app.Fragment;
48 import androidx.leanback.preference.LeanbackSettingsFragmentCompat;
49 import androidx.preference.Preference;
50 import androidx.preference.TwoStatePreference;
51 
52 import com.android.settingslib.applications.DefaultAppInfo;
53 import com.android.settingslib.development.DevelopmentSettingsEnabler;
54 import com.android.tv.settings.LongClickPreference;
55 import com.android.tv.settings.MainFragment;
56 import com.android.tv.settings.R;
57 import com.android.tv.settings.SettingsPreferenceFragment;
58 import com.android.tv.settings.about.RebootConfirmFragment;
59 import com.android.tv.settings.autofill.AutofillHelper;
60 import com.android.tv.settings.inputmethod.InputMethodHelper;
61 import com.android.tv.settings.overlay.FlavorUtils;
62 import com.android.tv.settings.privacy.PrivacyToggle;
63 import com.android.tv.settings.privacy.SensorFragment;
64 import com.android.tv.settings.system.SecurityFragment;
65 import com.android.tv.settings.util.SliceUtils;
66 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment;
67 import com.android.tv.twopanelsettings.slices.SlicePreference;
68 
69 import java.util.List;
70 
71 /**
72  * The "Device Preferences" screen in TV settings.
73  */
74 @Keep
75 public class DevicePrefFragment extends SettingsPreferenceFragment implements
76         LongClickPreference.OnLongClickListener {
77     @VisibleForTesting
78     static final String KEY_DEVELOPER = "developer";
79     @VisibleForTesting
80     static final String KEY_CAST_SETTINGS = "cast";
81     private static final String KEY_CAST_SETTINGS_SLICE = "cast_settings";
82     @VisibleForTesting
83     static final String KEY_KEYBOARD = "keyboard";
84     private static final String TAG = "DeviceFragment";
85     private static final String KEY_USAGE = "usageAndDiag";
86     private static final String KEY_INPUTS = "inputs";
87     private static final String KEY_SOUNDS = "sound_effects";
88     private static final String KEY_SOUNDS_SWITCH = "sound_effects_switch";
89     private static final String KEY_GOOGLE_SETTINGS = "google_settings";
90     private static final String KEY_HOME_SETTINGS = "home";
91     private static final String KEY_REBOOT = "reboot";
92     private static final String KEY_MIC = "microphone";
93     private static final String KEY_CAMERA = "camera";
94 
95     private Preference mSoundsPref;
96     private TwoStatePreference mSoundsSwitchPref;
97     private boolean mInputSettingNeeded;
98     private PackageManager mPm;
99     private AudioManager mAudioManager;
100 
getPreferenceScreenResId()101     private int getPreferenceScreenResId() {
102         if (isRestricted()) {
103             return R.xml.device_restricted;
104         }
105         switch (FlavorUtils.getFlavor(getContext())) {
106             case FLAVOR_CLASSIC:
107                 return R.xml.device;
108             case FLAVOR_TWO_PANEL:
109                 return R.xml.device_two_panel;
110             case FLAVOR_X:
111                 return R.xml.device_x;
112             case FLAVOR_VENDOR:
113                 return R.xml.device_vendor;
114             default:
115                 return R.xml.device;
116         }
117     }
118 
119     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)120     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
121         setPreferencesFromResource(getPreferenceScreenResId(), null);
122         mSoundsPref = findPreference(KEY_SOUNDS);
123         mSoundsSwitchPref = findPreference(KEY_SOUNDS_SWITCH);
124         if (mSoundsSwitchPref != null) {
125             mSoundsSwitchPref.setChecked(getSoundEffectsEnabled());
126         }
127 
128         final Preference inputPref = findPreference(KEY_INPUTS);
129         if (inputPref != null) {
130             inputPref.setVisible(mInputSettingNeeded);
131         }
132         final LongClickPreference restartPref = findPreference(KEY_REBOOT);
133         if (restartPref != null) {
134             restartPref.setLongClickListener(this);
135         }
136 
137         PrivacyToggle.MIC_TOGGLE.preparePreferenceWithSensorFragment(getContext(),
138                 findPreference(KEY_MIC), SensorFragment.TOGGLE_EXTRA);
139         PrivacyToggle.CAMERA_TOGGLE.preparePreferenceWithSensorFragment(getContext(),
140                 findPreference(KEY_CAMERA), SensorFragment.TOGGLE_EXTRA);
141     }
142 
143     @Override
onCreate(Bundle savedInstanceState)144     public void onCreate(Bundle savedInstanceState) {
145         final TvInputManager manager = (TvInputManager) getContext().getSystemService(
146                 Context.TV_INPUT_SERVICE);
147         if (manager != null) {
148             for (final TvInputInfo input : manager.getTvInputList()) {
149                 if (input.isPassthroughInput()) {
150                     mInputSettingNeeded = true;
151                 }
152             }
153         }
154         mAudioManager = getContext().getSystemService(AudioManager.class);
155         super.onCreate(savedInstanceState);
156     }
157 
158     @Override
onAttach(Context context)159     public void onAttach(Context context) {
160         super.onAttach(context);
161         mPm = context.getPackageManager();
162     }
163 
164     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)165     public View onCreateView(LayoutInflater inflater, ViewGroup container,
166             Bundle savedInstanceState) {
167         updateDeveloperOptions();
168         updateSounds();
169         updateGoogleSettings();
170         updateCastSettings();
171         updateKeyboardAutofillSettings();
172         hideIfIntentUnhandled(findPreference(KEY_HOME_SETTINGS));
173         hideIfIntentUnhandled(findPreference(KEY_CAST_SETTINGS));
174         hideIfIntentUnhandled(findPreference(KEY_USAGE));
175         return super.onCreateView(inflater, container, savedInstanceState);
176     }
177 
178     @Override
onPreferenceTreeClick(Preference preference)179     public boolean onPreferenceTreeClick(Preference preference) {
180         switch (preference.getKey()) {
181             case KEY_HOME_SETTINGS:
182                 logEntrySelected(TvSettingsEnums.PREFERENCES_HOME_SCREEN);
183                 break;
184             case KEY_GOOGLE_SETTINGS:
185                 logEntrySelected(TvSettingsEnums.PREFERENCES_ASSISTANT);
186                 break;
187             case KEY_CAST_SETTINGS:
188                 logEntrySelected(TvSettingsEnums.PREFERENCES_CHROMECAST_SHELL);
189                 break;
190             case KEY_REBOOT:
191                 logEntrySelected(TvSettingsEnums.SYSTEM_REBOOT);
192                 break;
193             case KEY_SOUNDS_SWITCH:
194                 if (mSoundsSwitchPref != null) {
195                     logToggleInteracted(TvSettingsEnums.DISPLAY_SOUND_SYSTEM_SOUNDS,
196                             mSoundsSwitchPref.isChecked());
197                     setSoundEffectsEnabled(mSoundsSwitchPref.isChecked());
198                 }
199                 break;
200         }
201         return super.onPreferenceTreeClick(preference);
202     }
203 
204     @Override
onPreferenceLongClick(Preference preference)205     public boolean onPreferenceLongClick(Preference preference) {
206         if (TextUtils.equals(preference.getKey(), KEY_REBOOT)) {
207             logEntrySelected(TvSettingsEnums.SYSTEM_REBOOT);
208             Fragment fragment = getCallbackFragment();
209             if (fragment instanceof LeanbackSettingsFragmentCompat) {
210                 ((LeanbackSettingsFragmentCompat) fragment).startImmersiveFragment(
211                         RebootConfirmFragment.newInstance(true /* safeMode */));
212                 return true;
213             } else if (fragment instanceof TwoPanelSettingsFragment) {
214                 ((TwoPanelSettingsFragment) fragment).startImmersiveFragment(
215                         RebootConfirmFragment.newInstance(true /* safeMode */));
216                 return true;
217             }
218         }
219         return false;
220     }
221 
getSoundEffectsEnabled()222     public boolean getSoundEffectsEnabled() {
223         return Settings.System.getInt(getActivity().getContentResolver(),
224                 Settings.System.SOUND_EFFECTS_ENABLED, 1) != 0;
225     }
226 
setSoundEffectsEnabled(boolean enabled)227     private void setSoundEffectsEnabled(boolean enabled) {
228         if (enabled) {
229             mAudioManager.loadSoundEffects();
230         } else {
231             mAudioManager.unloadSoundEffects();
232         }
233         Settings.System.putInt(getActivity().getContentResolver(),
234                 Settings.System.SOUND_EFFECTS_ENABLED, enabled ? 1 : 0);
235     }
236 
hideIfIntentUnhandled(Preference preference)237     private void hideIfIntentUnhandled(Preference preference) {
238         if (preference == null || !preference.isVisible()) {
239             return;
240         }
241         preference.setVisible(
242                 MainFragment.systemIntentIsHandled(getContext(), preference.getIntent()) != null);
243     }
244 
isRestricted()245     private boolean isRestricted() {
246         return SecurityFragment.isRestrictedProfileInEffect(getContext());
247     }
248 
249     @VisibleForTesting
updateDeveloperOptions()250     void updateDeveloperOptions() {
251         final Preference developerPref = findPreference(KEY_DEVELOPER);
252         if (developerPref == null) {
253             return;
254         }
255 
256         developerPref.setVisible(DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(
257                 getContext()));
258     }
259 
updateSounds()260     private void updateSounds() {
261         if (mSoundsPref == null) {
262             return;
263         }
264 
265         Intent soundIntent = new Intent(MainFragment.ACTION_SOUND);
266         final ResolveInfo info = MainFragment.systemIntentIsHandled(getContext(), soundIntent);
267         if (info != null) {
268             mSoundsPref.setVisible(false);
269         }
270     }
271 
updateGoogleSettings()272     private void updateGoogleSettings() {
273         final Preference googleSettingsPref = findPreference(KEY_GOOGLE_SETTINGS);
274         if (googleSettingsPref != null) {
275             final ResolveInfo info = MainFragment.systemIntentIsHandled(getContext(),
276                     googleSettingsPref.getIntent());
277             googleSettingsPref.setVisible(info != null);
278             if (info != null && info.activityInfo != null) {
279                 googleSettingsPref.setIcon(
280                         info.activityInfo.loadIcon(getContext().getPackageManager()));
281                 googleSettingsPref.setTitle(
282                         info.activityInfo.loadLabel(getContext().getPackageManager()));
283             }
284         }
285     }
286 
287     @VisibleForTesting
updateCastSettings()288     void updateCastSettings() {
289         final Preference castPref = findPreference(KEY_CAST_SETTINGS);
290         final SlicePreference castSlicePref = findPreference(KEY_CAST_SETTINGS_SLICE);
291         if (castPref != null) {
292             final ResolveInfo info = MainFragment.systemIntentIsHandled(
293                     getContext(), castPref.getIntent());
294             if (info != null) {
295                 try {
296                     final Context targetContext = getContext()
297                             .createPackageContext(info.resolvePackageName != null
298                                     ? info.resolvePackageName : info.activityInfo.packageName, 0);
299                     castPref.setIcon(targetContext.getDrawable(info.getIconResource()));
300                 } catch (Resources.NotFoundException | PackageManager.NameNotFoundException
301                         | SecurityException e) {
302                     Log.e(TAG, "Cast settings icon not found", e);
303                 }
304                 castPref.setTitle(info.activityInfo.loadLabel(getContext().getPackageManager()));
305             }
306         }
307         if (castSlicePref != null) {
308             if (!SliceUtils.isSliceProviderValid(getContext(), castSlicePref.getUri())
309                     || FlavorUtils.getFeatureFactory(getContext()).getBasicModeFeatureProvider()
310                     .isBasicMode(getContext())) {
311                 castSlicePref.setVisible(false);
312             }
313         }
314     }
315 
316     @VisibleForTesting
updateKeyboardAutofillSettings()317     void updateKeyboardAutofillSettings() {
318         final Preference keyboardPref = findPreference(KEY_KEYBOARD);
319 
320         List<DefaultAppInfo> candidates = AutofillHelper.getAutofillCandidates(getContext(),
321                 mPm, UserHandle.myUserId());
322 
323         // Switch title depends on whether there is autofill
324         if (candidates.isEmpty()) {
325             keyboardPref.setTitle(R.string.system_keyboard);
326         } else {
327             keyboardPref.setTitle(R.string.system_keyboard_autofill);
328         }
329 
330         CharSequence summary = "";
331         // append current keyboard to summary
332         String defaultImId = InputMethodHelper.getDefaultInputMethodId(getContext());
333         if (!TextUtils.isEmpty(defaultImId)) {
334             InputMethodInfo info = InputMethodHelper.findInputMethod(defaultImId,
335                     InputMethodHelper.getEnabledSystemInputMethodList(getContext()));
336             if (info != null) {
337                 summary = info.loadLabel(getContext().getPackageManager());
338             }
339 
340         }
341         // append current autofill to summary
342         DefaultAppInfo appInfo = AutofillHelper.getCurrentAutofill(getContext(), candidates);
343         if (appInfo != null) {
344             CharSequence autofillInfo = appInfo.loadLabel();
345             if (summary.length() > 0) {
346                 getContext().getString(R.string.string_concat, summary, autofillInfo);
347             } else {
348                 summary = autofillInfo;
349             }
350         }
351         keyboardPref.setSummary(summary);
352     }
353 
354     @Override
getPageId()355     protected int getPageId() {
356         return TvSettingsEnums.SYSTEM;
357     }
358 }
359