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 android.support.v17.preference;
18 
19 import android.os.Bundle;
20 import android.support.annotation.NonNull;
21 import android.support.annotation.Nullable;
22 import android.support.v14.preference.MultiSelectListPreference;
23 import android.support.v17.leanback.widget.VerticalGridView;
24 import android.support.v7.preference.DialogPreference;
25 import android.support.v7.preference.ListPreference;
26 import android.support.v7.widget.RecyclerView;
27 import android.text.TextUtils;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.Checkable;
32 import android.widget.TextView;
33 
34 import java.util.HashSet;
35 import java.util.Set;
36 
37 public class LeanbackListPreferenceDialogFragment extends LeanbackPreferenceDialogFragment {
38 
newInstanceSingle(String key)39     public static LeanbackListPreferenceDialogFragment newInstanceSingle(String key) {
40         final Bundle args = new Bundle(5);
41         args.putString(ARG_KEY, key);
42 
43         final LeanbackListPreferenceDialogFragment
44                 fragment = new LeanbackListPreferenceDialogFragment();
45         fragment.setArguments(args);
46 
47         return fragment;
48     }
49 
newInstanceMulti(String key)50     public static LeanbackListPreferenceDialogFragment newInstanceMulti(String key) {
51         final Bundle args = new Bundle(5);
52         args.putString(ARG_KEY, key);
53 
54         final LeanbackListPreferenceDialogFragment
55                 fragment = new LeanbackListPreferenceDialogFragment();
56         fragment.setArguments(args);
57 
58         return fragment;
59     }
60 
61     @Override
onCreate(Bundle savedInstanceState)62     public void onCreate(Bundle savedInstanceState) {
63         super.onCreate(savedInstanceState);
64 
65         final DialogPreference preference = getPreference();
66         if (!(preference instanceof ListPreference) &&
67                 !(preference instanceof MultiSelectListPreference)) {
68             throw new IllegalArgumentException("Preference must be a ListPreference or " +
69                     "MultiSelectListPreference");
70         }
71     }
72 
73     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)74     public @Nullable View onCreateView(LayoutInflater inflater, ViewGroup container,
75             Bundle savedInstanceState) {
76         final View view = inflater.inflate(R.layout.leanback_list_preference_fragment, container,
77                 false);
78         final VerticalGridView verticalGridView =
79                 (VerticalGridView) view.findViewById(android.R.id.list);
80 
81         verticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE);
82         verticalGridView.setFocusScrollStrategy(VerticalGridView.FOCUS_SCROLL_ALIGNED);
83         verticalGridView.setAdapter(onCreateAdapter());
84         verticalGridView.requestFocus();
85 
86         final DialogPreference preference = getPreference();
87         final CharSequence title = preference.getDialogTitle();
88         if (!TextUtils.isEmpty(title)) {
89             final TextView titleView = (TextView) view.findViewById(R.id.decor_title);
90             titleView.setText(title);
91         }
92 
93         final CharSequence message = preference.getDialogMessage();
94         if (!TextUtils.isEmpty(message)) {
95             final TextView messageView = (TextView) view.findViewById(android.R.id.message);
96             messageView.setVisibility(View.VISIBLE);
97             messageView.setText(message);
98         }
99 
100         return view;
101     }
102 
onCreateAdapter()103     public RecyclerView.Adapter onCreateAdapter() {
104         final DialogPreference preference = getPreference();
105         if (preference instanceof MultiSelectListPreference) {
106             final MultiSelectListPreference pref = (MultiSelectListPreference) preference;
107             final CharSequence[] entries = pref.getEntries();
108             final CharSequence[] entryValues = pref.getEntryValues();
109             final Set<String> initialSelections = pref.getValues();
110             return new AdapterMulti(entries, entryValues, initialSelections);
111         } else if (preference instanceof ListPreference) {
112             final ListPreference pref = (ListPreference) preference;
113             final CharSequence[] entries = pref.getEntries();
114             final CharSequence[] entryValues = pref.getEntryValues();
115             final String initialSelection = pref.getValue();
116             return new AdapterSingle(entries, entryValues, initialSelection);
117         } else {
118             throw new IllegalStateException("Unknown preference type");
119         }
120     }
121 
122     public class AdapterSingle extends RecyclerView.Adapter<ViewHolder>
123             implements ViewHolder.OnItemClickListener {
124 
125         private final CharSequence[] mEntries;
126         private final CharSequence[] mEntryValues;
127         private CharSequence mSelectedValue;
128 
AdapterSingle(CharSequence[] entries, CharSequence[] entryValues, CharSequence selectedValue)129         public AdapterSingle(CharSequence[] entries, CharSequence[] entryValues,
130                 CharSequence selectedValue) {
131             mEntries = entries;
132             mEntryValues = entryValues;
133             mSelectedValue = selectedValue;
134         }
135 
136         @Override
onCreateViewHolder(ViewGroup parent, int viewType)137         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
138             final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
139             final View view = inflater.inflate(R.layout.leanback_list_preference_item_single,
140                     parent, false);
141             return new ViewHolder(view, this);
142         }
143 
144         @Override
onBindViewHolder(ViewHolder holder, int position)145         public void onBindViewHolder(ViewHolder holder, int position) {
146             holder.getWidgetView().setChecked(mEntryValues[position].equals(mSelectedValue));
147             holder.getTitleView().setText(mEntries[position]);
148         }
149 
150         @Override
getItemCount()151         public int getItemCount() {
152             return mEntries.length;
153         }
154 
155         @Override
onItemClick(ViewHolder viewHolder)156         public void onItemClick(ViewHolder viewHolder) {
157             final int index = viewHolder.getAdapterPosition();
158             if (index == RecyclerView.NO_POSITION) {
159                 return;
160             }
161             final CharSequence entry = mEntryValues[index];
162             final ListPreference preference = (ListPreference) getPreference();
163             if (index >= 0) {
164                 String value = mEntryValues[index].toString();
165                 if (preference.callChangeListener(value)) {
166                     preference.setValue(value);
167                     mSelectedValue = entry;
168                 }
169             }
170 
171             getFragmentManager().popBackStack();
172             notifyDataSetChanged();
173         }
174     }
175 
176     public class AdapterMulti extends RecyclerView.Adapter<ViewHolder>
177             implements ViewHolder.OnItemClickListener {
178 
179         private final CharSequence[] mEntries;
180         private final CharSequence[] mEntryValues;
181         private final Set<String> mSelections;
182 
AdapterMulti(CharSequence[] entries, CharSequence[] entryValues, Set<String> initialSelections)183         public AdapterMulti(CharSequence[] entries, CharSequence[] entryValues,
184                 Set<String> initialSelections) {
185             mEntries = entries;
186             mEntryValues = entryValues;
187             mSelections = new HashSet<>(initialSelections);
188         }
189 
190         @Override
onCreateViewHolder(ViewGroup parent, int viewType)191         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
192             final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
193             final View view = inflater.inflate(R.layout.leanback_list_preference_item_multi, parent,
194                     false);
195             return new ViewHolder(view, this);
196         }
197 
198         @Override
onBindViewHolder(ViewHolder holder, int position)199         public void onBindViewHolder(ViewHolder holder, int position) {
200             holder.getWidgetView().setChecked(
201                     mSelections.contains(mEntryValues[position].toString()));
202             holder.getTitleView().setText(mEntries[position]);
203         }
204 
205         @Override
getItemCount()206         public int getItemCount() {
207             return mEntries.length;
208         }
209 
210         @Override
onItemClick(ViewHolder viewHolder)211         public void onItemClick(ViewHolder viewHolder) {
212             final int index = viewHolder.getAdapterPosition();
213             if (index == RecyclerView.NO_POSITION) {
214                 return;
215             }
216             final String entry = mEntryValues[index].toString();
217             if (mSelections.contains(entry)) {
218                 mSelections.remove(entry);
219             } else {
220                 mSelections.add(entry);
221             }
222             final MultiSelectListPreference multiSelectListPreference
223                     = (MultiSelectListPreference) getPreference();
224             // Pass copies of the set to callChangeListener and setValues to avoid mutations
225             if (multiSelectListPreference.callChangeListener(new HashSet<>(mSelections))) {
226                 multiSelectListPreference.setValues(new HashSet<>(mSelections));
227             } else {
228                 // Change refused, back it out
229                 if (mSelections.contains(entry)) {
230                     mSelections.remove(entry);
231                 } else {
232                     mSelections.add(entry);
233                 }
234             }
235 
236             notifyDataSetChanged();
237         }
238     }
239 
240     public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
241 
242         public interface OnItemClickListener {
onItemClick(ViewHolder viewHolder)243             void onItemClick(ViewHolder viewHolder);
244         }
245 
246         private final Checkable mWidgetView;
247         private final TextView mTitleView;
248         private final ViewGroup mContainer;
249         private final OnItemClickListener mListener;
250 
ViewHolder(@onNull View view, @NonNull OnItemClickListener listener)251         public ViewHolder(@NonNull View view, @NonNull OnItemClickListener listener) {
252             super(view);
253             mWidgetView = (Checkable) view.findViewById(R.id.button);
254             mContainer = (ViewGroup) view.findViewById(R.id.container);
255             mTitleView = (TextView) view.findViewById(android.R.id.title);
256             mContainer.setOnClickListener(this);
257             mListener = listener;
258         }
259 
getWidgetView()260         public Checkable getWidgetView() {
261             return mWidgetView;
262         }
263 
getTitleView()264         public TextView getTitleView() {
265             return mTitleView;
266         }
267 
getContainer()268         public ViewGroup getContainer() {
269             return mContainer;
270         }
271 
272         @Override
onClick(View v)273         public void onClick(View v) {
274             mListener.onItemClick(this);
275         }
276     }
277 }
278