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