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