blob: 8ed2605e9e8cb6f7d7b6de9b18b6fa60e4544a88 [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
Filip Pavlis61fc0d42017-01-06 18:15:51 +000019import android.animation.LayoutTransition;
Scott Kennedy3c32b622015-02-22 16:58:58 -080020import android.annotation.Nullable;
Tor Norbye7b9c9122013-05-30 16:48:33 -070021import android.annotation.StringRes;
Mathew Inwoodeac8d0a2018-08-17 13:51:26 +010022import android.annotation.UnsupportedAppUsage;
Tor Norbye7b9c9122013-05-30 16:48:33 -070023import android.annotation.XmlRes;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070024import android.app.Fragment;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070025import android.app.FragmentBreadCrumbs;
Dianne Hackborn3a57fb92010-11-15 17:58:52 -080026import android.app.FragmentManager;
Andrew Stadleraa904f42010-09-02 14:50:08 -070027import android.app.FragmentTransaction;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.app.ListActivity;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070029import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.content.Intent;
Dianne Hackborn50ed8292010-12-03 12:30:21 -080031import android.content.res.Resources;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070032import android.content.res.TypedArray;
33import android.content.res.XmlResourceParser;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.os.Bundle;
35import android.os.Handler;
36import android.os.Message;
Dianne Hackborna21e3da2010-09-12 19:27:46 -070037import android.os.Parcel;
38import android.os.Parcelable;
Freeman Ng19ea2e02010-03-25 15:09:00 -070039import android.text.TextUtils;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070040import android.util.AttributeSet;
Dianne Hackborn50ed8292010-12-03 12:30:21 -080041import android.util.TypedValue;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070042import android.util.Xml;
43import android.view.LayoutInflater;
Filip Pavlis6af15eb2017-03-01 13:48:33 +000044import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import android.view.View;
Amith Yamasani405c1af2011-05-26 13:08:25 -070046import android.view.View.OnClickListener;
Gilles Debunne39725ac2011-06-14 18:52:41 -070047import android.view.ViewGroup;
Dianne Hackborna21e3da2010-09-12 19:27:46 -070048import android.widget.AbsListView;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070049import android.widget.ArrayAdapter;
Gilles Debunne39725ac2011-06-14 18:52:41 -070050import android.widget.BaseAdapter;
Freeman Ng19ea2e02010-03-25 15:09:00 -070051import android.widget.Button;
Dianne Hackborn5c769a42010-08-26 17:08:08 -070052import android.widget.FrameLayout;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070053import android.widget.ImageView;
54import android.widget.ListView;
55import android.widget.TextView;
56
Gilles Debunne39725ac2011-06-14 18:52:41 -070057import com.android.internal.util.XmlUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058
Amith Yamasani405c1af2011-05-26 13:08:25 -070059import org.xmlpull.v1.XmlPullParser;
60import org.xmlpull.v1.XmlPullParserException;
61
Gilles Debunne39725ac2011-06-14 18:52:41 -070062import java.io.IOException;
63import java.util.ArrayList;
64import java.util.List;
65
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066/**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070067 * This is the base class for an activity to show a hierarchy of preferences
68 * to the user. Prior to {@link android.os.Build.VERSION_CODES#HONEYCOMB}
69 * this class only allowed the display of a single set of preference; this
70 * functionality should now be found in the new {@link PreferenceFragment}
71 * class. If you are using PreferenceActivity in its old mode, the documentation
72 * there applies to the deprecated APIs here.
Freeman Ng19ea2e02010-03-25 15:09:00 -070073 *
Gilles Debunne39725ac2011-06-14 18:52:41 -070074 * <p>This activity shows one or more headers of preferences, each of which
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070075 * is associated with a {@link PreferenceFragment} to display the preferences
76 * of that header. The actual layout and display of these associations can
77 * however vary; currently there are two major approaches it may take:
78 *
79 * <ul>
Filip Pavlis6af15eb2017-03-01 13:48:33 +000080 * <li>On a small screen it may display only the headers as a single list when first launched.
81 * Selecting one of the header items will only show the PreferenceFragment of that header (on
82 * Android N and lower a new Activity is launched).
Joshua Baxtera9aa1be2018-06-04 13:25:13 -070083 * <li>On a large screen it may display both the headers and current PreferenceFragment together as
Filip Pavlis6af15eb2017-03-01 13:48:33 +000084 * panes. Selecting a header item switches to showing the correct PreferenceFragment for that item.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070085 * </ul>
86 *
87 * <p>Subclasses of PreferenceActivity should implement
88 * {@link #onBuildHeaders} to populate the header list with the desired
89 * items. Doing this implicitly switches the class into its new "headers
90 * + fragments" mode rather than the old style of just showing a single
91 * preferences list.
Filip Pavlis61fc0d42017-01-06 18:15:51 +000092 *
Scott Maincdd0c592012-07-26 17:03:51 -070093 * <div class="special reference">
94 * <h3>Developer Guides</h3>
95 * <p>For information about using {@code PreferenceActivity},
96 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
97 * guide.</p>
98 * </div>
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070099 *
100 * <a name="SampleCode"></a>
101 * <h3>Sample Code</h3>
102 *
103 * <p>The following sample code shows a simple preference activity that
104 * has two different sets of preferences. The implementation, consisting
105 * of the activity itself as well as its two preference fragments is:</p>
106 *
107 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/PreferenceWithHeaders.java
108 * activity}
109 *
110 * <p>The preference_headers resource describes the headers to be displayed
111 * and the fragments associated with them. It is:
112 *
113 * {@sample development/samples/ApiDemos/res/xml/preference_headers.xml headers}
114 *
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700115 * <p>The first header is shown by Prefs1Fragment, which populates itself
116 * from the following XML resource:</p>
117 *
118 * {@sample development/samples/ApiDemos/res/xml/fragmented_preferences.xml preferences}
119 *
120 * <p>Note that this XML resource contains a preference screen holding another
121 * fragment, the Prefs1FragmentInner implemented here. This allows the user
122 * to traverse down a hierarchy of preferences; pressing back will pop each
123 * fragment off the stack to return to the previous preferences.
124 *
125 * <p>See {@link PreferenceFragment} for information on implementing the
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700126 * fragments themselves.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 */
128public abstract class PreferenceActivity extends ListActivity implements
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700129 PreferenceManager.OnPreferenceTreeClickListener,
130 PreferenceFragment.OnPreferenceStartFragmentCallback {
Freeman Ng19ea2e02010-03-25 15:09:00 -0700131
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +0000132 private static final String TAG = "PreferenceActivity";
133
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700134 // Constants for state save/restore
135 private static final String HEADERS_TAG = ":android:headers";
136 private static final String CUR_HEADER_TAG = ":android:cur_header";
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700137 private static final String PREFERENCES_TAG = ":android:preferences";
Freeman Ng19ea2e02010-03-25 15:09:00 -0700138
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700139 /**
140 * When starting this activity, the invoking Intent can contain this extra
141 * string to specify which fragment should be initially displayed.
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700142 * <p/>Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity
143 * will call isValidFragment() to confirm that the fragment class name is valid for this
144 * activity.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700145 */
146 public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700147
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700148 /**
149 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
Dianne Hackborne72f2372011-03-16 10:43:18 -0700150 * this extra can also be specified to supply a Bundle of arguments to pass
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700151 * to that fragment when it is instantiated during the initial creation
152 * of PreferenceActivity.
153 */
154 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
155
156 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -0700157 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
158 * this extra can also be specify to supply the title to be shown for
159 * that fragment.
160 */
161 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title";
162
163 /**
164 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
165 * this extra can also be specify to supply the short title to be shown for
166 * that fragment.
167 */
168 public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE
169 = ":android:show_fragment_short_title";
170
171 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700172 * When starting this activity, the invoking Intent can contain this extra
173 * boolean that the header list should not be displayed. This is most often
174 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
175 * the activity to display a specific fragment that the user has navigated
176 * to.
177 */
178 public static final String EXTRA_NO_HEADERS = ":android:no_headers";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700179
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700180 private static final String BACK_STACK_PREFS = ":android:prefs";
181
Freeman Ng19ea2e02010-03-25 15:09:00 -0700182 // extras that allow any preference activity to be launched as part of a wizard
183
184 // show Back and Next buttons? takes boolean parameter
185 // Back will then return RESULT_CANCELED and Next RESULT_OK
186 private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
187
Freeman Ng09dbf182010-08-11 15:45:01 -0700188 // add a Skip button?
189 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
190
Freeman Ng19ea2e02010-03-25 15:09:00 -0700191 // specify custom text for the Back or Next buttons, or cause a button to not appear
192 // at all by setting it to null
193 private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
194 private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
195
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700196 // --- State for new mode when showing a list of headers + prefs fragment
197
198 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
199
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700200 private FrameLayout mListFooter;
201
Mathew Inwoodeac8d0a2018-08-17 13:51:26 +0100202 @UnsupportedAppUsage
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800203 private ViewGroup mPrefsContainer;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700204
Filip Pavlisb07c6f62017-02-17 19:23:04 +0000205 // Backup of the original activity title. This is used when navigating back to the headers list
206 // in onBackPress to restore the title.
207 private CharSequence mActivityTitle;
208
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000209 // Null if in legacy mode.
210 private ViewGroup mHeadersContainer;
211
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700212 private FragmentBreadCrumbs mFragmentBreadCrumbs;
213
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700214 private boolean mSinglePane;
215
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700216 private Header mCurHeader;
217
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700218 // --- State for old mode when showing a single preference list
Freeman Ng19ea2e02010-03-25 15:09:00 -0700219
Mathew Inwoodeac8d0a2018-08-17 13:51:26 +0100220 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 private PreferenceManager mPreferenceManager;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700222
Adam Powelle7fea452010-03-18 14:51:39 -0700223 private Bundle mSavedInstanceState;
224
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700225 // --- Common state
226
227 private Button mNextButton;
228
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700229 private int mPreferenceHeaderItemResId = 0;
230 private boolean mPreferenceHeaderRemoveEmptyIcon = false;
231
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 /**
233 * The starting request code given out to preference framework.
234 */
235 private static final int FIRST_REQUEST_CODE = 100;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700236
Dianne Hackborn3e449ce2010-09-11 20:52:31 -0700237 private static final int MSG_BIND_PREFERENCES = 1;
238 private static final int MSG_BUILD_HEADERS = 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 private Handler mHandler = new Handler() {
240 @Override
241 public void handleMessage(Message msg) {
242 switch (msg.what) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700243 case MSG_BIND_PREFERENCES: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244 bindPreferences();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700245 } break;
246 case MSG_BUILD_HEADERS: {
247 ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders);
248 mHeaders.clear();
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700249 onBuildHeaders(mHeaders);
Gilles Debunne39725ac2011-06-14 18:52:41 -0700250 if (mAdapter instanceof BaseAdapter) {
251 ((BaseAdapter) mAdapter).notifyDataSetChanged();
Andrew Stadler83681eb2010-11-04 15:49:05 -0700252 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700253 Header header = onGetNewHeader();
254 if (header != null && header.fragment != null) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700255 Header mappedHeader = findBestMatchingHeader(header, oldHeaders);
256 if (mappedHeader == null || mCurHeader != mappedHeader) {
257 switchToHeader(header);
258 }
259 } else if (mCurHeader != null) {
260 Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders);
261 if (mappedHeader != null) {
262 setSelectedHeader(mappedHeader);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700263 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700264 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700265 } break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266 }
267 }
268 };
269
Andrew Stadler468c3232010-08-17 16:16:42 -0700270 private static class HeaderAdapter extends ArrayAdapter<Header> {
271 private static class HeaderViewHolder {
272 ImageView icon;
273 TextView title;
274 TextView summary;
275 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700276
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700277 private LayoutInflater mInflater;
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700278 private int mLayoutResId;
279 private boolean mRemoveIconIfEmpty;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700280
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700281 public HeaderAdapter(Context context, List<Header> objects, int layoutResId,
282 boolean removeIconBehavior) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700283 super(context, 0, objects);
284 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700285 mLayoutResId = layoutResId;
286 mRemoveIconIfEmpty = removeIconBehavior;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700287 }
288
289 @Override
290 public View getView(int position, View convertView, ViewGroup parent) {
291 HeaderViewHolder holder;
292 View view;
293
294 if (convertView == null) {
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700295 view = mInflater.inflate(mLayoutResId, parent, false);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700296 holder = new HeaderViewHolder();
Andrew Stadler468c3232010-08-17 16:16:42 -0700297 holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
298 holder.title = (TextView) view.findViewById(com.android.internal.R.id.title);
299 holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700300 view.setTag(holder);
301 } else {
302 view = convertView;
Andrew Stadler468c3232010-08-17 16:16:42 -0700303 holder = (HeaderViewHolder) view.getTag();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700304 }
305
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000306 // All view fields must be updated every time, because the view may be recycled
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700307 Header header = getItem(position);
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700308 if (mRemoveIconIfEmpty) {
309 if (header.iconRes == 0) {
310 holder.icon.setVisibility(View.GONE);
311 } else {
312 holder.icon.setVisibility(View.VISIBLE);
313 holder.icon.setImageResource(header.iconRes);
314 }
315 } else {
316 holder.icon.setImageResource(header.iconRes);
317 }
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800318 holder.title.setText(header.getTitle(getContext().getResources()));
319 CharSequence summary = header.getSummary(getContext().getResources());
320 if (!TextUtils.isEmpty(summary)) {
Andrew Stadler468c3232010-08-17 16:16:42 -0700321 holder.summary.setVisibility(View.VISIBLE);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800322 holder.summary.setText(summary);
323 } else {
324 holder.summary.setVisibility(View.GONE);
Andrew Stadler468c3232010-08-17 16:16:42 -0700325 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700326
327 return view;
328 }
329 }
330
331 /**
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700332 * Default value for {@link Header#id Header.id} indicating that no
333 * identifier value is set. All other values (including those below -1)
334 * are valid.
335 */
336 public static final long HEADER_ID_UNDEFINED = -1;
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700337
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700338 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700339 * Description of a single Header item that the user can select.
340 */
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700341 public static final class Header implements Parcelable {
342 /**
343 * Identifier for this header, to correlate with a new list when
344 * it is updated. The default value is
345 * {@link PreferenceActivity#HEADER_ID_UNDEFINED}, meaning no id.
346 * @attr ref android.R.styleable#PreferenceHeader_id
347 */
348 public long id = HEADER_ID_UNDEFINED;
349
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700350 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800351 * Resource ID of title of the header that is shown to the user.
352 * @attr ref android.R.styleable#PreferenceHeader_title
353 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700354 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800355 public int titleRes;
356
357 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700358 * Title of the header that is shown to the user.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700359 * @attr ref android.R.styleable#PreferenceHeader_title
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700360 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700361 public CharSequence title;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700362
363 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800364 * Resource ID of optional summary describing what this header controls.
365 * @attr ref android.R.styleable#PreferenceHeader_summary
366 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700367 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800368 public int summaryRes;
369
370 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700371 * Optional summary describing what this header controls.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700372 * @attr ref android.R.styleable#PreferenceHeader_summary
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700373 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700374 public CharSequence summary;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700375
376 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800377 * Resource ID of optional text to show as the title in the bread crumb.
378 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
379 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700380 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800381 public int breadCrumbTitleRes;
382
383 /**
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700384 * Optional text to show as the title in the bread crumb.
385 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
386 */
387 public CharSequence breadCrumbTitle;
388
389 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800390 * Resource ID of optional text to show as the short title in the bread crumb.
391 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
392 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700393 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800394 public int breadCrumbShortTitleRes;
395
396 /**
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700397 * Optional text to show as the short title in the bread crumb.
398 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
399 */
400 public CharSequence breadCrumbShortTitle;
401
402 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700403 * Optional icon resource to show for this header.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700404 * @attr ref android.R.styleable#PreferenceHeader_icon
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700405 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700406 public int iconRes;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700407
408 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700409 * Full class name of the fragment to display when this header is
410 * selected.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700411 * @attr ref android.R.styleable#PreferenceHeader_fragment
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700412 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700413 public String fragment;
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700414
415 /**
416 * Optional arguments to supply to the fragment when it is
417 * instantiated.
418 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700419 public Bundle fragmentArguments;
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700420
421 /**
422 * Intent to launch when the preference is selected.
423 */
424 public Intent intent;
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700425
426 /**
427 * Optional additional data for use by subclasses of PreferenceActivity.
428 */
429 public Bundle extras;
430
431 public Header() {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700432 // Empty
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700433 }
434
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800435 /**
436 * Return the currently set title. If {@link #titleRes} is set,
437 * this resource is loaded from <var>res</var> and returned. Otherwise
438 * {@link #title} is returned.
439 */
440 public CharSequence getTitle(Resources res) {
441 if (titleRes != 0) {
442 return res.getText(titleRes);
443 }
444 return title;
445 }
446
447 /**
448 * Return the currently set summary. If {@link #summaryRes} is set,
449 * this resource is loaded from <var>res</var> and returned. Otherwise
450 * {@link #summary} is returned.
451 */
452 public CharSequence getSummary(Resources res) {
453 if (summaryRes != 0) {
454 return res.getText(summaryRes);
455 }
Dianne Hackborn9d071802010-12-08 14:49:15 -0800456 return summary;
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800457 }
458
459 /**
460 * Return the currently set bread crumb title. If {@link #breadCrumbTitleRes} is set,
461 * this resource is loaded from <var>res</var> and returned. Otherwise
462 * {@link #breadCrumbTitle} is returned.
463 */
464 public CharSequence getBreadCrumbTitle(Resources res) {
465 if (breadCrumbTitleRes != 0) {
466 return res.getText(breadCrumbTitleRes);
467 }
468 return breadCrumbTitle;
469 }
470
471 /**
472 * Return the currently set bread crumb short title. If
473 * {@link #breadCrumbShortTitleRes} is set,
474 * this resource is loaded from <var>res</var> and returned. Otherwise
475 * {@link #breadCrumbShortTitle} is returned.
476 */
477 public CharSequence getBreadCrumbShortTitle(Resources res) {
478 if (breadCrumbShortTitleRes != 0) {
479 return res.getText(breadCrumbShortTitleRes);
480 }
481 return breadCrumbShortTitle;
482 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000483
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700484 @Override
485 public int describeContents() {
486 return 0;
487 }
488
489 @Override
490 public void writeToParcel(Parcel dest, int flags) {
491 dest.writeLong(id);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800492 dest.writeInt(titleRes);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700493 TextUtils.writeToParcel(title, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800494 dest.writeInt(summaryRes);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700495 TextUtils.writeToParcel(summary, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800496 dest.writeInt(breadCrumbTitleRes);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700497 TextUtils.writeToParcel(breadCrumbTitle, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800498 dest.writeInt(breadCrumbShortTitleRes);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700499 TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700500 dest.writeInt(iconRes);
501 dest.writeString(fragment);
502 dest.writeBundle(fragmentArguments);
503 if (intent != null) {
504 dest.writeInt(1);
505 intent.writeToParcel(dest, flags);
506 } else {
507 dest.writeInt(0);
508 }
509 dest.writeBundle(extras);
510 }
511
512 public void readFromParcel(Parcel in) {
513 id = in.readLong();
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800514 titleRes = in.readInt();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700515 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800516 summaryRes = in.readInt();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700517 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800518 breadCrumbTitleRes = in.readInt();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700519 breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800520 breadCrumbShortTitleRes = in.readInt();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700521 breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700522 iconRes = in.readInt();
523 fragment = in.readString();
524 fragmentArguments = in.readBundle();
525 if (in.readInt() != 0) {
526 intent = Intent.CREATOR.createFromParcel(in);
527 }
528 extras = in.readBundle();
529 }
530
531 Header(Parcel in) {
532 readFromParcel(in);
533 }
534
535 public static final Creator<Header> CREATOR = new Creator<Header>() {
536 public Header createFromParcel(Parcel source) {
537 return new Header(source);
538 }
539 public Header[] newArray(int size) {
540 return new Header[size];
541 }
542 };
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700543 }
544
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800545 @Override
Filip Pavlis6af15eb2017-03-01 13:48:33 +0000546 public boolean onOptionsItemSelected(MenuItem item) {
547 if (item.getItemId() == android.R.id.home) {
548 // Override home navigation button to call onBackPressed (b/35152749).
549 onBackPressed();
550 return true;
551 }
552 return super.onOptionsItemSelected(item);
553 }
554
555 @Override
Scott Kennedy3c32b622015-02-22 16:58:58 -0800556 protected void onCreate(@Nullable Bundle savedInstanceState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 super.onCreate(savedInstanceState);
558
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700559 // Theming for the PreferenceActivity layout and for the Preference Header(s) layout
560 TypedArray sa = obtainStyledAttributes(null,
561 com.android.internal.R.styleable.PreferenceActivity,
562 com.android.internal.R.attr.preferenceActivityStyle,
563 0);
564
565 final int layoutResId = sa.getResourceId(
566 com.android.internal.R.styleable.PreferenceActivity_layout,
567 com.android.internal.R.layout.preference_list_content);
568
569 mPreferenceHeaderItemResId = sa.getResourceId(
570 com.android.internal.R.styleable.PreferenceActivity_headerLayout,
571 com.android.internal.R.layout.preference_header_item);
572 mPreferenceHeaderRemoveEmptyIcon = sa.getBoolean(
573 com.android.internal.R.styleable.PreferenceActivity_headerRemoveIconIfEmpty,
574 false);
575
576 sa.recycle();
577
578 setContentView(layoutResId);
Freeman Ng19ea2e02010-03-25 15:09:00 -0700579
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700580 mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800581 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame);
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000582 mHeadersContainer = (ViewGroup) findViewById(com.android.internal.R.id.headers);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700583 boolean hidingHeaders = onIsHidingHeaders();
584 mSinglePane = hidingHeaders || !onIsMultiPane();
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700585 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
586 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Dianne Hackborne72f2372011-03-16 10:43:18 -0700587 int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
588 int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
Filip Pavlisb07c6f62017-02-17 19:23:04 +0000589 mActivityTitle = getTitle();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700590
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700591 if (savedInstanceState != null) {
592 // We are restarting from a previous saved state; used that to
593 // initialize, instead of starting fresh.
594 ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG);
595 if (headers != null) {
596 mHeaders.addAll(headers);
597 int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG,
Gilles Debunne39725ac2011-06-14 18:52:41 -0700598 (int) HEADER_ID_UNDEFINED);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700599 if (curHeader >= 0 && curHeader < mHeaders.size()) {
600 setSelectedHeader(mHeaders.get(curHeader));
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000601 } else if (!mSinglePane && initialFragment == null) {
602 switchToHeader(onGetInitialHeader());
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700603 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000604 } else {
605 // This will for instance hide breadcrumbs for single pane.
606 showBreadCrumbs(getTitle(), null);
607 }
608 } else {
609 if (!onIsHidingHeaders()) {
610 onBuildHeaders(mHeaders);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700611 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700612
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000613 if (initialFragment != null) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700614 switchToHeader(initialFragment, initialArguments);
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000615 } else if (!mSinglePane && mHeaders.size() > 0) {
616 switchToHeader(onGetInitialHeader());
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700617 }
618 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700619
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000620 if (mHeaders.size() > 0) {
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700621 setListAdapter(new HeaderAdapter(this, mHeaders, mPreferenceHeaderItemResId,
622 mPreferenceHeaderRemoveEmptyIcon));
Filip Pavliseadd5a92017-02-10 15:51:41 +0000623 if (!mSinglePane) {
624 getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
625 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000626 }
627
628 if (mSinglePane && initialFragment != null && initialTitle != 0) {
629 CharSequence initialTitleStr = getText(initialTitle);
630 CharSequence initialShortTitleStr = initialShortTitle != 0
631 ? getText(initialShortTitle) : null;
632 showBreadCrumbs(initialTitleStr, initialShortTitleStr);
633 }
634
635 if (mHeaders.size() == 0 && initialFragment == null) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700636 // If there are no headers, we are in the old "just show a screen
637 // of preferences" mode.
Amith Yamasani405c1af2011-05-26 13:08:25 -0700638 setContentView(com.android.internal.R.layout.preference_list_content_single);
Amith Yamasani8da35292010-11-05 09:15:51 -0700639 mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800640 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700641 mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
642 mPreferenceManager.setOnPreferenceTreeClickListener(this);
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000643 mHeadersContainer = null;
644 } else if (mSinglePane) {
645 // Single-pane so one of the header or prefs containers must be hidden.
646 if (initialFragment != null || mCurHeader != null) {
647 mHeadersContainer.setVisibility(View.GONE);
648 } else {
649 mPrefsContainer.setVisibility(View.GONE);
650 }
651
652 // This animates our manual transitions between headers and prefs panel in single-pane.
653 // It also comes last so we don't animate any initial layout changes done above.
654 ViewGroup container = (ViewGroup) findViewById(
655 com.android.internal.R.id.prefs_container);
656 container.setLayoutTransition(new LayoutTransition());
657 } else {
658 // Multi-pane
659 if (mHeaders.size() > 0 && mCurHeader != null) {
660 setSelectedHeader(mCurHeader);
661 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700662 }
663
Freeman Ng19ea2e02010-03-25 15:09:00 -0700664 // see if we should show Back/Next buttons
665 Intent intent = getIntent();
666 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
667
668 findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
669
670 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
671 backButton.setOnClickListener(new OnClickListener() {
672 public void onClick(View v) {
673 setResult(RESULT_CANCELED);
674 finish();
675 }
676 });
Freeman Ng09dbf182010-08-11 15:45:01 -0700677 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
678 skipButton.setOnClickListener(new OnClickListener() {
679 public void onClick(View v) {
680 setResult(RESULT_OK);
681 finish();
682 }
683 });
Freeman Ng19ea2e02010-03-25 15:09:00 -0700684 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
685 mNextButton.setOnClickListener(new OnClickListener() {
686 public void onClick(View v) {
687 setResult(RESULT_OK);
688 finish();
689 }
690 });
691
692 // set our various button parameters
693 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
694 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
695 if (TextUtils.isEmpty(buttonText)) {
696 mNextButton.setVisibility(View.GONE);
697 }
698 else {
699 mNextButton.setText(buttonText);
700 }
701 }
702 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
703 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
704 if (TextUtils.isEmpty(buttonText)) {
705 backButton.setVisibility(View.GONE);
706 }
707 else {
708 backButton.setText(buttonText);
709 }
710 }
Freeman Ng09dbf182010-08-11 15:45:01 -0700711 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
712 skipButton.setVisibility(View.VISIBLE);
713 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700714 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700715 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700716
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000717 @Override
718 public void onBackPressed() {
719 if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
720 && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
721 mCurHeader = null;
722
723 mPrefsContainer.setVisibility(View.GONE);
724 mHeadersContainer.setVisibility(View.VISIBLE);
Filip Pavlisb07c6f62017-02-17 19:23:04 +0000725 if (mActivityTitle != null) {
726 showBreadCrumbs(mActivityTitle, null);
727 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000728 getListView().clearChoices();
729 } else {
730 super.onBackPressed();
731 }
732 }
733
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700734 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700735 * Returns true if this activity is currently showing the header list.
736 */
737 public boolean hasHeaders() {
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000738 return mHeadersContainer != null && mHeadersContainer.getVisibility() == View.VISIBLE;
Dianne Hackborn291905e2010-08-17 15:17:15 -0700739 }
740
741 /**
Amith Yamasani423d48b2012-06-20 13:54:53 -0700742 * Returns the Header list
743 * @hide
744 */
Mathew Inwoodeac8d0a2018-08-17 13:51:26 +0100745 @UnsupportedAppUsage
Amith Yamasani423d48b2012-06-20 13:54:53 -0700746 public List<Header> getHeaders() {
747 return mHeaders;
748 }
749
750 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700751 * Returns true if this activity is showing multiple panes -- the headers
752 * and a preference fragment.
753 */
754 public boolean isMultiPane() {
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000755 return !mSinglePane;
Dianne Hackborn291905e2010-08-17 15:17:15 -0700756 }
757
758 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700759 * Called to determine if the activity should run in multi-pane mode.
760 * The default implementation returns true if the screen is large
761 * enough.
762 */
763 public boolean onIsMultiPane() {
Amith Yamasani405c1af2011-05-26 13:08:25 -0700764 boolean preferMultiPane = getResources().getBoolean(
765 com.android.internal.R.bool.preferences_prefer_dual_pane);
766 return preferMultiPane;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700767 }
768
769 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700770 * Called to determine whether the header list should be hidden.
771 * The default implementation returns the
772 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
773 * This is set to false, for example, when the activity is being re-launched
774 * to show a particular preference activity.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700775 */
776 public boolean onIsHidingHeaders() {
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700777 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700778 }
779
780 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700781 * Called to determine the initial header to be shown. The default
782 * implementation simply returns the fragment of the first header. Note
783 * that the returned Header object does not actually need to exist in
784 * your header list -- whatever its fragment is will simply be used to
785 * show for the initial UI.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700786 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700787 public Header onGetInitialHeader() {
Dianne Hackborn19470ec2013-02-12 11:42:51 -0800788 for (int i=0; i<mHeaders.size(); i++) {
789 Header h = mHeaders.get(i);
790 if (h.fragment != null) {
791 return h;
792 }
793 }
794 throw new IllegalStateException("Must have at least one header with a fragment");
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700795 }
796
797 /**
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700798 * Called after the header list has been updated ({@link #onBuildHeaders}
799 * has been called and returned due to {@link #invalidateHeaders()}) to
800 * specify the header that should now be selected. The default implementation
801 * returns null to keep whatever header is currently selected.
802 */
803 public Header onGetNewHeader() {
804 return null;
805 }
806
807 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700808 * Called when the activity needs its list of headers build. By
809 * implementing this and adding at least one item to the list, you
810 * will cause the activity to run in its modern fragment mode. Note
811 * that this function may not always be called; for example, if the
812 * activity has been asked to display a particular fragment without
813 * the header list, there is no need to build the headers.
814 *
815 * <p>Typical implementations will use {@link #loadHeadersFromResource}
816 * to fill in the list from a resource.
817 *
818 * @param target The list in which to place the headers.
819 */
820 public void onBuildHeaders(List<Header> target) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700821 // Should be overloaded by subclasses
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700822 }
823
824 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700825 * Call when you need to change the headers being displayed. Will result
826 * in onBuildHeaders() later being called to retrieve the new list.
827 */
828 public void invalidateHeaders() {
829 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
830 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
831 }
832 }
833
834 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700835 * Parse the given XML file as a header description, adding each
836 * parsed Header into the target list.
837 *
838 * @param resid The XML resource to load and parse.
839 * @param target The list in which the parsed headers should be placed.
840 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700841 public void loadHeadersFromResource(@XmlRes int resid, List<Header> target) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700842 XmlResourceParser parser = null;
843 try {
844 parser = getResources().getXml(resid);
845 AttributeSet attrs = Xml.asAttributeSet(parser);
846
847 int type;
848 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
849 && type != XmlPullParser.START_TAG) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700850 // Parse next until start tag is found
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700851 }
852
853 String nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700854 if (!"preference-headers".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700855 throw new RuntimeException(
Dianne Hackborndef15372010-08-15 12:43:52 -0700856 "XML document must start with <preference-headers> tag; found"
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000857 + nodeName + " at " + parser.getPositionDescription());
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700858 }
859
Dianne Hackborndef15372010-08-15 12:43:52 -0700860 Bundle curBundle = null;
861
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700862 final int outerDepth = parser.getDepth();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700863 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000864 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700865 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
866 continue;
867 }
868
869 nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700870 if ("header".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700871 Header header = new Header();
872
Alan Viverettef878c292013-12-02 18:36:00 -0800873 TypedArray sa = obtainStyledAttributes(
874 attrs, com.android.internal.R.styleable.PreferenceHeader);
Amith Yamasanied13cde2010-09-17 16:56:47 -0700875 header.id = sa.getResourceId(
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700876 com.android.internal.R.styleable.PreferenceHeader_id,
877 (int)HEADER_ID_UNDEFINED);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800878 TypedValue tv = sa.peekValue(
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700879 com.android.internal.R.styleable.PreferenceHeader_title);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800880 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
881 if (tv.resourceId != 0) {
882 header.titleRes = tv.resourceId;
883 } else {
884 header.title = tv.string;
885 }
886 }
887 tv = sa.peekValue(
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700888 com.android.internal.R.styleable.PreferenceHeader_summary);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800889 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
890 if (tv.resourceId != 0) {
891 header.summaryRes = tv.resourceId;
892 } else {
893 header.summary = tv.string;
894 }
895 }
896 tv = sa.peekValue(
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700897 com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800898 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
899 if (tv.resourceId != 0) {
900 header.breadCrumbTitleRes = tv.resourceId;
901 } else {
902 header.breadCrumbTitle = tv.string;
903 }
904 }
905 tv = sa.peekValue(
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700906 com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800907 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
908 if (tv.resourceId != 0) {
909 header.breadCrumbShortTitleRes = tv.resourceId;
910 } else {
911 header.breadCrumbShortTitle = tv.string;
912 }
913 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700914 header.iconRes = sa.getResourceId(
915 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
916 header.fragment = sa.getString(
917 com.android.internal.R.styleable.PreferenceHeader_fragment);
918 sa.recycle();
919
Dianne Hackborndef15372010-08-15 12:43:52 -0700920 if (curBundle == null) {
921 curBundle = new Bundle();
922 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700923
924 final int innerDepth = parser.getDepth();
925 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000926 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700927 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
928 continue;
929 }
930
931 String innerNodeName = parser.getName();
932 if (innerNodeName.equals("extra")) {
933 getResources().parseBundleExtra("extra", attrs, curBundle);
934 XmlUtils.skipCurrentTag(parser);
935
936 } else if (innerNodeName.equals("intent")) {
937 header.intent = Intent.parseIntent(getResources(), parser, attrs);
938
939 } else {
940 XmlUtils.skipCurrentTag(parser);
941 }
942 }
943
Dianne Hackborndef15372010-08-15 12:43:52 -0700944 if (curBundle.size() > 0) {
945 header.fragmentArguments = curBundle;
946 curBundle = null;
947 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700948
Dianne Hackborndef15372010-08-15 12:43:52 -0700949 target.add(header);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700950 } else {
951 XmlUtils.skipCurrentTag(parser);
952 }
953 }
954
955 } catch (XmlPullParserException e) {
956 throw new RuntimeException("Error parsing headers", e);
957 } catch (IOException e) {
958 throw new RuntimeException("Error parsing headers", e);
959 } finally {
960 if (parser != null) parser.close();
961 }
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700962 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700963
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700964 /**
965 * Subclasses should override this method and verify that the given fragment is a valid type
Amith Yamasanic2be0d62013-09-23 11:16:28 -0700966 * to be attached to this activity. The default implementation returns <code>true</code> for
967 * apps built for <code>android:targetSdkVersion</code> older than
968 * {@link android.os.Build.VERSION_CODES#KITKAT}. For later versions, it will throw an exception.
Amith Yamasani20915daf2013-08-01 12:44:01 -0700969 * @param fragmentName the class name of the Fragment about to be attached to this activity.
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700970 * @return true if the fragment class name is valid for this Activity and false otherwise.
971 */
972 protected boolean isValidFragment(String fragmentName) {
Chet Haasee8222dd2013-09-05 07:44:18 -0700973 if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.KITKAT) {
Amith Yamasania5001b92013-09-06 14:18:00 -0700974 throw new RuntimeException(
975 "Subclasses of PreferenceActivity must override isValidFragment(String)"
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000976 + " to verify that the Fragment class is valid! "
977 + this.getClass().getName()
978 + " has not checked if fragment " + fragmentName + " is valid.");
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700979 } else {
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700980 return true;
981 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800982 }
983
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700984 /**
985 * Set a footer that should be shown at the bottom of the header list.
986 */
987 public void setListFooter(View view) {
988 mListFooter.removeAllViews();
989 mListFooter.addView(view, new FrameLayout.LayoutParams(
990 FrameLayout.LayoutParams.MATCH_PARENT,
991 FrameLayout.LayoutParams.WRAP_CONTENT));
992 }
993
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800994 @Override
995 protected void onStop() {
996 super.onStop();
Freeman Ng19ea2e02010-03-25 15:09:00 -0700997
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700998 if (mPreferenceManager != null) {
999 mPreferenceManager.dispatchActivityStop();
1000 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001001 }
1002
1003 @Override
1004 protected void onDestroy() {
Hiroaki Kuriyama1ebf13e2012-11-16 18:46:49 +09001005 mHandler.removeMessages(MSG_BIND_PREFERENCES);
1006 mHandler.removeMessages(MSG_BUILD_HEADERS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001007 super.onDestroy();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001008
1009 if (mPreferenceManager != null) {
1010 mPreferenceManager.dispatchActivityDestroy();
1011 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 }
1013
1014 @Override
1015 protected void onSaveInstanceState(Bundle outState) {
1016 super.onSaveInstanceState(outState);
1017
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001018 if (mHeaders.size() > 0) {
1019 outState.putParcelableArrayList(HEADERS_TAG, mHeaders);
1020 if (mCurHeader != null) {
1021 int index = mHeaders.indexOf(mCurHeader);
1022 if (index >= 0) {
1023 outState.putInt(CUR_HEADER_TAG, index);
1024 }
1025 }
1026 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001027
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001028 if (mPreferenceManager != null) {
1029 final PreferenceScreen preferenceScreen = getPreferenceScreen();
1030 if (preferenceScreen != null) {
1031 Bundle container = new Bundle();
1032 preferenceScreen.saveHierarchyState(container);
1033 outState.putBundle(PREFERENCES_TAG, container);
1034 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001035 }
1036 }
1037
1038 @Override
1039 protected void onRestoreInstanceState(Bundle state) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001040 if (mPreferenceManager != null) {
1041 Bundle container = state.getBundle(PREFERENCES_TAG);
1042 if (container != null) {
1043 final PreferenceScreen preferenceScreen = getPreferenceScreen();
1044 if (preferenceScreen != null) {
1045 preferenceScreen.restoreHierarchyState(container);
1046 mSavedInstanceState = state;
1047 return;
1048 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001049 }
1050 }
Adam Powelle7fea452010-03-18 14:51:39 -07001051
1052 // Only call this if we didn't save the instance state for later.
1053 // If we did save it, it will be restored when we bind the adapter.
1054 super.onRestoreInstanceState(state);
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001055
1056 if (!mSinglePane) {
1057 // Multi-pane.
1058 if (mCurHeader != null) {
1059 setSelectedHeader(mCurHeader);
1060 }
1061 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001062 }
1063
1064 @Override
1065 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1066 super.onActivityResult(requestCode, resultCode, data);
Freeman Ng19ea2e02010-03-25 15:09:00 -07001067
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001068 if (mPreferenceManager != null) {
1069 mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
1070 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001071 }
1072
1073 @Override
1074 public void onContentChanged() {
1075 super.onContentChanged();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001076
1077 if (mPreferenceManager != null) {
1078 postBindPreferences();
1079 }
1080 }
1081
1082 @Override
1083 protected void onListItemClick(ListView l, View v, int position, long id) {
Amith Yamasani49bdc162013-07-17 15:52:45 -07001084 if (!isResumed()) {
1085 return;
1086 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001087 super.onListItemClick(l, v, position, id);
1088
1089 if (mAdapter != null) {
Gilles Debunne39725ac2011-06-14 18:52:41 -07001090 Object item = mAdapter.getItem(position);
1091 if (item instanceof Header) onHeaderClick((Header) item, position);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001092 }
1093 }
1094
1095 /**
1096 * Called when the user selects an item in the header list. The default
Dianne Hackborne72f2372011-03-16 10:43:18 -07001097 * implementation will call either
1098 * {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001099 * or {@link #switchToHeader(Header)} as appropriate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001100 *
1101 * @param header The header that was selected.
1102 * @param position The header's position in the list.
1103 */
1104 public void onHeaderClick(Header header, int position) {
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001105 if (header.fragment != null) {
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001106 switchToHeader(header);
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001107 } else if (header.intent != null) {
1108 startActivity(header.intent);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001109 }
1110 }
1111
1112 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -07001113 * Called by {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} when
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001114 * in single-pane mode, to build an Intent to launch a new activity showing
1115 * the selected fragment. The default implementation constructs an Intent
1116 * that re-launches the current activity with the appropriate arguments to
1117 * display the fragment.
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001118 *
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001119 * @param fragmentName The name of the fragment to display.
1120 * @param args Optional arguments to supply to the fragment.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001121 * @param titleRes Optional resource ID of title to show for this item.
Gilles Debunne39725ac2011-06-14 18:52:41 -07001122 * @param shortTitleRes Optional resource ID of short title to show for this item.
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001123 * @return Returns an Intent that can be launched to display the given
1124 * fragment.
1125 */
Dianne Hackborne72f2372011-03-16 10:43:18 -07001126 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
Tor Norbye7b9c9122013-05-30 16:48:33 -07001127 @StringRes int titleRes, int shortTitleRes) {
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001128 Intent intent = new Intent(Intent.ACTION_MAIN);
1129 intent.setClass(this, getClass());
1130 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
1131 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
Dianne Hackborne72f2372011-03-16 10:43:18 -07001132 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, titleRes);
1133 intent.putExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, shortTitleRes);
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001134 intent.putExtra(EXTRA_NO_HEADERS, true);
1135 return intent;
1136 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001137
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001138 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -07001139 * Like {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
1140 * but uses a 0 titleRes.
1141 */
1142 public void startWithFragment(String fragmentName, Bundle args,
1143 Fragment resultTo, int resultRequestCode) {
1144 startWithFragment(fragmentName, args, resultTo, resultRequestCode, 0, 0);
1145 }
1146
1147 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001148 * Start a new instance of this activity, showing only the given
1149 * preference fragment. When launched in this mode, the header list
1150 * will be hidden and the given preference fragment will be instantiated
1151 * and fill the entire activity.
1152 *
1153 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001154 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001155 * @param resultTo Option fragment that should receive the result of
1156 * the activity launch.
1157 * @param resultRequestCode If resultTo is non-null, this is the request
1158 * code in which to report the result.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001159 * @param titleRes Resource ID of string to display for the title of
1160 * this set of preferences.
Gilles Debunne39725ac2011-06-14 18:52:41 -07001161 * @param shortTitleRes Resource ID of string to display for the short title of
Dianne Hackborne72f2372011-03-16 10:43:18 -07001162 * this set of preferences.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001163 */
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001164 public void startWithFragment(String fragmentName, Bundle args,
Tor Norbye7b9c9122013-05-30 16:48:33 -07001165 Fragment resultTo, int resultRequestCode, @StringRes int titleRes,
1166 @StringRes int shortTitleRes) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001167 Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001168 if (resultTo == null) {
1169 startActivity(intent);
1170 } else {
1171 resultTo.startActivityForResult(intent, resultRequestCode);
1172 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001173 }
1174
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001175 /**
1176 * Change the base title of the bread crumbs for the current preferences.
1177 * This will normally be called for you. See
1178 * {@link android.app.FragmentBreadCrumbs} for more information.
1179 */
1180 public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) {
1181 if (mFragmentBreadCrumbs == null) {
Amith Yamasani3e860402010-12-10 14:20:51 -08001182 View crumbs = findViewById(android.R.id.title);
1183 // For screens with a different kind of title, don't create breadcrumbs.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001184 try {
1185 mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs;
1186 } catch (ClassCastException e) {
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001187 setTitle(title);
Dianne Hackborne72f2372011-03-16 10:43:18 -07001188 return;
1189 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001190 if (mFragmentBreadCrumbs == null) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001191 if (title != null) {
1192 setTitle(title);
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001193 }
Dianne Hackborne72f2372011-03-16 10:43:18 -07001194 return;
Jim Millerc57406c2010-12-08 16:01:05 -08001195 }
Amith Yamasani3ec7bac2012-10-03 16:02:51 -07001196 if (mSinglePane) {
1197 mFragmentBreadCrumbs.setVisibility(View.GONE);
1198 // Hide the breadcrumb section completely for single-pane
1199 View bcSection = findViewById(com.android.internal.R.id.breadcrumb_section);
1200 if (bcSection != null) bcSection.setVisibility(View.GONE);
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001201 setTitle(title);
Amith Yamasani3ec7bac2012-10-03 16:02:51 -07001202 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001203 mFragmentBreadCrumbs.setMaxVisible(2);
1204 mFragmentBreadCrumbs.setActivity(this);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001205 }
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001206 if (mFragmentBreadCrumbs.getVisibility() != View.VISIBLE) {
1207 setTitle(title);
1208 } else {
1209 mFragmentBreadCrumbs.setTitle(title, shortTitle);
1210 mFragmentBreadCrumbs.setParentTitle(null, null, null);
1211 }
Amith Yamasanic9ecb732010-12-14 14:23:21 -08001212 }
1213
1214 /**
1215 * Should be called after onCreate to ensure that the breadcrumbs, if any, were created.
1216 * This prepends a title to the fragment breadcrumbs and attaches a listener to any clicks
1217 * on the parent entry.
1218 * @param title the title for the breadcrumb
1219 * @param shortTitle the short title for the breadcrumb
1220 */
1221 public void setParentTitle(CharSequence title, CharSequence shortTitle,
1222 OnClickListener listener) {
1223 if (mFragmentBreadCrumbs != null) {
1224 mFragmentBreadCrumbs.setParentTitle(title, shortTitle, listener);
1225 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001226 }
1227
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001228 void setSelectedHeader(Header header) {
1229 mCurHeader = header;
1230 int index = mHeaders.indexOf(header);
1231 if (index >= 0) {
1232 getListView().setItemChecked(index, true);
1233 } else {
1234 getListView().clearChoices();
1235 }
Dianne Hackborn34905a92011-07-21 17:30:07 -07001236 showBreadCrumbs(header);
1237 }
1238
1239 void showBreadCrumbs(Header header) {
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001240 if (header != null) {
Dianne Hackborn50ed8292010-12-03 12:30:21 -08001241 CharSequence title = header.getBreadCrumbTitle(getResources());
1242 if (title == null) title = header.getTitle(getResources());
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001243 if (title == null) title = getTitle();
Dianne Hackborn50ed8292010-12-03 12:30:21 -08001244 showBreadCrumbs(title, header.getBreadCrumbShortTitle(getResources()));
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001245 } else {
1246 showBreadCrumbs(getTitle(), null);
1247 }
1248 }
1249
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001250 private void switchToHeaderInner(String fragmentName, Bundle args) {
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001251 getFragmentManager().popBackStack(BACK_STACK_PREFS,
1252 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Amith Yamasani364ed4d2013-07-26 13:37:56 -07001253 if (!isValidFragment(fragmentName)) {
1254 throw new IllegalArgumentException("Invalid fragment for this activity: "
1255 + fragmentName);
1256 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001257
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001258 Fragment f = Fragment.instantiate(this, fragmentName, args);
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001259 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001260 transaction.setTransition(mSinglePane
1261 ? FragmentTransaction.TRANSIT_NONE
1262 : FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Chet Haase9ff82bf2010-10-05 14:30:51 -07001263 transaction.replace(com.android.internal.R.id.prefs, f);
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001264 transaction.commitAllowingStateLoss();
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001265
1266 if (mSinglePane && mPrefsContainer.getVisibility() == View.GONE) {
1267 // We are transitioning from headers to preferences panel in single-pane so we need
1268 // to hide headers and show the prefs container.
1269 mPrefsContainer.setVisibility(View.VISIBLE);
1270 mHeadersContainer.setVisibility(View.GONE);
1271 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001272 }
1273
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001274 /**
1275 * When in two-pane mode, switch the fragment pane to show the given
1276 * preference fragment.
1277 *
1278 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001279 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001280 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001281 public void switchToHeader(String fragmentName, Bundle args) {
Brian Attwell02bc31e2014-06-16 17:06:13 -07001282 Header selectedHeader = null;
1283 for (int i = 0; i < mHeaders.size(); i++) {
1284 if (fragmentName.equals(mHeaders.get(i).fragment)) {
1285 selectedHeader = mHeaders.get(i);
1286 break;
1287 }
1288 }
1289 setSelectedHeader(selectedHeader);
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001290 switchToHeaderInner(fragmentName, args);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001291 }
1292
Andrew Stadleraa904f42010-09-02 14:50:08 -07001293 /**
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001294 * When in two-pane mode, switch to the fragment pane to show the given
1295 * preference fragment.
1296 *
1297 * @param header The new header to display.
1298 */
1299 public void switchToHeader(Header header) {
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001300 if (mCurHeader == header) {
1301 // This is the header we are currently displaying. Just make sure
1302 // to pop the stack up to its root state.
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001303 getFragmentManager().popBackStack(BACK_STACK_PREFS,
1304 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001305 } else {
Dianne Hackborn19470ec2013-02-12 11:42:51 -08001306 if (header.fragment == null) {
1307 throw new IllegalStateException("can't switch to header that has no fragment");
1308 }
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001309 switchToHeaderInner(header.fragment, header.fragmentArguments);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001310 setSelectedHeader(header);
1311 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001312 }
1313
1314 Header findBestMatchingHeader(Header cur, ArrayList<Header> from) {
1315 ArrayList<Header> matches = new ArrayList<Header>();
1316 for (int j=0; j<from.size(); j++) {
1317 Header oh = from.get(j);
1318 if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) {
1319 // Must be this one.
1320 matches.clear();
1321 matches.add(oh);
1322 break;
1323 }
1324 if (cur.fragment != null) {
1325 if (cur.fragment.equals(oh.fragment)) {
1326 matches.add(oh);
1327 }
1328 } else if (cur.intent != null) {
1329 if (cur.intent.equals(oh.intent)) {
1330 matches.add(oh);
1331 }
1332 } else if (cur.title != null) {
1333 if (cur.title.equals(oh.title)) {
1334 matches.add(oh);
1335 }
1336 }
1337 }
1338 final int NM = matches.size();
1339 if (NM == 1) {
1340 return matches.get(0);
1341 } else if (NM > 1) {
1342 for (int j=0; j<NM; j++) {
1343 Header oh = matches.get(j);
1344 if (cur.fragmentArguments != null &&
1345 cur.fragmentArguments.equals(oh.fragmentArguments)) {
1346 return oh;
1347 }
1348 if (cur.extras != null && cur.extras.equals(oh.extras)) {
1349 return oh;
1350 }
1351 if (cur.title != null && cur.title.equals(oh.title)) {
1352 return oh;
1353 }
1354 }
1355 }
1356 return null;
1357 }
1358
1359 /**
Andrew Stadleraa904f42010-09-02 14:50:08 -07001360 * Start a new fragment.
1361 *
1362 * @param fragment The fragment to start
1363 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1364 * the current fragment will be replaced.
1365 */
1366 public void startPreferenceFragment(Fragment fragment, boolean push) {
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001367 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001368 transaction.replace(com.android.internal.R.id.prefs, fragment);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001369 if (push) {
Chet Haase9ff82bf2010-10-05 14:30:51 -07001370 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001371 transaction.addToBackStack(BACK_STACK_PREFS);
Chet Haase9ff82bf2010-10-05 14:30:51 -07001372 } else {
Dianne Hackborn327fbd22011-01-17 14:38:50 -08001373 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001374 }
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001375 transaction.commitAllowingStateLoss();
Andrew Stadleraa904f42010-09-02 14:50:08 -07001376 }
1377
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001378 /**
Amith Yamasania47372c2013-08-05 10:55:28 -07001379 * Start a new fragment containing a preference panel. If the preferences
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001380 * are being displayed in multi-pane mode, the given fragment class will
1381 * be instantiated and placed in the appropriate pane. If running in
1382 * single-pane mode, a new activity will be launched in which to show the
1383 * fragment.
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001384 *
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001385 * @param fragmentClass Full name of the class implementing the fragment.
1386 * @param args Any desired arguments to supply to the fragment.
1387 * @param titleRes Optional resource identifier of the title of this
1388 * fragment.
1389 * @param titleText Optional text of the title of this fragment.
1390 * @param resultTo Optional fragment that result data should be sent to.
1391 * If non-null, resultTo.onActivityResult() will be called when this
1392 * preference panel is done. The launched panel must use
1393 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
1394 * @param resultRequestCode If resultTo is non-null, this is the caller's
Tor Norbye7b9c9122013-05-30 16:48:33 -07001395 * request code to be received with the result.
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001396 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07001397 public void startPreferencePanel(String fragmentClass, Bundle args, @StringRes int titleRes,
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001398 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001399 Fragment f = Fragment.instantiate(this, fragmentClass, args);
1400 if (resultTo != null) {
1401 f.setTargetFragment(resultTo, resultRequestCode);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001402 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001403 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1404 transaction.replace(com.android.internal.R.id.prefs, f);
1405 if (titleRes != 0) {
1406 transaction.setBreadCrumbTitle(titleRes);
1407 } else if (titleText != null) {
1408 transaction.setBreadCrumbTitle(titleText);
1409 }
1410 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1411 transaction.addToBackStack(BACK_STACK_PREFS);
1412 transaction.commitAllowingStateLoss();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001413 }
Amith Yamasani364ed4d2013-07-26 13:37:56 -07001414
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001415 /**
1416 * Called by a preference panel fragment to finish itself.
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001417 *
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001418 * @param caller The fragment that is asking to be finished.
1419 * @param resultCode Optional result code to send back to the original
1420 * launching fragment.
1421 * @param resultData Optional result data to send back to the original
1422 * launching fragment.
1423 */
1424 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001425 // TODO: be smarter about popping the stack.
1426 onBackPressed();
1427 if (caller != null) {
1428 if (caller.getTargetFragment() != null) {
1429 caller.getTargetFragment().onActivityResult(caller.getTargetRequestCode(),
1430 resultCode, resultData);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001431 }
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001432 }
1433 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001434
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -07001435 @Override
1436 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001437 startPreferencePanel(pref.getFragment(), pref.getExtras(), pref.getTitleRes(),
1438 pref.getTitle(), null, 0);
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -07001439 return true;
1440 }
1441
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001442 /**
1443 * Posts a message to bind the preferences to the list view.
1444 * <p>
1445 * Binding late is preferred as any custom preference types created in
1446 * {@link #onCreate(Bundle)} are able to have their views recycled.
1447 */
Mathew Inwoodeac8d0a2018-08-17 13:51:26 +01001448 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001449 private void postBindPreferences() {
1450 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
1451 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
1452 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001453
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001454 private void bindPreferences() {
1455 final PreferenceScreen preferenceScreen = getPreferenceScreen();
1456 if (preferenceScreen != null) {
1457 preferenceScreen.bind(getListView());
Adam Powelle7fea452010-03-18 14:51:39 -07001458 if (mSavedInstanceState != null) {
1459 super.onRestoreInstanceState(mSavedInstanceState);
1460 mSavedInstanceState = null;
1461 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001462 }
1463 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001464
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001465 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001466 * Returns the {@link PreferenceManager} used by this activity.
1467 * @return The {@link PreferenceManager}.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001468 *
1469 * @deprecated This function is not relevant for a modern fragment-based
1470 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001471 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001472 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001473 public PreferenceManager getPreferenceManager() {
1474 return mPreferenceManager;
1475 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001476
Mathew Inwoodeac8d0a2018-08-17 13:51:26 +01001477 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001478 private void requirePreferenceManager() {
1479 if (mPreferenceManager == null) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001480 if (mAdapter == null) {
1481 throw new RuntimeException("This should be called after super.onCreate.");
1482 }
1483 throw new RuntimeException(
1484 "Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001485 }
1486 }
1487
1488 /**
1489 * Sets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001490 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001491 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001492 *
1493 * @deprecated This function is not relevant for a modern fragment-based
1494 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001495 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001496 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001497 public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001498 requirePreferenceManager();
1499
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001500 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
1501 postBindPreferences();
1502 CharSequence title = getPreferenceScreen().getTitle();
1503 // Set the title of the activity
1504 if (title != null) {
1505 setTitle(title);
1506 }
1507 }
1508 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001509
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001510 /**
1511 * Gets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001512 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001513 * @return The {@link PreferenceScreen} that is the root of the preference
1514 * hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001515 *
1516 * @deprecated This function is not relevant for a modern fragment-based
1517 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001518 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001519 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001520 public PreferenceScreen getPreferenceScreen() {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001521 if (mPreferenceManager != null) {
1522 return mPreferenceManager.getPreferenceScreen();
1523 }
1524 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001525 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001526
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001527 /**
1528 * Adds preferences from activities that match the given {@link Intent}.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001529 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001530 * @param intent The {@link Intent} to query activities.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001531 *
1532 * @deprecated This function is not relevant for a modern fragment-based
1533 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001534 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001535 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001536 public void addPreferencesFromIntent(Intent intent) {
1537 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -07001538
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001539 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
1540 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001541
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001542 /**
1543 * Inflates the given XML resource and adds the preference hierarchy to the current
1544 * preference hierarchy.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001545 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001546 * @param preferencesResId The XML resource ID to inflate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001547 *
1548 * @deprecated This function is not relevant for a modern fragment-based
1549 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001550 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001551 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001552 public void addPreferencesFromResource(int preferencesResId) {
1553 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -07001554
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001555 setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
1556 getPreferenceScreen()));
1557 }
1558
1559 /**
1560 * {@inheritDoc}
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001561 *
1562 * @deprecated This function is not relevant for a modern fragment-based
1563 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001564 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001565 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001566 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
1567 return false;
1568 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001569
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001570 /**
1571 * Finds a {@link Preference} based on its key.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001572 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001573 * @param key The key of the preference to retrieve.
1574 * @return The {@link Preference} with the key, or null.
1575 * @see PreferenceGroup#findPreference(CharSequence)
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001576 *
1577 * @deprecated This function is not relevant for a modern fragment-based
1578 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001579 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001580 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001581 public Preference findPreference(CharSequence key) {
Freeman Ng19ea2e02010-03-25 15:09:00 -07001582
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001583 if (mPreferenceManager == null) {
1584 return null;
1585 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001586
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001587 return mPreferenceManager.findPreference(key);
1588 }
1589
1590 @Override
1591 protected void onNewIntent(Intent intent) {
1592 if (mPreferenceManager != null) {
1593 mPreferenceManager.dispatchNewIntent(intent);
1594 }
1595 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001596
1597 // give subclasses access to the Next button
1598 /** @hide */
1599 protected boolean hasNextButton() {
1600 return mNextButton != null;
1601 }
1602 /** @hide */
1603 protected Button getNextButton() {
1604 return mNextButton;
1605 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001606}