blob: c28ccd3c7a4065c962dab389a98b5f15062e17a9 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070019import com.android.internal.util.XmlUtils;
20
21import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.app.Fragment;
Andrew Stadleraa904f42010-09-02 14:50:08 -070025import android.app.FragmentTransaction;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.app.ListActivity;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070027import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.content.Intent;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070029import android.content.res.Configuration;
30import android.content.res.TypedArray;
31import android.content.res.XmlResourceParser;
32import android.graphics.drawable.Drawable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.os.Bundle;
34import android.os.Handler;
35import android.os.Message;
Freeman Ng19ea2e02010-03-25 15:09:00 -070036import android.text.TextUtils;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070037import android.util.AttributeSet;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070038import android.util.Xml;
39import android.view.LayoutInflater;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.view.View;
Freeman Ng19ea2e02010-03-25 15:09:00 -070041import android.view.View.OnClickListener;
Andrew Stadleraa904f42010-09-02 14:50:08 -070042import android.view.ViewGroup;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070043import android.widget.ArrayAdapter;
Freeman Ng19ea2e02010-03-25 15:09:00 -070044import android.widget.Button;
Dianne Hackborn5c769a42010-08-26 17:08:08 -070045import android.widget.FrameLayout;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070046import android.widget.ImageView;
47import android.widget.ListView;
48import android.widget.TextView;
49
50import java.io.IOException;
51import java.util.ArrayList;
52import java.util.List;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053
54/**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070055 * This is the base class for an activity to show a hierarchy of preferences
56 * to the user. Prior to {@link android.os.Build.VERSION_CODES#HONEYCOMB}
57 * this class only allowed the display of a single set of preference; this
58 * functionality should now be found in the new {@link PreferenceFragment}
59 * class. If you are using PreferenceActivity in its old mode, the documentation
60 * there applies to the deprecated APIs here.
Freeman Ng19ea2e02010-03-25 15:09:00 -070061 *
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070062 * <p>This activity shows one or more headers of preferences, each of with
63 * is associated with a {@link PreferenceFragment} to display the preferences
64 * of that header. The actual layout and display of these associations can
65 * however vary; currently there are two major approaches it may take:
66 *
67 * <ul>
68 * <li>On a small screen it may display only the headers as a single list
69 * when first launched. Selecting one of the header items will re-launch
70 * the activity with it only showing the PreferenceFragment of that header.
71 * <li>On a large screen in may display both the headers and current
72 * PreferenceFragment together as panes. Selecting a header item switches
73 * to showing the correct PreferenceFragment for that item.
74 * </ul>
75 *
76 * <p>Subclasses of PreferenceActivity should implement
77 * {@link #onBuildHeaders} to populate the header list with the desired
78 * items. Doing this implicitly switches the class into its new "headers
79 * + fragments" mode rather than the old style of just showing a single
80 * preferences list.
81 *
82 * <a name="SampleCode"></a>
83 * <h3>Sample Code</h3>
84 *
85 * <p>The following sample code shows a simple preference activity that
86 * has two different sets of preferences. The implementation, consisting
87 * of the activity itself as well as its two preference fragments is:</p>
88 *
89 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/PreferenceWithHeaders.java
90 * activity}
91 *
92 * <p>The preference_headers resource describes the headers to be displayed
93 * and the fragments associated with them. It is:
94 *
95 * {@sample development/samples/ApiDemos/res/xml/preference_headers.xml headers}
96 *
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -070097 * <p>The first header is shown by Prefs1Fragment, which populates itself
98 * from the following XML resource:</p>
99 *
100 * {@sample development/samples/ApiDemos/res/xml/fragmented_preferences.xml preferences}
101 *
102 * <p>Note that this XML resource contains a preference screen holding another
103 * fragment, the Prefs1FragmentInner implemented here. This allows the user
104 * to traverse down a hierarchy of preferences; pressing back will pop each
105 * fragment off the stack to return to the previous preferences.
106 *
107 * <p>See {@link PreferenceFragment} for information on implementing the
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700108 * fragments themselves.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109 */
110public abstract class PreferenceActivity extends ListActivity implements
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700111 PreferenceManager.OnPreferenceTreeClickListener,
112 PreferenceFragment.OnPreferenceStartFragmentCallback {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700113 private static final String TAG = "PreferenceActivity";
Freeman Ng19ea2e02010-03-25 15:09:00 -0700114
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 private static final String PREFERENCES_TAG = "android:preferences";
Freeman Ng19ea2e02010-03-25 15:09:00 -0700116
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700117 /**
118 * When starting this activity, the invoking Intent can contain this extra
119 * string to specify which fragment should be initially displayed.
120 */
121 public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700122
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700123 /**
124 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
125 * this extra can also be specify to supply a Bundle of arguments to pass
126 * to that fragment when it is instantiated during the initial creation
127 * of PreferenceActivity.
128 */
129 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
130
131 /**
132 * When starting this activity, the invoking Intent can contain this extra
133 * boolean that the header list should not be displayed. This is most often
134 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
135 * the activity to display a specific fragment that the user has navigated
136 * to.
137 */
138 public static final String EXTRA_NO_HEADERS = ":android:no_headers";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700139
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700140 private static final String BACK_STACK_PREFS = ":android:prefs";
141
Freeman Ng19ea2e02010-03-25 15:09:00 -0700142 // extras that allow any preference activity to be launched as part of a wizard
143
144 // show Back and Next buttons? takes boolean parameter
145 // Back will then return RESULT_CANCELED and Next RESULT_OK
146 private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
147
Freeman Ng09dbf182010-08-11 15:45:01 -0700148 // add a Skip button?
149 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
150
Freeman Ng19ea2e02010-03-25 15:09:00 -0700151 // specify custom text for the Back or Next buttons, or cause a button to not appear
152 // at all by setting it to null
153 private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
154 private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
155
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700156 // --- State for new mode when showing a list of headers + prefs fragment
157
158 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
159
160 private HeaderAdapter mAdapter;
161
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700162 private FrameLayout mListFooter;
163
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700164 private View mPrefsContainer;
165
166 private boolean mSinglePane;
167
168 // --- State for old mode when showing a single preference list
Freeman Ng19ea2e02010-03-25 15:09:00 -0700169
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 private PreferenceManager mPreferenceManager;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700171
Adam Powelle7fea452010-03-18 14:51:39 -0700172 private Bundle mSavedInstanceState;
173
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700174 // --- Common state
175
176 private Button mNextButton;
177
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 /**
179 * The starting request code given out to preference framework.
180 */
181 private static final int FIRST_REQUEST_CODE = 100;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700182
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 private static final int MSG_BIND_PREFERENCES = 0;
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700184 private static final int MSG_BUILD_HEADERS = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 private Handler mHandler = new Handler() {
186 @Override
187 public void handleMessage(Message msg) {
188 switch (msg.what) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189 case MSG_BIND_PREFERENCES:
190 bindPreferences();
191 break;
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700192 case MSG_BUILD_HEADERS:
193 onBuildHeaders(mHeaders);
194 mAdapter.notifyDataSetChanged();
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700195 Header header = onGetNewHeader();
196 if (header != null && header.fragment != null) {
197 switchToHeader(header.fragment, header.fragmentArguments);
198 }
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700199 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 }
201 }
202 };
203
Andrew Stadler468c3232010-08-17 16:16:42 -0700204 private static class HeaderAdapter extends ArrayAdapter<Header> {
205 private static class HeaderViewHolder {
206 ImageView icon;
207 TextView title;
208 TextView summary;
209 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700210
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700211 private LayoutInflater mInflater;
212
213 public HeaderAdapter(Context context, List<Header> objects) {
214 super(context, 0, objects);
215 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
216 }
217
218 @Override
219 public View getView(int position, View convertView, ViewGroup parent) {
220 HeaderViewHolder holder;
221 View view;
222
223 if (convertView == null) {
224 view = mInflater.inflate(com.android.internal.R.layout.preference_list_item,
225 parent, false);
226 holder = new HeaderViewHolder();
Andrew Stadler468c3232010-08-17 16:16:42 -0700227 holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
228 holder.title = (TextView) view.findViewById(com.android.internal.R.id.title);
229 holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700230 view.setTag(holder);
231 } else {
232 view = convertView;
Andrew Stadler468c3232010-08-17 16:16:42 -0700233 holder = (HeaderViewHolder) view.getTag();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700234 }
235
Andrew Stadler468c3232010-08-17 16:16:42 -0700236 // All view fields must be updated every time, because the view may be recycled
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700237 Header header = getItem(position);
Andrew Stadler468c3232010-08-17 16:16:42 -0700238 if (header.icon == null) {
239 holder.icon.setImageDrawable(null);
240 holder.icon.setImageResource(header.iconRes);
241 } else {
242 holder.icon.setImageResource(0);
243 holder.icon.setImageDrawable(header.icon);
244 }
245 holder.title.setText(header.title);
246 if (TextUtils.isEmpty(header.summary)) {
247 holder.summary.setVisibility(View.GONE);
248 } else {
249 holder.summary.setVisibility(View.VISIBLE);
250 holder.summary.setText(header.summary);
251 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700252
253 return view;
254 }
255 }
256
257 /**
258 * Description of a single Header item that the user can select.
259 */
260 public static class Header {
261 /**
262 * Title of the header that is shown to the user.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700263 * @attr ref android.R.styleable#PreferenceHeader_title
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700264 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700265 public CharSequence title;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700266
267 /**
268 * Optional summary describing what this header controls.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700269 * @attr ref android.R.styleable#PreferenceHeader_summary
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700270 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700271 public CharSequence summary;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700272
273 /**
274 * Optional icon resource to show for this header.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700275 * @attr ref android.R.styleable#PreferenceHeader_icon
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700276 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700277 public int iconRes;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700278
279 /**
280 * Optional icon drawable to show for this header. (If this is non-null,
281 * the iconRes will be ignored.)
282 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700283 public Drawable icon;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700284
285 /**
286 * Full class name of the fragment to display when this header is
287 * selected.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700288 * @attr ref android.R.styleable#PreferenceHeader_fragment
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700289 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700290 public String fragment;
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700291
292 /**
293 * Optional arguments to supply to the fragment when it is
294 * instantiated.
295 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700296 public Bundle fragmentArguments;
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700297
298 /**
299 * Intent to launch when the preference is selected.
300 */
301 public Intent intent;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700302 }
303
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304 @Override
305 protected void onCreate(Bundle savedInstanceState) {
306 super.onCreate(savedInstanceState);
307
308 setContentView(com.android.internal.R.layout.preference_list_content);
Freeman Ng19ea2e02010-03-25 15:09:00 -0700309
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700310 mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700311 mPrefsContainer = findViewById(com.android.internal.R.id.prefs);
312 boolean hidingHeaders = onIsHidingHeaders();
313 mSinglePane = hidingHeaders || !onIsMultiPane();
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700314 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
315 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700316
317 if (initialFragment != null && mSinglePane) {
318 // If we are just showing a fragment, we want to run in
319 // new fragment mode, but don't need to compute and show
320 // the headers.
321 getListView().setVisibility(View.GONE);
322 mPrefsContainer.setVisibility(View.VISIBLE);
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700323 switchToHeader(initialFragment, initialArguments);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700324
325 } else {
326 // We need to try to build the headers.
327 onBuildHeaders(mHeaders);
328
329 // If there are headers, then at this point we need to show
330 // them and, depending on the screen, we may also show in-line
331 // the currently selected preference fragment.
332 if (mHeaders.size() > 0) {
333 mAdapter = new HeaderAdapter(this, mHeaders);
334 setListAdapter(mAdapter);
335 if (!mSinglePane) {
336 mPrefsContainer.setVisibility(View.VISIBLE);
Dianne Hackborndef15372010-08-15 12:43:52 -0700337 if (initialFragment == null) {
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700338 Header h = onGetInitialHeader();
339 initialFragment = h.fragment;
340 initialArguments = h.fragmentArguments;
341 }
342 switchToHeader(initialFragment, initialArguments);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700343 }
344
345 // If there are no headers, we are in the old "just show a screen
346 // of preferences" mode.
347 } else {
348 mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
349 mPreferenceManager.setOnPreferenceTreeClickListener(this);
350 }
351 }
352
353 getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
354
Freeman Ng19ea2e02010-03-25 15:09:00 -0700355 // see if we should show Back/Next buttons
356 Intent intent = getIntent();
357 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
358
359 findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
360
361 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
362 backButton.setOnClickListener(new OnClickListener() {
363 public void onClick(View v) {
364 setResult(RESULT_CANCELED);
365 finish();
366 }
367 });
Freeman Ng09dbf182010-08-11 15:45:01 -0700368 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
369 skipButton.setOnClickListener(new OnClickListener() {
370 public void onClick(View v) {
371 setResult(RESULT_OK);
372 finish();
373 }
374 });
Freeman Ng19ea2e02010-03-25 15:09:00 -0700375 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
376 mNextButton.setOnClickListener(new OnClickListener() {
377 public void onClick(View v) {
378 setResult(RESULT_OK);
379 finish();
380 }
381 });
382
383 // set our various button parameters
384 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
385 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
386 if (TextUtils.isEmpty(buttonText)) {
387 mNextButton.setVisibility(View.GONE);
388 }
389 else {
390 mNextButton.setText(buttonText);
391 }
392 }
393 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
394 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
395 if (TextUtils.isEmpty(buttonText)) {
396 backButton.setVisibility(View.GONE);
397 }
398 else {
399 backButton.setText(buttonText);
400 }
401 }
Freeman Ng09dbf182010-08-11 15:45:01 -0700402 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
403 skipButton.setVisibility(View.VISIBLE);
404 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700405 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700406 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700407
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700408 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700409 * Returns true if this activity is currently showing the header list.
410 */
411 public boolean hasHeaders() {
412 return getListView().getVisibility() == View.VISIBLE
413 && mPreferenceManager == null;
414 }
415
416 /**
417 * Returns true if this activity is showing multiple panes -- the headers
418 * and a preference fragment.
419 */
420 public boolean isMultiPane() {
421 return hasHeaders() && mPrefsContainer.getVisibility() == View.VISIBLE;
422 }
423
424 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700425 * Called to determine if the activity should run in multi-pane mode.
426 * The default implementation returns true if the screen is large
427 * enough.
428 */
429 public boolean onIsMultiPane() {
430 Configuration config = getResources().getConfiguration();
431 if ((config.screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK)
432 == Configuration.SCREENLAYOUT_SIZE_XLARGE
433 && config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
434 return true;
435 }
436 return false;
437 }
438
439 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700440 * Called to determine whether the header list should be hidden.
441 * The default implementation returns the
442 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
443 * This is set to false, for example, when the activity is being re-launched
444 * to show a particular preference activity.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700445 */
446 public boolean onIsHidingHeaders() {
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700447 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700448 }
449
450 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700451 * Called to determine the initial header to be shown. The default
452 * implementation simply returns the fragment of the first header. Note
453 * that the returned Header object does not actually need to exist in
454 * your header list -- whatever its fragment is will simply be used to
455 * show for the initial UI.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700456 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700457 public Header onGetInitialHeader() {
458 return mHeaders.get(0);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700459 }
460
461 /**
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700462 * Called after the header list has been updated ({@link #onBuildHeaders}
463 * has been called and returned due to {@link #invalidateHeaders()}) to
464 * specify the header that should now be selected. The default implementation
465 * returns null to keep whatever header is currently selected.
466 */
467 public Header onGetNewHeader() {
468 return null;
469 }
470
471 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700472 * Called when the activity needs its list of headers build. By
473 * implementing this and adding at least one item to the list, you
474 * will cause the activity to run in its modern fragment mode. Note
475 * that this function may not always be called; for example, if the
476 * activity has been asked to display a particular fragment without
477 * the header list, there is no need to build the headers.
478 *
479 * <p>Typical implementations will use {@link #loadHeadersFromResource}
480 * to fill in the list from a resource.
481 *
482 * @param target The list in which to place the headers.
483 */
484 public void onBuildHeaders(List<Header> target) {
485 }
486
487 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700488 * Call when you need to change the headers being displayed. Will result
489 * in onBuildHeaders() later being called to retrieve the new list.
490 */
491 public void invalidateHeaders() {
492 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
493 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
494 }
495 }
496
497 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700498 * Parse the given XML file as a header description, adding each
499 * parsed Header into the target list.
500 *
501 * @param resid The XML resource to load and parse.
502 * @param target The list in which the parsed headers should be placed.
503 */
504 public void loadHeadersFromResource(int resid, List<Header> target) {
505 XmlResourceParser parser = null;
506 try {
507 parser = getResources().getXml(resid);
508 AttributeSet attrs = Xml.asAttributeSet(parser);
509
510 int type;
511 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
512 && type != XmlPullParser.START_TAG) {
513 }
514
515 String nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700516 if (!"preference-headers".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700517 throw new RuntimeException(
Dianne Hackborndef15372010-08-15 12:43:52 -0700518 "XML document must start with <preference-headers> tag; found"
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700519 + nodeName + " at " + parser.getPositionDescription());
520 }
521
Dianne Hackborndef15372010-08-15 12:43:52 -0700522 Bundle curBundle = null;
523
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700524 final int outerDepth = parser.getDepth();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700525 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
526 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
527 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
528 continue;
529 }
530
531 nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700532 if ("header".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700533 Header header = new Header();
534
535 TypedArray sa = getResources().obtainAttributes(attrs,
536 com.android.internal.R.styleable.PreferenceHeader);
537 header.title = sa.getText(
538 com.android.internal.R.styleable.PreferenceHeader_title);
539 header.summary = sa.getText(
540 com.android.internal.R.styleable.PreferenceHeader_summary);
541 header.iconRes = sa.getResourceId(
542 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
543 header.fragment = sa.getString(
544 com.android.internal.R.styleable.PreferenceHeader_fragment);
545 sa.recycle();
546
Dianne Hackborndef15372010-08-15 12:43:52 -0700547 if (curBundle == null) {
548 curBundle = new Bundle();
549 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700550
551 final int innerDepth = parser.getDepth();
552 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
553 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
554 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
555 continue;
556 }
557
558 String innerNodeName = parser.getName();
559 if (innerNodeName.equals("extra")) {
560 getResources().parseBundleExtra("extra", attrs, curBundle);
561 XmlUtils.skipCurrentTag(parser);
562
563 } else if (innerNodeName.equals("intent")) {
564 header.intent = Intent.parseIntent(getResources(), parser, attrs);
565
566 } else {
567 XmlUtils.skipCurrentTag(parser);
568 }
569 }
570
Dianne Hackborndef15372010-08-15 12:43:52 -0700571 if (curBundle.size() > 0) {
572 header.fragmentArguments = curBundle;
573 curBundle = null;
574 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700575
Dianne Hackborndef15372010-08-15 12:43:52 -0700576 target.add(header);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700577 } else {
578 XmlUtils.skipCurrentTag(parser);
579 }
580 }
581
582 } catch (XmlPullParserException e) {
583 throw new RuntimeException("Error parsing headers", e);
584 } catch (IOException e) {
585 throw new RuntimeException("Error parsing headers", e);
586 } finally {
587 if (parser != null) parser.close();
588 }
589
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590 }
591
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700592 /**
593 * Set a footer that should be shown at the bottom of the header list.
594 */
595 public void setListFooter(View view) {
596 mListFooter.removeAllViews();
597 mListFooter.addView(view, new FrameLayout.LayoutParams(
598 FrameLayout.LayoutParams.MATCH_PARENT,
599 FrameLayout.LayoutParams.WRAP_CONTENT));
600 }
601
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 @Override
603 protected void onStop() {
604 super.onStop();
Freeman Ng19ea2e02010-03-25 15:09:00 -0700605
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700606 if (mPreferenceManager != null) {
607 mPreferenceManager.dispatchActivityStop();
608 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800609 }
610
611 @Override
612 protected void onDestroy() {
613 super.onDestroy();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700614
615 if (mPreferenceManager != null) {
616 mPreferenceManager.dispatchActivityDestroy();
617 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 }
619
620 @Override
621 protected void onSaveInstanceState(Bundle outState) {
622 super.onSaveInstanceState(outState);
623
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700624 if (mPreferenceManager != null) {
625 final PreferenceScreen preferenceScreen = getPreferenceScreen();
626 if (preferenceScreen != null) {
627 Bundle container = new Bundle();
628 preferenceScreen.saveHierarchyState(container);
629 outState.putBundle(PREFERENCES_TAG, container);
630 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631 }
632 }
633
634 @Override
635 protected void onRestoreInstanceState(Bundle state) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700636 if (mPreferenceManager != null) {
637 Bundle container = state.getBundle(PREFERENCES_TAG);
638 if (container != null) {
639 final PreferenceScreen preferenceScreen = getPreferenceScreen();
640 if (preferenceScreen != null) {
641 preferenceScreen.restoreHierarchyState(container);
642 mSavedInstanceState = state;
643 return;
644 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800645 }
646 }
Adam Powelle7fea452010-03-18 14:51:39 -0700647
648 // Only call this if we didn't save the instance state for later.
649 // If we did save it, it will be restored when we bind the adapter.
650 super.onRestoreInstanceState(state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651 }
652
653 @Override
654 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
655 super.onActivityResult(requestCode, resultCode, data);
Freeman Ng19ea2e02010-03-25 15:09:00 -0700656
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700657 if (mPreferenceManager != null) {
658 mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
659 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800660 }
661
662 @Override
663 public void onContentChanged() {
664 super.onContentChanged();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700665
666 if (mPreferenceManager != null) {
667 postBindPreferences();
668 }
669 }
670
671 @Override
672 protected void onListItemClick(ListView l, View v, int position, long id) {
673 super.onListItemClick(l, v, position, id);
674
675 if (mAdapter != null) {
676 onHeaderClick(mHeaders.get(position), position);
677 }
678 }
679
680 /**
681 * Called when the user selects an item in the header list. The default
Jean-Baptiste Queru72dc7802010-08-13 09:25:19 -0700682 * implementation will call either {@link #startWithFragment(String, Bundle)}
683 * or {@link #switchToHeader(String, Bundle)} as appropriate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700684 *
685 * @param header The header that was selected.
686 * @param position The header's position in the list.
687 */
688 public void onHeaderClick(Header header, int position) {
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700689 if (header.fragment != null) {
690 if (mSinglePane) {
691 startWithFragment(header.fragment, header.fragmentArguments);
692 } else {
693 switchToHeader(header.fragment, header.fragmentArguments);
694 }
695 } else if (header.intent != null) {
696 startActivity(header.intent);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700697 }
698 }
699
700 /**
701 * Start a new instance of this activity, showing only the given
702 * preference fragment. When launched in this mode, the header list
703 * will be hidden and the given preference fragment will be instantiated
704 * and fill the entire activity.
705 *
706 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700707 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700708 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700709 public void startWithFragment(String fragmentName, Bundle args) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700710 Intent intent = new Intent(Intent.ACTION_MAIN);
711 intent.setClass(this, getClass());
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700712 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
713 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
714 intent.putExtra(EXTRA_NO_HEADERS, true);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700715 startActivity(intent);
716 }
717
718 /**
719 * When in two-pane mode, switch the fragment pane to show the given
720 * preference fragment.
721 *
722 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700723 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700724 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700725 public void switchToHeader(String fragmentName, Bundle args) {
Dianne Hackborndef15372010-08-15 12:43:52 -0700726 getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700727
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700728 Fragment f = Fragment.instantiate(this, fragmentName, args);
Dianne Hackborndef15372010-08-15 12:43:52 -0700729 getFragmentManager().openTransaction().replace(
730 com.android.internal.R.id.prefs, f).commit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731 }
732
Andrew Stadleraa904f42010-09-02 14:50:08 -0700733 /**
734 * Start a new fragment.
735 *
736 * @param fragment The fragment to start
737 * @param push If true, the current fragment will be pushed onto the back stack. If false,
738 * the current fragment will be replaced.
739 */
740 public void startPreferenceFragment(Fragment fragment, boolean push) {
741 FragmentTransaction transaction = getFragmentManager().openTransaction();
742 transaction.replace(com.android.internal.R.id.prefs, fragment);
743 if (push) {
744 transaction.addToBackStack(BACK_STACK_PREFS);
745 }
746 transaction.commit();
747 }
748
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700749 @Override
750 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
Dianne Hackborndef15372010-08-15 12:43:52 -0700751 Fragment f = Fragment.instantiate(this, pref.getFragment(), pref.getExtras());
Andrew Stadleraa904f42010-09-02 14:50:08 -0700752 startPreferenceFragment(f, true);
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700753 return true;
754 }
755
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800756 /**
757 * Posts a message to bind the preferences to the list view.
758 * <p>
759 * Binding late is preferred as any custom preference types created in
760 * {@link #onCreate(Bundle)} are able to have their views recycled.
761 */
762 private void postBindPreferences() {
763 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
764 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
765 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700766
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800767 private void bindPreferences() {
768 final PreferenceScreen preferenceScreen = getPreferenceScreen();
769 if (preferenceScreen != null) {
770 preferenceScreen.bind(getListView());
Adam Powelle7fea452010-03-18 14:51:39 -0700771 if (mSavedInstanceState != null) {
772 super.onRestoreInstanceState(mSavedInstanceState);
773 mSavedInstanceState = null;
774 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800775 }
776 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700777
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800779 * Returns the {@link PreferenceManager} used by this activity.
780 * @return The {@link PreferenceManager}.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700781 *
782 * @deprecated This function is not relevant for a modern fragment-based
783 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800784 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700785 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800786 public PreferenceManager getPreferenceManager() {
787 return mPreferenceManager;
788 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700789
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800790 private void requirePreferenceManager() {
791 if (mPreferenceManager == null) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700792 if (mAdapter == null) {
793 throw new RuntimeException("This should be called after super.onCreate.");
794 }
795 throw new RuntimeException(
796 "Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797 }
798 }
799
800 /**
801 * Sets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -0700802 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800803 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700804 *
805 * @deprecated This function is not relevant for a modern fragment-based
806 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700808 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809 public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700810 requirePreferenceManager();
811
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800812 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
813 postBindPreferences();
814 CharSequence title = getPreferenceScreen().getTitle();
815 // Set the title of the activity
816 if (title != null) {
817 setTitle(title);
818 }
819 }
820 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700821
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800822 /**
823 * Gets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -0700824 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 * @return The {@link PreferenceScreen} that is the root of the preference
826 * hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700827 *
828 * @deprecated This function is not relevant for a modern fragment-based
829 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800830 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700831 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800832 public PreferenceScreen getPreferenceScreen() {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700833 if (mPreferenceManager != null) {
834 return mPreferenceManager.getPreferenceScreen();
835 }
836 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800837 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700838
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800839 /**
840 * Adds preferences from activities that match the given {@link Intent}.
Freeman Ng19ea2e02010-03-25 15:09:00 -0700841 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800842 * @param intent The {@link Intent} to query activities.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700843 *
844 * @deprecated This function is not relevant for a modern fragment-based
845 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800846 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700847 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800848 public void addPreferencesFromIntent(Intent intent) {
849 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -0700850
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
852 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700853
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854 /**
855 * Inflates the given XML resource and adds the preference hierarchy to the current
856 * preference hierarchy.
Freeman Ng19ea2e02010-03-25 15:09:00 -0700857 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800858 * @param preferencesResId The XML resource ID to inflate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700859 *
860 * @deprecated This function is not relevant for a modern fragment-based
861 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700863 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 public void addPreferencesFromResource(int preferencesResId) {
865 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -0700866
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
868 getPreferenceScreen()));
869 }
870
871 /**
872 * {@inheritDoc}
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700873 *
874 * @deprecated This function is not relevant for a modern fragment-based
875 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800876 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700877 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800878 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
879 return false;
880 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700881
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800882 /**
883 * Finds a {@link Preference} based on its key.
Freeman Ng19ea2e02010-03-25 15:09:00 -0700884 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800885 * @param key The key of the preference to retrieve.
886 * @return The {@link Preference} with the key, or null.
887 * @see PreferenceGroup#findPreference(CharSequence)
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700888 *
889 * @deprecated This function is not relevant for a modern fragment-based
890 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800891 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700892 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800893 public Preference findPreference(CharSequence key) {
Freeman Ng19ea2e02010-03-25 15:09:00 -0700894
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800895 if (mPreferenceManager == null) {
896 return null;
897 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700898
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 return mPreferenceManager.findPreference(key);
900 }
901
902 @Override
903 protected void onNewIntent(Intent intent) {
904 if (mPreferenceManager != null) {
905 mPreferenceManager.dispatchNewIntent(intent);
906 }
907 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700908
909 // give subclasses access to the Next button
910 /** @hide */
911 protected boolean hasNextButton() {
912 return mNextButton != null;
913 }
914 /** @hide */
915 protected Button getNextButton() {
916 return mNextButton;
917 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800918}