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.compat; 18 19 import android.animation.AnimatorInflater; 20 import android.content.Context; 21 import android.os.Bundle; 22 import android.view.Gravity; 23 import android.view.KeyEvent; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.widget.TextView; 28 29 import androidx.annotation.NonNull; 30 import androidx.leanback.preference.LeanbackPreferenceFragmentCompat; 31 import androidx.preference.Preference; 32 import androidx.preference.PreferenceGroup; 33 import androidx.preference.PreferenceGroupAdapter; 34 import androidx.preference.PreferenceScreen; 35 import androidx.preference.PreferenceViewHolder; 36 import androidx.preference.TwoStatePreference; 37 import androidx.recyclerview.widget.RecyclerView; 38 39 import com.android.tv.settings.HasSettingsManager; 40 import com.android.tv.settings.R; 41 import com.android.tv.settings.library.PreferenceCompat; 42 import com.android.tv.settings.library.SettingsManager; 43 import com.android.tv.settings.library.State; 44 import com.android.tv.settings.library.overlay.FlavorUtils; 45 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment; 46 47 import java.util.List; 48 49 /** Provide utility class to render settings preferences. */ 50 public abstract class PreferenceControllerFragmentCompat extends LeanbackPreferenceFragmentCompat 51 implements Preference.OnPreferenceChangeListener { 52 private SettingsManager mSettingsManager; 53 private String mTitle; 54 private State mState; 55 56 @Override onAttach(Context context)57 public void onAttach(Context context) { 58 super.onAttach(context); 59 if (getActivity() instanceof HasSettingsManager) { 60 mSettingsManager = ((HasSettingsManager) getActivity()).getSettingsManager(); 61 mState = mSettingsManager.createState(getStateIdentifier()); 62 if (mState != null) { 63 mState.onAttach(); 64 } 65 } 66 } 67 68 @Override onCreate(Bundle savedInstanceState)69 public void onCreate(Bundle savedInstanceState) { 70 super.onCreate(savedInstanceState); 71 if (mState != null) { 72 mState.onCreate(getArguments()); 73 } 74 } 75 76 @Override onStart()77 public void onStart() { 78 super.onStart(); 79 if (mState != null) { 80 mState.onStart(); 81 } 82 } 83 84 @Override onResume()85 public void onResume() { 86 super.onResume(); 87 if (getCallbackFragment() instanceof TwoPanelSettingsFragment) { 88 TwoPanelSettingsFragment parentFragment = 89 (TwoPanelSettingsFragment) getCallbackFragment(); 90 parentFragment.addListenerForFragment(this); 91 } 92 if (mState != null) { 93 mState.onResume(); 94 } 95 } 96 97 @Override onPause()98 public void onPause() { 99 super.onPause(); 100 if (getCallbackFragment() instanceof TwoPanelSettingsFragment) { 101 TwoPanelSettingsFragment parentFragment = 102 (TwoPanelSettingsFragment) getCallbackFragment(); 103 parentFragment.removeListenerForFragment(this); 104 } 105 if (mState != null) { 106 mState.onPause(); 107 } 108 } 109 110 @Override onStop()111 public void onStop() { 112 super.onStop(); 113 if (mState != null) { 114 mState.onStop(); 115 } 116 } 117 118 @Override onDestroy()119 public void onDestroy() { 120 super.onDestroy(); 121 if (mState != null) { 122 mState.onDestroy(); 123 } 124 } 125 126 @Override onDetach()127 public void onDetach() { 128 super.onDetach(); 129 if (mState != null) { 130 mState.onDetach(); 131 } 132 } 133 134 @Override onPreferenceTreeClick(Preference preference)135 public boolean onPreferenceTreeClick(Preference preference) { 136 if (getActivity() instanceof HasSettingsManager) { 137 if (mState == null || (!(preference instanceof HasKeys))) { 138 return super.onPreferenceTreeClick(preference); 139 } 140 boolean handled = mSettingsManager.onPreferenceClick( 141 mState, 142 ((HasKeys) preference).getKeys(), 143 preference instanceof TwoStatePreference 144 && ((TwoStatePreference) preference).isChecked()); 145 if (handled) { 146 return true; 147 } 148 } 149 return super.onPreferenceTreeClick(preference); 150 } 151 152 @Override onPreferenceChange(Preference preference, Object newValue)153 public boolean onPreferenceChange(Preference preference, Object newValue) { 154 if (mSettingsManager == null || mState == null || !(preference instanceof HasKeys)) { 155 return false; 156 } 157 return mSettingsManager.onPreferenceChange( 158 mState, 159 ((HasKeys) preference).getKeys(), 160 newValue); 161 } 162 findTargetPreference(String[] key)163 protected Preference findTargetPreference(String[] key) { 164 Preference preference = findPreference(key[0]); 165 for (int i = 1; i < key.length; i++) { 166 if (preference instanceof PreferenceGroup) { 167 PreferenceGroup preferenceGroup = (PreferenceGroup) preference; 168 preference = preferenceGroup.findPreference(key[i]); 169 } else { 170 return null; 171 } 172 } 173 return preference; 174 } 175 updatePref(PreferenceCompat prefCompat)176 public HasKeys updatePref(PreferenceCompat prefCompat) { 177 if (prefCompat == null) { 178 return null; 179 } 180 String[] key = prefCompat.getKey(); 181 Preference preference = findTargetPreference(key); 182 if (preference == null) { 183 return null; 184 } 185 186 RenderUtil.updatePreference( 187 getContext(), (HasKeys) preference, prefCompat, preference.getOrder()); 188 if (prefCompat.hasOnPreferenceChangeListener()) { 189 preference.setOnPreferenceChangeListener(this); 190 } 191 if (prefCompat.getChildPrefCompats() != null && prefCompat.getChildPrefCompats().size() > 0 192 && preference instanceof PreferenceGroup) { 193 RenderUtil.updatePreferenceGroup((PreferenceGroup) preference, 194 prefCompat.getChildPrefCompats()); 195 } 196 return (HasKeys) preference; 197 } 198 updateAllPref(List<PreferenceCompat> preferenceCompatList)199 public void updateAllPref(List<PreferenceCompat> preferenceCompatList) { 200 if (preferenceCompatList == null) { 201 return; 202 } 203 preferenceCompatList.stream() 204 .forEach(preferenceCompat -> updatePref(preferenceCompat)); 205 } 206 updateScreenTitle(String title)207 public void updateScreenTitle(String title) { 208 setTitle(title); 209 mTitle = title; 210 } 211 212 @Override onCreatePreferences(Bundle bundle, String s)213 public void onCreatePreferences(Bundle bundle, String s) { 214 } 215 216 /** 217 * Return the state identifier to be matched with SettingsAPI for the fragment. 218 * 219 * @return state identifier 220 */ getStateIdentifier()221 public abstract int getStateIdentifier(); 222 getState()223 public State getState() { 224 return mState; 225 } 226 getState(Class<T> clazz)227 public <T extends State> T getState(Class<T> clazz) { 228 return clazz.cast(mState); 229 } 230 231 @Override onViewCreated(View view, Bundle savedInstanceState)232 public void onViewCreated(View view, Bundle savedInstanceState) { 233 super.onViewCreated(view, savedInstanceState); 234 if (view != null) { 235 TextView titleView = view.findViewById(R.id.decor_title); 236 // We rely on getResources().getConfiguration().getLayoutDirection() instead of 237 // view.isLayoutRtl() as the latter could return false in some complex scenarios even if 238 // it is RTL. 239 if (titleView != null) { 240 if (mTitle != null) { 241 titleView.setText(mTitle); 242 } 243 if (getResources().getConfiguration().getLayoutDirection() 244 == View.LAYOUT_DIRECTION_RTL) { 245 titleView.setGravity(Gravity.RIGHT); 246 } 247 } 248 if (FlavorUtils.isTwoPanel(getContext())) { 249 ViewGroup decor = view.findViewById(R.id.decor_title_container); 250 if (decor != null) { 251 decor.setOutlineProvider(null); 252 decor.setBackgroundResource(R.color.tp_preference_panel_background_color); 253 } 254 } 255 removeAnimationClipping(view); 256 } 257 } 258 removeAnimationClipping(View v)259 protected void removeAnimationClipping(View v) { 260 if (v instanceof ViewGroup) { 261 ((ViewGroup) v).setClipChildren(false); 262 ((ViewGroup) v).setClipToPadding(false); 263 for (int index = 0; index < ((ViewGroup) v).getChildCount(); index++) { 264 View child = ((ViewGroup) v).getChildAt(index); 265 removeAnimationClipping(child); 266 } 267 } 268 } 269 270 @Override onCreateAdapter(PreferenceScreen preferenceScreen)271 protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { 272 return new PreferenceGroupAdapter(preferenceScreen) { 273 @Override 274 @NonNull 275 public PreferenceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, 276 int viewType) { 277 PreferenceViewHolder vh = super.onCreateViewHolder(parent, viewType); 278 if (FlavorUtils.isTwoPanel(getContext())) { 279 vh.itemView.setStateListAnimator(AnimatorInflater.loadStateListAnimator( 280 getContext(), R.animator.preference)); 281 } 282 vh.itemView.setOnTouchListener((v, e) -> { 283 if (e.getActionMasked() == MotionEvent.ACTION_DOWN) { 284 vh.itemView.requestFocus(); 285 v.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, 286 KeyEvent.KEYCODE_DPAD_CENTER)); 287 return true; 288 } else if (e.getActionMasked() == MotionEvent.ACTION_UP) { 289 v.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, 290 KeyEvent.KEYCODE_DPAD_CENTER)); 291 return true; 292 } 293 return false; 294 }); 295 vh.itemView.setFocusable(true); 296 vh.itemView.setFocusableInTouchMode(true); 297 return vh; 298 } 299 }; 300 } 301 302 @Override 303 public void onDisplayPreferenceDialog(Preference preference) { 304 if (getActivity() instanceof HasSettingsManager) { 305 if (mState == null || (!(preference instanceof HasKeys))) { 306 super.onDisplayPreferenceDialog(preference); 307 return; 308 } 309 mSettingsManager.onDisplayPreferenceDialog(mState, ((HasKeys) preference).getKeys()); 310 return; 311 } 312 super.onDisplayPreferenceDialog(preference); 313 } 314 } 315 316