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