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