• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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