1 /* 2 * Copyright (C) 2013 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.settings.accessibility; 18 19 import android.app.AlertDialog.Builder; 20 import android.app.Dialog; 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.preference.DialogPreference; 26 import android.util.AttributeSet; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.AbsListView; 31 import android.widget.AdapterView; 32 import android.widget.AdapterView.OnItemClickListener; 33 import android.widget.BaseAdapter; 34 35 /** 36 * Abstract dialog preference that displays a set of values and optional titles. 37 */ 38 public abstract class ListDialogPreference extends DialogPreference { 39 private CharSequence[] mEntryTitles; 40 private int[] mEntryValues; 41 42 private OnValueChangedListener mOnValueChangedListener; 43 44 /** The layout resource to use for grid items. */ 45 private int mListItemLayout; 46 47 /** The current value of this preference. */ 48 private int mValue; 49 50 /** The index within the value set of the current value. */ 51 private int mValueIndex; 52 53 /** Whether the value had been set using {@link #setValue}. */ 54 private boolean mValueSet; 55 ListDialogPreference(Context context, AttributeSet attrs)56 public ListDialogPreference(Context context, AttributeSet attrs) { 57 super(context, attrs); 58 } 59 60 /** 61 * Sets a listened to invoke when the value of this preference changes. 62 * 63 * @param listener the listener to invoke 64 */ setOnValueChangedListener(OnValueChangedListener listener)65 public void setOnValueChangedListener(OnValueChangedListener listener) { 66 mOnValueChangedListener = listener; 67 } 68 69 /** 70 * Sets the layout to use for grid items. 71 * 72 * @param layoutResId the layout to use for displaying grid items 73 */ setListItemLayoutResource(int layoutResId)74 public void setListItemLayoutResource(int layoutResId) { 75 mListItemLayout = layoutResId; 76 } 77 78 /** 79 * Sets the list of item values. Values must be distinct. 80 * 81 * @param values the list of item values 82 */ setValues(int[] values)83 public void setValues(int[] values) { 84 mEntryValues = values; 85 86 if (mValueSet && mValueIndex == AbsListView.INVALID_POSITION) { 87 mValueIndex = getIndexForValue(mValue); 88 } 89 } 90 91 /** 92 * Sets the list of item titles. May be null if no titles are specified, or 93 * may be shorter than the list of values to leave some titles unspecified. 94 * 95 * @param titles the list of item titles 96 */ setTitles(CharSequence[] titles)97 public void setTitles(CharSequence[] titles) { 98 mEntryTitles = titles; 99 } 100 101 /** 102 * Populates a list item view with data for the specified index. 103 * 104 * @param view the view to populate 105 * @param index the index for which to populate the view 106 * @see #setListItemLayoutResource(int) 107 * @see #getValueAt(int) 108 * @see #getTitleAt(int) 109 */ onBindListItem(View view, int index)110 protected abstract void onBindListItem(View view, int index); 111 112 /** 113 * @return the title at the specified index, or null if none specified 114 */ getTitleAt(int index)115 protected CharSequence getTitleAt(int index) { 116 if (mEntryTitles == null || mEntryTitles.length <= index) { 117 return null; 118 } 119 120 return mEntryTitles[index]; 121 } 122 123 /** 124 * @return the value at the specified index 125 */ getValueAt(int index)126 protected int getValueAt(int index) { 127 return mEntryValues[index]; 128 } 129 130 @Override getSummary()131 public CharSequence getSummary() { 132 if (mValueIndex >= 0) { 133 return getTitleAt(mValueIndex); 134 } 135 136 return null; 137 } 138 139 @Override onPrepareDialogBuilder(Builder builder)140 protected void onPrepareDialogBuilder(Builder builder) { 141 super.onPrepareDialogBuilder(builder); 142 143 final Context context = getContext(); 144 final int dialogLayout = getDialogLayoutResource(); 145 final View picker = LayoutInflater.from(context).inflate(dialogLayout, null); 146 final ListPreferenceAdapter adapter = new ListPreferenceAdapter(); 147 final AbsListView list = (AbsListView) picker.findViewById(android.R.id.list); 148 list.setAdapter(adapter); 149 list.setOnItemClickListener(new OnItemClickListener() { 150 @Override 151 public void onItemClick(AdapterView<?> adapter, View v, int position, long id) { 152 if (callChangeListener((int) id)) { 153 setValue((int) id); 154 } 155 156 final Dialog dialog = getDialog(); 157 if (dialog != null) { 158 dialog.dismiss(); 159 } 160 } 161 }); 162 163 // Set initial selection. 164 final int selectedPosition = getIndexForValue(mValue); 165 if (selectedPosition != AbsListView.INVALID_POSITION) { 166 list.setSelection(selectedPosition); 167 } 168 169 builder.setView(picker); 170 builder.setPositiveButton(null, null); 171 } 172 173 /** 174 * @return the index of the specified value within the list of entry values, 175 * or {@link AbsListView#INVALID_POSITION} if not found 176 */ getIndexForValue(int value)177 protected int getIndexForValue(int value) { 178 final int[] values = mEntryValues; 179 if (values != null) { 180 final int count = values.length; 181 for (int i = 0; i < count; i++) { 182 if (values[i] == value) { 183 return i; 184 } 185 } 186 } 187 188 return AbsListView.INVALID_POSITION; 189 } 190 191 /** 192 * Sets the current value. If the value exists within the set of entry 193 * values, updates the selection index. 194 * 195 * @param value the value to set 196 */ setValue(int value)197 public void setValue(int value) { 198 final boolean changed = mValue != value; 199 if (changed || !mValueSet) { 200 mValue = value; 201 mValueIndex = getIndexForValue(value); 202 mValueSet = true; 203 persistInt(value); 204 if (changed) { 205 notifyDependencyChange(shouldDisableDependents()); 206 notifyChanged(); 207 } 208 if (mOnValueChangedListener != null) { 209 mOnValueChangedListener.onValueChanged(this, value); 210 } 211 } 212 } 213 214 /** 215 * @return the current value 216 */ getValue()217 public int getValue() { 218 return mValue; 219 } 220 221 @Override onGetDefaultValue(TypedArray a, int index)222 protected Object onGetDefaultValue(TypedArray a, int index) { 223 return a.getInt(index, 0); 224 } 225 226 @Override onSetInitialValue(boolean restoreValue, Object defaultValue)227 protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { 228 setValue(restoreValue ? getPersistedInt(mValue) : (Integer) defaultValue); 229 } 230 231 @Override onSaveInstanceState()232 protected Parcelable onSaveInstanceState() { 233 final Parcelable superState = super.onSaveInstanceState(); 234 if (isPersistent()) { 235 // No need to save instance state since it's persistent 236 return superState; 237 } 238 239 final SavedState myState = new SavedState(superState); 240 myState.value = getValue(); 241 return myState; 242 } 243 244 @Override onRestoreInstanceState(Parcelable state)245 protected void onRestoreInstanceState(Parcelable state) { 246 if (state == null || !state.getClass().equals(SavedState.class)) { 247 // Didn't save state for us in onSaveInstanceState 248 super.onRestoreInstanceState(state); 249 return; 250 } 251 252 SavedState myState = (SavedState) state; 253 super.onRestoreInstanceState(myState.getSuperState()); 254 setValue(myState.value); 255 } 256 257 private class ListPreferenceAdapter extends BaseAdapter { 258 private LayoutInflater mInflater; 259 260 @Override getCount()261 public int getCount() { 262 return mEntryValues.length; 263 } 264 265 @Override getItem(int position)266 public Integer getItem(int position) { 267 return mEntryValues[position]; 268 } 269 270 @Override getItemId(int position)271 public long getItemId(int position) { 272 return mEntryValues[position]; 273 } 274 275 @Override hasStableIds()276 public boolean hasStableIds() { 277 return true; 278 } 279 280 @Override getView(int position, View convertView, ViewGroup parent)281 public View getView(int position, View convertView, ViewGroup parent) { 282 if (convertView == null) { 283 if (mInflater == null) { 284 mInflater = LayoutInflater.from(parent.getContext()); 285 } 286 convertView = mInflater.inflate(mListItemLayout, parent, false); 287 } 288 onBindListItem(convertView, position); 289 return convertView; 290 } 291 } 292 293 private static class SavedState extends BaseSavedState { 294 public int value; 295 SavedState(Parcel source)296 public SavedState(Parcel source) { 297 super(source); 298 value = source.readInt(); 299 } 300 301 @Override writeToParcel(Parcel dest, int flags)302 public void writeToParcel(Parcel dest, int flags) { 303 super.writeToParcel(dest, flags); 304 dest.writeInt(value); 305 } 306 SavedState(Parcelable superState)307 public SavedState(Parcelable superState) { 308 super(superState); 309 } 310 311 @SuppressWarnings({ "hiding", "unused" }) 312 public static final Creator<SavedState> CREATOR = new Creator<SavedState>() { 313 @Override 314 public SavedState createFromParcel(Parcel in) { 315 return new SavedState(in); 316 } 317 318 @Override 319 public SavedState[] newArray(int size) { 320 return new SavedState[size]; 321 } 322 }; 323 } 324 325 public interface OnValueChangedListener { onValueChanged(ListDialogPreference preference, int value)326 public void onValueChanged(ListDialogPreference preference, int value); 327 } 328 } 329