1/*
2 * Copyright (C) 2007 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 android.preference;
18
19import android.app.Activity;
20import android.app.ListActivity;
21import android.content.Intent;
22import android.content.SharedPreferences;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.Message;
26import android.view.View;
27
28/**
29 * Shows a hierarchy of {@link Preference} objects as
30 * lists, possibly spanning multiple screens. These preferences will
31 * automatically save to {@link SharedPreferences} as the user interacts with
32 * them. To retrieve an instance of {@link SharedPreferences} that the
33 * preference hierarchy in this activity will use, call
34 * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
35 * with a context in the same package as this activity.
36 * <p>
37 * Furthermore, the preferences shown will follow the visual style of system
38 * preferences. It is easy to create a hierarchy of preferences (that can be
39 * shown on multiple screens) via XML. For these reasons, it is recommended to
40 * use this activity (as a superclass) to deal with preferences in applications.
41 * <p>
42 * A {@link PreferenceScreen} object should be at the top of the preference
43 * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
44 * denote a screen break--that is the preferences contained within subsequent
45 * {@link PreferenceScreen} should be shown on another screen. The preference
46 * framework handles showing these other screens from the preference hierarchy.
47 * <p>
48 * The preference hierarchy can be formed in multiple ways:
49 * <li> From an XML file specifying the hierarchy
50 * <li> From different {@link Activity Activities} that each specify its own
51 * preferences in an XML file via {@link Activity} meta-data
52 * <li> From an object hierarchy rooted with {@link PreferenceScreen}
53 * <p>
54 * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
55 * root element should be a {@link PreferenceScreen}. Subsequent elements can point
56 * to actual {@link Preference} subclasses. As mentioned above, subsequent
57 * {@link PreferenceScreen} in the hierarchy will result in the screen break.
58 * <p>
59 * To specify an {@link Intent} to query {@link Activity Activities} that each
60 * have preferences, use {@link #addPreferencesFromIntent}. Each
61 * {@link Activity} can specify meta-data in the manifest (via the key
62 * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML
63 * resource. These XML resources will be inflated into a single preference
64 * hierarchy and shown by this activity.
65 * <p>
66 * To specify an object hierarchy rooted with {@link PreferenceScreen}, use
67 * {@link #setPreferenceScreen(PreferenceScreen)}.
68 * <p>
69 * As a convenience, this activity implements a click listener for any
70 * preference in the current hierarchy, see
71 * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
72 *
73 * @see Preference
74 * @see PreferenceScreen
75 */
76public abstract class PreferenceActivity extends ListActivity implements
77        PreferenceManager.OnPreferenceTreeClickListener {
78
79    private static final String PREFERENCES_TAG = "android:preferences";
80
81    private PreferenceManager mPreferenceManager;
82
83    private Bundle mSavedInstanceState;
84
85    /**
86     * The starting request code given out to preference framework.
87     */
88    private static final int FIRST_REQUEST_CODE = 100;
89
90    private static final int MSG_BIND_PREFERENCES = 0;
91    private Handler mHandler = new Handler() {
92        @Override
93        public void handleMessage(Message msg) {
94            switch (msg.what) {
95
96                case MSG_BIND_PREFERENCES:
97                    bindPreferences();
98                    break;
99            }
100        }
101    };
102
103    @Override
104    protected void onCreate(Bundle savedInstanceState) {
105        super.onCreate(savedInstanceState);
106
107        setContentView(com.android.internal.R.layout.preference_list_content);
108
109        mPreferenceManager = onCreatePreferenceManager();
110        getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
111    }
112
113    @Override
114    protected void onStop() {
115        super.onStop();
116
117        mPreferenceManager.dispatchActivityStop();
118    }
119
120    @Override
121    protected void onDestroy() {
122        super.onDestroy();
123
124        mPreferenceManager.dispatchActivityDestroy();
125    }
126
127    @Override
128    protected void onSaveInstanceState(Bundle outState) {
129        super.onSaveInstanceState(outState);
130
131        final PreferenceScreen preferenceScreen = getPreferenceScreen();
132        if (preferenceScreen != null) {
133            Bundle container = new Bundle();
134            preferenceScreen.saveHierarchyState(container);
135            outState.putBundle(PREFERENCES_TAG, container);
136        }
137    }
138
139    @Override
140    protected void onRestoreInstanceState(Bundle state) {
141        Bundle container = state.getBundle(PREFERENCES_TAG);
142        if (container != null) {
143            final PreferenceScreen preferenceScreen = getPreferenceScreen();
144            if (preferenceScreen != null) {
145                preferenceScreen.restoreHierarchyState(container);
146                mSavedInstanceState = state;
147                return;
148            }
149        }
150
151        // Only call this if we didn't save the instance state for later.
152        // If we did save it, it will be restored when we bind the adapter.
153        super.onRestoreInstanceState(state);
154    }
155
156    @Override
157    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
158        super.onActivityResult(requestCode, resultCode, data);
159
160        mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
161    }
162
163    @Override
164    public void onContentChanged() {
165        super.onContentChanged();
166        postBindPreferences();
167    }
168
169    /**
170     * Posts a message to bind the preferences to the list view.
171     * <p>
172     * Binding late is preferred as any custom preference types created in
173     * {@link #onCreate(Bundle)} are able to have their views recycled.
174     */
175    private void postBindPreferences() {
176        if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
177        mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
178    }
179
180    private void bindPreferences() {
181        final PreferenceScreen preferenceScreen = getPreferenceScreen();
182        if (preferenceScreen != null) {
183            preferenceScreen.bind(getListView());
184            if (mSavedInstanceState != null) {
185                super.onRestoreInstanceState(mSavedInstanceState);
186                mSavedInstanceState = null;
187            }
188        }
189    }
190
191    /**
192     * Creates the {@link PreferenceManager}.
193     *
194     * @return The {@link PreferenceManager} used by this activity.
195     */
196    private PreferenceManager onCreatePreferenceManager() {
197        PreferenceManager preferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
198        preferenceManager.setOnPreferenceTreeClickListener(this);
199        return preferenceManager;
200    }
201
202    /**
203     * Returns the {@link PreferenceManager} used by this activity.
204     * @return The {@link PreferenceManager}.
205     */
206    public PreferenceManager getPreferenceManager() {
207        return mPreferenceManager;
208    }
209
210    private void requirePreferenceManager() {
211        if (mPreferenceManager == null) {
212            throw new RuntimeException("This should be called after super.onCreate.");
213        }
214    }
215
216    /**
217     * Sets the root of the preference hierarchy that this activity is showing.
218     *
219     * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
220     */
221    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
222        if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
223            postBindPreferences();
224            CharSequence title = getPreferenceScreen().getTitle();
225            // Set the title of the activity
226            if (title != null) {
227                setTitle(title);
228            }
229        }
230    }
231
232    /**
233     * Gets the root of the preference hierarchy that this activity is showing.
234     *
235     * @return The {@link PreferenceScreen} that is the root of the preference
236     *         hierarchy.
237     */
238    public PreferenceScreen getPreferenceScreen() {
239        return mPreferenceManager.getPreferenceScreen();
240    }
241
242    /**
243     * Adds preferences from activities that match the given {@link Intent}.
244     *
245     * @param intent The {@link Intent} to query activities.
246     */
247    public void addPreferencesFromIntent(Intent intent) {
248        requirePreferenceManager();
249
250        setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
251    }
252
253    /**
254     * Inflates the given XML resource and adds the preference hierarchy to the current
255     * preference hierarchy.
256     *
257     * @param preferencesResId The XML resource ID to inflate.
258     */
259    public void addPreferencesFromResource(int preferencesResId) {
260        requirePreferenceManager();
261
262        setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
263                getPreferenceScreen()));
264    }
265
266    /**
267     * {@inheritDoc}
268     */
269    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
270        return false;
271    }
272
273    /**
274     * Finds a {@link Preference} based on its key.
275     *
276     * @param key The key of the preference to retrieve.
277     * @return The {@link Preference} with the key, or null.
278     * @see PreferenceGroup#findPreference(CharSequence)
279     */
280    public Preference findPreference(CharSequence key) {
281
282        if (mPreferenceManager == null) {
283            return null;
284        }
285
286        return mPreferenceManager.findPreference(key);
287    }
288
289    @Override
290    protected void onNewIntent(Intent intent) {
291        if (mPreferenceManager != null) {
292            mPreferenceManager.dispatchNewIntent(intent);
293        }
294    }
295
296}
297