1 /*
2  * Copyright (C) 2015 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 androidx.leanback.preference;
18 
19 import android.content.Context;
20 import android.os.Bundle;
21 import android.text.TextUtils;
22 import android.util.TypedValue;
23 import android.view.ContextThemeWrapper;
24 import android.view.LayoutInflater;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.Checkable;
28 import android.widget.TextView;
29 
30 import androidx.collection.ArraySet;
31 import androidx.leanback.widget.VerticalGridView;
32 import androidx.preference.DialogPreference;
33 import androidx.preference.ListPreference;
34 import androidx.preference.MultiSelectListPreference;
35 import androidx.recyclerview.widget.RecyclerView;
36 
37 import org.jspecify.annotations.NonNull;
38 import org.jspecify.annotations.Nullable;
39 
40 import java.util.Collections;
41 import java.util.HashSet;
42 import java.util.Set;
43 
44 /**
45  * @deprecated Use {@link LeanbackListPreferenceDialogFragmentCompat}
46  */
47 @Deprecated
48 public class LeanbackListPreferenceDialogFragment extends LeanbackPreferenceDialogFragment {
49 
50     private static final String SAVE_STATE_IS_MULTI =
51             "LeanbackListPreferenceDialogFragment.isMulti";
52     private static final String SAVE_STATE_ENTRIES = "LeanbackListPreferenceDialogFragment.entries";
53     private static final String SAVE_STATE_ENTRY_VALUES =
54             "LeanbackListPreferenceDialogFragment.entryValues";
55     private static final String SAVE_STATE_TITLE = "LeanbackListPreferenceDialogFragment.title";
56     private static final String SAVE_STATE_MESSAGE = "LeanbackListPreferenceDialogFragment.message";
57     private static final String SAVE_STATE_INITIAL_SELECTIONS =
58             "LeanbackListPreferenceDialogFragment.initialSelections";
59     private static final String SAVE_STATE_INITIAL_SELECTION =
60             "LeanbackListPreferenceDialogFragment.initialSelection";
61 
62     private boolean mMulti;
63     private CharSequence[] mEntries;
64     private CharSequence[] mEntryValues;
65     private CharSequence mDialogTitle;
66     private CharSequence mDialogMessage;
67     Set<String> mInitialSelections;
68     private String mInitialSelection;
69 
newInstanceSingle(String key)70     public static LeanbackListPreferenceDialogFragment newInstanceSingle(String key) {
71         final Bundle args = new Bundle(1);
72         args.putString(ARG_KEY, key);
73 
74         final LeanbackListPreferenceDialogFragment
75                 fragment = new LeanbackListPreferenceDialogFragment();
76         fragment.setArguments(args);
77 
78         return fragment;
79     }
80 
newInstanceMulti(String key)81     public static LeanbackListPreferenceDialogFragment newInstanceMulti(String key) {
82         final Bundle args = new Bundle(1);
83         args.putString(ARG_KEY, key);
84 
85         final LeanbackListPreferenceDialogFragment
86                 fragment = new LeanbackListPreferenceDialogFragment();
87         fragment.setArguments(args);
88 
89         return fragment;
90     }
91 
92     @Override
onCreate(Bundle savedInstanceState)93     public void onCreate(Bundle savedInstanceState) {
94         super.onCreate(savedInstanceState);
95 
96         if (savedInstanceState == null) {
97             final DialogPreference preference = getPreference();
98             mDialogTitle = preference.getDialogTitle();
99             mDialogMessage = preference.getDialogMessage();
100 
101             if (preference instanceof ListPreference) {
102                 mMulti = false;
103                 mEntries = ((ListPreference) preference).getEntries();
104                 mEntryValues = ((ListPreference) preference).getEntryValues();
105                 mInitialSelection = ((ListPreference) preference).getValue();
106             } else if (preference instanceof MultiSelectListPreference) {
107                 mMulti = true;
108                 mEntries = ((MultiSelectListPreference) preference).getEntries();
109                 mEntryValues = ((MultiSelectListPreference) preference).getEntryValues();
110                 mInitialSelections = ((MultiSelectListPreference) preference).getValues();
111             } else {
112                 throw new IllegalArgumentException("Preference must be a ListPreference or "
113                         + "MultiSelectListPreference");
114             }
115         } else {
116             mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
117             mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
118             mMulti = savedInstanceState.getBoolean(SAVE_STATE_IS_MULTI);
119             mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);
120             mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
121             if (mMulti) {
122                 final String[] initialSelections = savedInstanceState.getStringArray(
123                         SAVE_STATE_INITIAL_SELECTIONS);
124                 mInitialSelections = new ArraySet<>(
125                         initialSelections != null ? initialSelections.length : 0);
126                 if (initialSelections != null) {
127                     Collections.addAll(mInitialSelections, initialSelections);
128                 }
129             } else {
130                 mInitialSelection = savedInstanceState.getString(SAVE_STATE_INITIAL_SELECTION);
131             }
132         }
133     }
134 
135     @Override
onSaveInstanceState(Bundle outState)136     public void onSaveInstanceState(Bundle outState) {
137         super.onSaveInstanceState(outState);
138         outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
139         outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
140         outState.putBoolean(SAVE_STATE_IS_MULTI, mMulti);
141         outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);
142         outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
143         if (mMulti) {
144             outState.putStringArray(SAVE_STATE_INITIAL_SELECTIONS,
145                     mInitialSelections.toArray(new String[mInitialSelections.size()]));
146         } else {
147             outState.putString(SAVE_STATE_INITIAL_SELECTION, mInitialSelection);
148         }
149     }
150 
151     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)152     public @Nullable View onCreateView(LayoutInflater inflater, ViewGroup container,
153             Bundle savedInstanceState) {
154         final TypedValue tv = new TypedValue();
155         getActivity().getTheme().resolveAttribute(
156                 androidx.preference.R.attr.preferenceTheme, tv, true);
157         int theme = tv.resourceId;
158         if (theme == 0) {
159             // Fallback to default theme.
160             theme = R.style.PreferenceThemeOverlayLeanback;
161         }
162         Context styledContext = new ContextThemeWrapper(getActivity(), theme);
163         LayoutInflater styledInflater = inflater.cloneInContext(styledContext);
164         final View view = styledInflater.inflate(R.layout.leanback_list_preference_fragment,
165                 container, false);
166         final VerticalGridView verticalGridView =
167                 (VerticalGridView) view.findViewById(android.R.id.list);
168 
169         verticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE);
170         verticalGridView.setFocusScrollStrategy(VerticalGridView.FOCUS_SCROLL_ALIGNED);
171         verticalGridView.setAdapter(onCreateAdapter());
172         verticalGridView.requestFocus();
173 
174         final CharSequence title = mDialogTitle;
175         if (!TextUtils.isEmpty(title)) {
176             final TextView titleView = (TextView) view.findViewById(R.id.decor_title);
177             titleView.setText(title);
178         }
179 
180         final CharSequence message = mDialogMessage;
181         if (!TextUtils.isEmpty(message)) {
182             final TextView messageView = (TextView) view.findViewById(android.R.id.message);
183             messageView.setVisibility(View.VISIBLE);
184             messageView.setText(message);
185         }
186 
187         return view;
188     }
189 
onCreateAdapter()190     public RecyclerView.Adapter onCreateAdapter() {
191         //final DialogPreference preference = getPreference();
192         if (mMulti) {
193             return new AdapterMulti(mEntries, mEntryValues, mInitialSelections);
194         } else {
195             return new AdapterSingle(mEntries, mEntryValues, mInitialSelection);
196         }
197     }
198 
199     /**
200      * Adapter for single choice.
201      *
202      * @deprecated Use LeanbackListPreferenceDialogFragmentCompat.
203      */
204     @Deprecated
205     public class AdapterSingle extends RecyclerView.Adapter<ViewHolder>
206             implements ViewHolder.OnItemClickListener {
207 
208         private final CharSequence[] mEntries;
209         private final CharSequence[] mEntryValues;
210         private CharSequence mSelectedValue;
211 
AdapterSingle(CharSequence[] entries, CharSequence[] entryValues, CharSequence selectedValue)212         public AdapterSingle(CharSequence[] entries, CharSequence[] entryValues,
213                 CharSequence selectedValue) {
214             mEntries = entries;
215             mEntryValues = entryValues;
216             mSelectedValue = selectedValue;
217         }
218 
219         @Override
onCreateViewHolder(ViewGroup parent, int viewType)220         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
221             final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
222             final View view = inflater.inflate(R.layout.leanback_list_preference_item_single,
223                     parent, false);
224             return new ViewHolder(view, this);
225         }
226 
227         @Override
onBindViewHolder(ViewHolder holder, int position)228         public void onBindViewHolder(ViewHolder holder, int position) {
229             holder.getWidgetView().setChecked(
230                     TextUtils.equals(mEntryValues[position].toString(), mSelectedValue));
231             holder.getTitleView().setText(mEntries[position]);
232         }
233 
234         @Override
getItemCount()235         public int getItemCount() {
236             return mEntries.length;
237         }
238 
239         @Override
onItemClick(ViewHolder viewHolder)240         public void onItemClick(ViewHolder viewHolder) {
241             final int index = viewHolder.getAbsoluteAdapterPosition();
242             if (index == RecyclerView.NO_POSITION) {
243                 return;
244             }
245             final CharSequence entry = mEntryValues[index];
246             final ListPreference preference = (ListPreference) getPreference();
247             if (index >= 0) {
248                 String value = mEntryValues[index].toString();
249                 if (preference.callChangeListener(value)) {
250                     preference.setValue(value);
251                     mSelectedValue = entry;
252                 }
253             }
254 
255             getFragmentManager().popBackStack();
256             notifyDataSetChanged();
257         }
258     }
259 
260     /**
261      * Adapter for multiple choices.
262      *
263      * @deprecated Ue LeanbackListPreferenceDialogFragmentCompat.
264      */
265     @Deprecated
266     public class AdapterMulti extends RecyclerView.Adapter<ViewHolder>
267             implements ViewHolder.OnItemClickListener {
268 
269         private final CharSequence[] mEntries;
270         private final CharSequence[] mEntryValues;
271         private final Set<String> mSelections;
272 
AdapterMulti(CharSequence[] entries, CharSequence[] entryValues, Set<String> initialSelections)273         public AdapterMulti(CharSequence[] entries, CharSequence[] entryValues,
274                 Set<String> initialSelections) {
275             mEntries = entries;
276             mEntryValues = entryValues;
277             mSelections = new HashSet<>(initialSelections);
278         }
279 
280         @Override
onCreateViewHolder(ViewGroup parent, int viewType)281         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
282             final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
283             final View view = inflater.inflate(R.layout.leanback_list_preference_item_multi, parent,
284                     false);
285             return new ViewHolder(view, this);
286         }
287 
288         @Override
onBindViewHolder(ViewHolder holder, int position)289         public void onBindViewHolder(ViewHolder holder, int position) {
290             holder.getWidgetView().setChecked(
291                     mSelections.contains(mEntryValues[position].toString()));
292             holder.getTitleView().setText(mEntries[position]);
293         }
294 
295         @Override
getItemCount()296         public int getItemCount() {
297             return mEntries.length;
298         }
299 
300         @Override
onItemClick(ViewHolder viewHolder)301         public void onItemClick(ViewHolder viewHolder) {
302             final int index = viewHolder.getAbsoluteAdapterPosition();
303             if (index == RecyclerView.NO_POSITION) {
304                 return;
305             }
306             final String entry = mEntryValues[index].toString();
307             if (mSelections.contains(entry)) {
308                 mSelections.remove(entry);
309             } else {
310                 mSelections.add(entry);
311             }
312             final MultiSelectListPreference multiSelectListPreference
313                     = (MultiSelectListPreference) getPreference();
314             // Pass copies of the set to callChangeListener and setValues to avoid mutations
315             if (multiSelectListPreference.callChangeListener(new HashSet<>(mSelections))) {
316                 multiSelectListPreference.setValues(new HashSet<>(mSelections));
317                 mInitialSelections = mSelections;
318             } else {
319                 // Change refused, back it out
320                 if (mSelections.contains(entry)) {
321                     mSelections.remove(entry);
322                 } else {
323                     mSelections.add(entry);
324                 }
325             }
326 
327             notifyDataSetChanged();
328         }
329     }
330 
331     /**
332      * ViewHolder of the List.
333      *
334      * @deprecated Ue LeanbackListPreferenceDialogFragmentCompat.
335      */
336     @Deprecated
337     public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
338 
339         public interface OnItemClickListener {
onItemClick(ViewHolder viewHolder)340             void onItemClick(ViewHolder viewHolder);
341         }
342 
343         private final Checkable mWidgetView;
344         private final TextView mTitleView;
345         private final ViewGroup mContainer;
346         private final OnItemClickListener mListener;
347 
ViewHolder(@onNull View view, @NonNull OnItemClickListener listener)348         public ViewHolder(@NonNull View view, @NonNull OnItemClickListener listener) {
349             super(view);
350             mWidgetView = (Checkable) view.findViewById(R.id.button);
351             mContainer = (ViewGroup) view.findViewById(R.id.container);
352             mTitleView = (TextView) view.findViewById(android.R.id.title);
353             mContainer.setOnClickListener(this);
354             mListener = listener;
355         }
356 
getWidgetView()357         public Checkable getWidgetView() {
358             return mWidgetView;
359         }
360 
getTitleView()361         public TextView getTitleView() {
362             return mTitleView;
363         }
364 
getContainer()365         public ViewGroup getContainer() {
366             return mContainer;
367         }
368 
369         @Override
onClick(View v)370         public void onClick(View v) {
371             mListener.onItemClick(this);
372         }
373     }
374 }
375