PreferenceActivity.java revision 19ea2e0d788810473136ceca46c1c28326daff5e
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.text.TextUtils;
27import android.view.View;
28import android.view.View.OnClickListener;
29import android.widget.Button;
30
31/**
32 * Shows a hierarchy of {@link Preference} objects as
33 * lists, possibly spanning multiple screens. These preferences will
34 * automatically save to {@link SharedPreferences} as the user interacts with
35 * them. To retrieve an instance of {@link SharedPreferences} that the
36 * preference hierarchy in this activity will use, call
37 * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
38 * with a context in the same package as this activity.
39 * <p>
40 * Furthermore, the preferences shown will follow the visual style of system
41 * preferences. It is easy to create a hierarchy of preferences (that can be
42 * shown on multiple screens) via XML. For these reasons, it is recommended to
43 * use this activity (as a superclass) to deal with preferences in applications.
44 * <p>
45 * A {@link PreferenceScreen} object should be at the top of the preference
46 * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
47 * denote a screen break--that is the preferences contained within subsequent
48 * {@link PreferenceScreen} should be shown on another screen. The preference
49 * framework handles showing these other screens from the preference hierarchy.
50 * <p>
51 * The preference hierarchy can be formed in multiple ways:
52 * <li> From an XML file specifying the hierarchy
53 * <li> From different {@link Activity Activities} that each specify its own
54 * preferences in an XML file via {@link Activity} meta-data
55 * <li> From an object hierarchy rooted with {@link PreferenceScreen}
56 * <p>
57 * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
58 * root element should be a {@link PreferenceScreen}. Subsequent elements can point
59 * to actual {@link Preference} subclasses. As mentioned above, subsequent
60 * {@link PreferenceScreen} in the hierarchy will result in the screen break.
61 * <p>
62 * To specify an {@link Intent} to query {@link Activity Activities} that each
63 * have preferences, use {@link #addPreferencesFromIntent}. Each
64 * {@link Activity} can specify meta-data in the manifest (via the key
65 * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML
66 * resource. These XML resources will be inflated into a single preference
67 * hierarchy and shown by this activity.
68 * <p>
69 * To specify an object hierarchy rooted with {@link PreferenceScreen}, use
70 * {@link #setPreferenceScreen(PreferenceScreen)}.
71 * <p>
72 * As a convenience, this activity implements a click listener for any
73 * preference in the current hierarchy, see
74 * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
75 *
76 * @see Preference
77 * @see PreferenceScreen
78 */
79public abstract class PreferenceActivity extends ListActivity implements
80        PreferenceManager.OnPreferenceTreeClickListener {
81
82    private static final String PREFERENCES_TAG = "android:preferences";
83
84    // extras that allow any preference activity to be launched as part of a wizard
85
86    // show Back and Next buttons? takes boolean parameter
87    // Back will then return RESULT_CANCELED and Next RESULT_OK
88    private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
89
90    // specify custom text for the Back or Next buttons, or cause a button to not appear
91    // at all by setting it to null
92    private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
93    private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
94
95    private Button mNextButton;
96
97    private PreferenceManager mPreferenceManager;
98
99    private Bundle mSavedInstanceState;
100
101    /**
102     * The starting request code given out to preference framework.
103     */
104    private static final int FIRST_REQUEST_CODE = 100;
105
106    private static final int MSG_BIND_PREFERENCES = 0;
107    private Handler mHandler = new Handler() {
108        @Override
109        public void handleMessage(Message msg) {
110            switch (msg.what) {
111
112                case MSG_BIND_PREFERENCES:
113                    bindPreferences();
114                    break;
115            }
116        }
117    };
118
119    @Override
120    protected void onCreate(Bundle savedInstanceState) {
121        super.onCreate(savedInstanceState);
122
123        setContentView(com.android.internal.R.layout.preference_list_content);
124
125        // see if we should show Back/Next buttons
126        Intent intent = getIntent();
127        if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
128
129            findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
130
131            Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
132            backButton.setOnClickListener(new OnClickListener() {
133                public void onClick(View v) {
134                    setResult(RESULT_CANCELED);
135                    finish();
136                }
137            });
138            mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
139            mNextButton.setOnClickListener(new OnClickListener() {
140                public void onClick(View v) {
141                    setResult(RESULT_OK);
142                    finish();
143                }
144            });
145
146            // set our various button parameters
147            if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
148                String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
149                if (TextUtils.isEmpty(buttonText)) {
150                    mNextButton.setVisibility(View.GONE);
151                }
152                else {
153                    mNextButton.setText(buttonText);
154                }
155            }
156            if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
157                String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
158                if (TextUtils.isEmpty(buttonText)) {
159                    backButton.setVisibility(View.GONE);
160                }
161                else {
162                    backButton.setText(buttonText);
163                }
164            }
165        }
166
167        mPreferenceManager = onCreatePreferenceManager();
168        getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
169    }
170
171    @Override
172    protected void onStop() {
173        super.onStop();
174
175        mPreferenceManager.dispatchActivityStop();
176    }
177
178    @Override
179    protected void onDestroy() {
180        super.onDestroy();
181        mPreferenceManager.dispatchActivityDestroy();
182    }
183
184    @Override
185    protected void onSaveInstanceState(Bundle outState) {
186        super.onSaveInstanceState(outState);
187
188        final PreferenceScreen preferenceScreen = getPreferenceScreen();
189        if (preferenceScreen != null) {
190            Bundle container = new Bundle();
191            preferenceScreen.saveHierarchyState(container);
192            outState.putBundle(PREFERENCES_TAG, container);
193        }
194    }
195
196    @Override
197    protected void onRestoreInstanceState(Bundle state) {
198        Bundle container = state.getBundle(PREFERENCES_TAG);
199        if (container != null) {
200            final PreferenceScreen preferenceScreen = getPreferenceScreen();
201            if (preferenceScreen != null) {
202                preferenceScreen.restoreHierarchyState(container);
203                mSavedInstanceState = state;
204                return;
205            }
206        }
207
208        // Only call this if we didn't save the instance state for later.
209        // If we did save it, it will be restored when we bind the adapter.
210        super.onRestoreInstanceState(state);
211    }
212
213    @Override
214    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
215        super.onActivityResult(requestCode, resultCode, data);
216
217        mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
218    }
219
220    @Override
221    public void onContentChanged() {
222        super.onContentChanged();
223        postBindPreferences();
224    }
225
226    /**
227     * Posts a message to bind the preferences to the list view.
228     * <p>
229     * Binding late is preferred as any custom preference types created in
230     * {@link #onCreate(Bundle)} are able to have their views recycled.
231     */
232    private void postBindPreferences() {
233        if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
234        mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
235    }
236
237    private void bindPreferences() {
238        final PreferenceScreen preferenceScreen = getPreferenceScreen();
239        if (preferenceScreen != null) {
240            preferenceScreen.bind(getListView());
241            if (mSavedInstanceState != null) {
242                super.onRestoreInstanceState(mSavedInstanceState);
243                mSavedInstanceState = null;
244            }
245        }
246    }
247
248    /**
249     * Creates the {@link PreferenceManager}.
250     *
251     * @return The {@link PreferenceManager} used by this activity.
252     */
253    private PreferenceManager onCreatePreferenceManager() {
254        PreferenceManager preferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
255        preferenceManager.setOnPreferenceTreeClickListener(this);
256        return preferenceManager;
257    }
258
259    /**
260     * Returns the {@link PreferenceManager} used by this activity.
261     * @return The {@link PreferenceManager}.
262     */
263    public PreferenceManager getPreferenceManager() {
264        return mPreferenceManager;
265    }
266
267    private void requirePreferenceManager() {
268        if (mPreferenceManager == null) {
269            throw new RuntimeException("This should be called after super.onCreate.");
270        }
271    }
272
273    /**
274     * Sets the root of the preference hierarchy that this activity is showing.
275     *
276     * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
277     */
278    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
279        if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
280            postBindPreferences();
281            CharSequence title = getPreferenceScreen().getTitle();
282            // Set the title of the activity
283            if (title != null) {
284                setTitle(title);
285            }
286        }
287    }
288
289    /**
290     * Gets the root of the preference hierarchy that this activity is showing.
291     *
292     * @return The {@link PreferenceScreen} that is the root of the preference
293     *         hierarchy.
294     */
295    public PreferenceScreen getPreferenceScreen() {
296        return mPreferenceManager.getPreferenceScreen();
297    }
298
299    /**
300     * Adds preferences from activities that match the given {@link Intent}.
301     *
302     * @param intent The {@link Intent} to query activities.
303     */
304    public void addPreferencesFromIntent(Intent intent) {
305        requirePreferenceManager();
306
307        setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
308    }
309
310    /**
311     * Inflates the given XML resource and adds the preference hierarchy to the current
312     * preference hierarchy.
313     *
314     * @param preferencesResId The XML resource ID to inflate.
315     */
316    public void addPreferencesFromResource(int preferencesResId) {
317        requirePreferenceManager();
318
319        setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
320                getPreferenceScreen()));
321    }
322
323    /**
324     * {@inheritDoc}
325     */
326    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
327        return false;
328    }
329
330    /**
331     * Finds a {@link Preference} based on its key.
332     *
333     * @param key The key of the preference to retrieve.
334     * @return The {@link Preference} with the key, or null.
335     * @see PreferenceGroup#findPreference(CharSequence)
336     */
337    public Preference findPreference(CharSequence key) {
338
339        if (mPreferenceManager == null) {
340            return null;
341        }
342
343        return mPreferenceManager.findPreference(key);
344    }
345
346    @Override
347    protected void onNewIntent(Intent intent) {
348        if (mPreferenceManager != null) {
349            mPreferenceManager.dispatchNewIntent(intent);
350        }
351    }
352
353    // give subclasses access to the Next button
354    /** @hide */
355    protected boolean hasNextButton() {
356        return mNextButton != null;
357    }
358    /** @hide */
359    protected Button getNextButton() {
360        return mNextButton;
361    }
362}
363