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