blob: eab5937a370e7ea87ded581f264ed360f6229ca2 [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 Inwoodf2217132018-08-17 13:41:55 +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 *
Louis Pullen-Freilichb9596fa2018-11-19 17:40:56 +0000100 * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
101 * <a href="{@docRoot}reference/androidx/preference/package-summary.html">
102 * Preference Library</a> for consistent behavior across all devices. For more information on
103 * using the AndroidX Preference Library see
104 * <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 */
Louis Pullen-Freilichb9596fa2018-11-19 17:40:56 +0000106@Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107public abstract class PreferenceActivity extends ListActivity implements
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700108 PreferenceManager.OnPreferenceTreeClickListener,
109 PreferenceFragment.OnPreferenceStartFragmentCallback {
Freeman Ng19ea2e02010-03-25 15:09:00 -0700110
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +0000111 private static final String TAG = "PreferenceActivity";
112
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700113 // Constants for state save/restore
114 private static final String HEADERS_TAG = ":android:headers";
115 private static final String CUR_HEADER_TAG = ":android:cur_header";
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700116 private static final String PREFERENCES_TAG = ":android:preferences";
Freeman Ng19ea2e02010-03-25 15:09:00 -0700117
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700118 /**
119 * When starting this activity, the invoking Intent can contain this extra
120 * string to specify which fragment should be initially displayed.
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700121 * <p/>Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity
122 * will call isValidFragment() to confirm that the fragment class name is valid for this
123 * activity.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700124 */
125 public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700126
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700127 /**
128 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
Dianne Hackborne72f2372011-03-16 10:43:18 -0700129 * this extra can also be specified to supply a Bundle of arguments to pass
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700130 * to that fragment when it is instantiated during the initial creation
131 * of PreferenceActivity.
132 */
133 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
134
135 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -0700136 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
137 * this extra can also be specify to supply the title to be shown for
138 * that fragment.
139 */
140 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title";
141
142 /**
143 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
144 * this extra can also be specify to supply the short title to be shown for
145 * that fragment.
146 */
147 public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE
148 = ":android:show_fragment_short_title";
149
150 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700151 * When starting this activity, the invoking Intent can contain this extra
152 * boolean that the header list should not be displayed. This is most often
153 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
154 * the activity to display a specific fragment that the user has navigated
155 * to.
156 */
157 public static final String EXTRA_NO_HEADERS = ":android:no_headers";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700158
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700159 private static final String BACK_STACK_PREFS = ":android:prefs";
160
Freeman Ng19ea2e02010-03-25 15:09:00 -0700161 // extras that allow any preference activity to be launched as part of a wizard
162
163 // show Back and Next buttons? takes boolean parameter
164 // Back will then return RESULT_CANCELED and Next RESULT_OK
165 private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
166
Freeman Ng09dbf182010-08-11 15:45:01 -0700167 // add a Skip button?
168 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
169
Freeman Ng19ea2e02010-03-25 15:09:00 -0700170 // specify custom text for the Back or Next buttons, or cause a button to not appear
171 // at all by setting it to null
172 private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
173 private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
174
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700175 // --- State for new mode when showing a list of headers + prefs fragment
176
177 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
178
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700179 private FrameLayout mListFooter;
180
Mathew Inwoodf2217132018-08-17 13:41:55 +0100181 @UnsupportedAppUsage
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800182 private ViewGroup mPrefsContainer;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700183
Filip Pavlisb07c6f62017-02-17 19:23:04 +0000184 // Backup of the original activity title. This is used when navigating back to the headers list
185 // in onBackPress to restore the title.
186 private CharSequence mActivityTitle;
187
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000188 // Null if in legacy mode.
189 private ViewGroup mHeadersContainer;
190
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700191 private FragmentBreadCrumbs mFragmentBreadCrumbs;
192
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700193 private boolean mSinglePane;
194
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700195 private Header mCurHeader;
196
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700197 // --- State for old mode when showing a single preference list
Freeman Ng19ea2e02010-03-25 15:09:00 -0700198
Mathew Inwoodf2217132018-08-17 13:41:55 +0100199 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 private PreferenceManager mPreferenceManager;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700201
Adam Powelle7fea452010-03-18 14:51:39 -0700202 private Bundle mSavedInstanceState;
203
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700204 // --- Common state
205
206 private Button mNextButton;
207
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700208 private int mPreferenceHeaderItemResId = 0;
209 private boolean mPreferenceHeaderRemoveEmptyIcon = false;
210
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 /**
212 * The starting request code given out to preference framework.
213 */
214 private static final int FIRST_REQUEST_CODE = 100;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700215
Dianne Hackborn3e449ce2010-09-11 20:52:31 -0700216 private static final int MSG_BIND_PREFERENCES = 1;
217 private static final int MSG_BUILD_HEADERS = 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 private Handler mHandler = new Handler() {
219 @Override
220 public void handleMessage(Message msg) {
221 switch (msg.what) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700222 case MSG_BIND_PREFERENCES: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 bindPreferences();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700224 } break;
225 case MSG_BUILD_HEADERS: {
226 ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders);
227 mHeaders.clear();
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700228 onBuildHeaders(mHeaders);
Gilles Debunne39725ac2011-06-14 18:52:41 -0700229 if (mAdapter instanceof BaseAdapter) {
230 ((BaseAdapter) mAdapter).notifyDataSetChanged();
Andrew Stadler83681eb2010-11-04 15:49:05 -0700231 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700232 Header header = onGetNewHeader();
233 if (header != null && header.fragment != null) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700234 Header mappedHeader = findBestMatchingHeader(header, oldHeaders);
235 if (mappedHeader == null || mCurHeader != mappedHeader) {
236 switchToHeader(header);
237 }
238 } else if (mCurHeader != null) {
239 Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders);
240 if (mappedHeader != null) {
241 setSelectedHeader(mappedHeader);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700242 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700243 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700244 } break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 }
246 }
247 };
248
Andrew Stadler468c3232010-08-17 16:16:42 -0700249 private static class HeaderAdapter extends ArrayAdapter<Header> {
250 private static class HeaderViewHolder {
251 ImageView icon;
252 TextView title;
253 TextView summary;
254 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700255
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700256 private LayoutInflater mInflater;
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700257 private int mLayoutResId;
258 private boolean mRemoveIconIfEmpty;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700259
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700260 public HeaderAdapter(Context context, List<Header> objects, int layoutResId,
261 boolean removeIconBehavior) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700262 super(context, 0, objects);
263 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700264 mLayoutResId = layoutResId;
265 mRemoveIconIfEmpty = removeIconBehavior;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700266 }
267
268 @Override
269 public View getView(int position, View convertView, ViewGroup parent) {
270 HeaderViewHolder holder;
271 View view;
272
273 if (convertView == null) {
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700274 view = mInflater.inflate(mLayoutResId, parent, false);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700275 holder = new HeaderViewHolder();
Andrew Stadler468c3232010-08-17 16:16:42 -0700276 holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
277 holder.title = (TextView) view.findViewById(com.android.internal.R.id.title);
278 holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700279 view.setTag(holder);
280 } else {
281 view = convertView;
Andrew Stadler468c3232010-08-17 16:16:42 -0700282 holder = (HeaderViewHolder) view.getTag();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700283 }
284
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000285 // All view fields must be updated every time, because the view may be recycled
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700286 Header header = getItem(position);
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700287 if (mRemoveIconIfEmpty) {
288 if (header.iconRes == 0) {
289 holder.icon.setVisibility(View.GONE);
290 } else {
291 holder.icon.setVisibility(View.VISIBLE);
292 holder.icon.setImageResource(header.iconRes);
293 }
294 } else {
295 holder.icon.setImageResource(header.iconRes);
296 }
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800297 holder.title.setText(header.getTitle(getContext().getResources()));
298 CharSequence summary = header.getSummary(getContext().getResources());
299 if (!TextUtils.isEmpty(summary)) {
Andrew Stadler468c3232010-08-17 16:16:42 -0700300 holder.summary.setVisibility(View.VISIBLE);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800301 holder.summary.setText(summary);
302 } else {
303 holder.summary.setVisibility(View.GONE);
Andrew Stadler468c3232010-08-17 16:16:42 -0700304 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700305
306 return view;
307 }
308 }
309
310 /**
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700311 * Default value for {@link Header#id Header.id} indicating that no
312 * identifier value is set. All other values (including those below -1)
313 * are valid.
314 */
315 public static final long HEADER_ID_UNDEFINED = -1;
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700316
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700317 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700318 * Description of a single Header item that the user can select.
Louis Pullen-Freilichb9596fa2018-11-19 17:40:56 +0000319 *
320 * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
321 * <a href="{@docRoot}reference/androidx/preference/package-summary.html">
322 * Preference Library</a> for consistent behavior across all devices.
323 * For more information on using the AndroidX Preference Library see
324 * <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700325 */
Louis Pullen-Freilichb9596fa2018-11-19 17:40:56 +0000326 @Deprecated
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700327 public static final class Header implements Parcelable {
328 /**
329 * Identifier for this header, to correlate with a new list when
330 * it is updated. The default value is
331 * {@link PreferenceActivity#HEADER_ID_UNDEFINED}, meaning no id.
332 * @attr ref android.R.styleable#PreferenceHeader_id
333 */
334 public long id = HEADER_ID_UNDEFINED;
335
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700336 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800337 * Resource ID of title of the header that is shown to the user.
338 * @attr ref android.R.styleable#PreferenceHeader_title
339 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700340 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800341 public int titleRes;
342
343 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700344 * Title of the header that is shown to the user.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700345 * @attr ref android.R.styleable#PreferenceHeader_title
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700346 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700347 public CharSequence title;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700348
349 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800350 * Resource ID of optional summary describing what this header controls.
351 * @attr ref android.R.styleable#PreferenceHeader_summary
352 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700353 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800354 public int summaryRes;
355
356 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700357 * Optional summary describing what this header controls.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700358 * @attr ref android.R.styleable#PreferenceHeader_summary
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700359 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700360 public CharSequence summary;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700361
362 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800363 * Resource ID of optional text to show as the title in the bread crumb.
364 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
365 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700366 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800367 public int breadCrumbTitleRes;
368
369 /**
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700370 * Optional text to show as the title in the bread crumb.
371 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
372 */
373 public CharSequence breadCrumbTitle;
374
375 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800376 * Resource ID of optional text to show as the short title in the bread crumb.
377 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
378 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700379 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800380 public int breadCrumbShortTitleRes;
381
382 /**
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700383 * Optional text to show as the short title in the bread crumb.
384 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
385 */
386 public CharSequence breadCrumbShortTitle;
387
388 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700389 * Optional icon resource to show for this header.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700390 * @attr ref android.R.styleable#PreferenceHeader_icon
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700391 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700392 public int iconRes;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700393
394 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700395 * Full class name of the fragment to display when this header is
396 * selected.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700397 * @attr ref android.R.styleable#PreferenceHeader_fragment
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700398 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700399 public String fragment;
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700400
401 /**
402 * Optional arguments to supply to the fragment when it is
403 * instantiated.
404 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700405 public Bundle fragmentArguments;
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700406
407 /**
408 * Intent to launch when the preference is selected.
409 */
410 public Intent intent;
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700411
412 /**
413 * Optional additional data for use by subclasses of PreferenceActivity.
414 */
415 public Bundle extras;
416
417 public Header() {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700418 // Empty
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700419 }
420
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800421 /**
422 * Return the currently set title. If {@link #titleRes} is set,
423 * this resource is loaded from <var>res</var> and returned. Otherwise
424 * {@link #title} is returned.
425 */
426 public CharSequence getTitle(Resources res) {
427 if (titleRes != 0) {
428 return res.getText(titleRes);
429 }
430 return title;
431 }
432
433 /**
434 * Return the currently set summary. If {@link #summaryRes} is set,
435 * this resource is loaded from <var>res</var> and returned. Otherwise
436 * {@link #summary} is returned.
437 */
438 public CharSequence getSummary(Resources res) {
439 if (summaryRes != 0) {
440 return res.getText(summaryRes);
441 }
Dianne Hackborn9d071802010-12-08 14:49:15 -0800442 return summary;
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800443 }
444
445 /**
446 * Return the currently set bread crumb title. If {@link #breadCrumbTitleRes} is set,
447 * this resource is loaded from <var>res</var> and returned. Otherwise
448 * {@link #breadCrumbTitle} is returned.
449 */
450 public CharSequence getBreadCrumbTitle(Resources res) {
451 if (breadCrumbTitleRes != 0) {
452 return res.getText(breadCrumbTitleRes);
453 }
454 return breadCrumbTitle;
455 }
456
457 /**
458 * Return the currently set bread crumb short title. If
459 * {@link #breadCrumbShortTitleRes} is set,
460 * this resource is loaded from <var>res</var> and returned. Otherwise
461 * {@link #breadCrumbShortTitle} is returned.
462 */
463 public CharSequence getBreadCrumbShortTitle(Resources res) {
464 if (breadCrumbShortTitleRes != 0) {
465 return res.getText(breadCrumbShortTitleRes);
466 }
467 return breadCrumbShortTitle;
468 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000469
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700470 @Override
471 public int describeContents() {
472 return 0;
473 }
474
475 @Override
476 public void writeToParcel(Parcel dest, int flags) {
477 dest.writeLong(id);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800478 dest.writeInt(titleRes);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700479 TextUtils.writeToParcel(title, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800480 dest.writeInt(summaryRes);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700481 TextUtils.writeToParcel(summary, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800482 dest.writeInt(breadCrumbTitleRes);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700483 TextUtils.writeToParcel(breadCrumbTitle, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800484 dest.writeInt(breadCrumbShortTitleRes);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700485 TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700486 dest.writeInt(iconRes);
487 dest.writeString(fragment);
488 dest.writeBundle(fragmentArguments);
489 if (intent != null) {
490 dest.writeInt(1);
491 intent.writeToParcel(dest, flags);
492 } else {
493 dest.writeInt(0);
494 }
495 dest.writeBundle(extras);
496 }
497
498 public void readFromParcel(Parcel in) {
499 id = in.readLong();
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800500 titleRes = in.readInt();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700501 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800502 summaryRes = in.readInt();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700503 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800504 breadCrumbTitleRes = in.readInt();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700505 breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800506 breadCrumbShortTitleRes = in.readInt();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700507 breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700508 iconRes = in.readInt();
509 fragment = in.readString();
510 fragmentArguments = in.readBundle();
511 if (in.readInt() != 0) {
512 intent = Intent.CREATOR.createFromParcel(in);
513 }
514 extras = in.readBundle();
515 }
516
517 Header(Parcel in) {
518 readFromParcel(in);
519 }
520
521 public static final Creator<Header> CREATOR = new Creator<Header>() {
522 public Header createFromParcel(Parcel source) {
523 return new Header(source);
524 }
525 public Header[] newArray(int size) {
526 return new Header[size];
527 }
528 };
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700529 }
530
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531 @Override
Filip Pavlis6af15eb2017-03-01 13:48:33 +0000532 public boolean onOptionsItemSelected(MenuItem item) {
533 if (item.getItemId() == android.R.id.home) {
534 // Override home navigation button to call onBackPressed (b/35152749).
535 onBackPressed();
536 return true;
537 }
538 return super.onOptionsItemSelected(item);
539 }
540
541 @Override
Scott Kennedy3c32b622015-02-22 16:58:58 -0800542 protected void onCreate(@Nullable Bundle savedInstanceState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800543 super.onCreate(savedInstanceState);
544
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700545 // Theming for the PreferenceActivity layout and for the Preference Header(s) layout
546 TypedArray sa = obtainStyledAttributes(null,
547 com.android.internal.R.styleable.PreferenceActivity,
548 com.android.internal.R.attr.preferenceActivityStyle,
549 0);
550
551 final int layoutResId = sa.getResourceId(
552 com.android.internal.R.styleable.PreferenceActivity_layout,
553 com.android.internal.R.layout.preference_list_content);
554
555 mPreferenceHeaderItemResId = sa.getResourceId(
556 com.android.internal.R.styleable.PreferenceActivity_headerLayout,
557 com.android.internal.R.layout.preference_header_item);
558 mPreferenceHeaderRemoveEmptyIcon = sa.getBoolean(
559 com.android.internal.R.styleable.PreferenceActivity_headerRemoveIconIfEmpty,
560 false);
561
562 sa.recycle();
563
564 setContentView(layoutResId);
Freeman Ng19ea2e02010-03-25 15:09:00 -0700565
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700566 mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800567 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame);
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000568 mHeadersContainer = (ViewGroup) findViewById(com.android.internal.R.id.headers);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700569 boolean hidingHeaders = onIsHidingHeaders();
570 mSinglePane = hidingHeaders || !onIsMultiPane();
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700571 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
572 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Dianne Hackborne72f2372011-03-16 10:43:18 -0700573 int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
574 int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
Filip Pavlisb07c6f62017-02-17 19:23:04 +0000575 mActivityTitle = getTitle();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700576
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700577 if (savedInstanceState != null) {
578 // We are restarting from a previous saved state; used that to
579 // initialize, instead of starting fresh.
580 ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG);
581 if (headers != null) {
582 mHeaders.addAll(headers);
583 int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG,
Gilles Debunne39725ac2011-06-14 18:52:41 -0700584 (int) HEADER_ID_UNDEFINED);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700585 if (curHeader >= 0 && curHeader < mHeaders.size()) {
586 setSelectedHeader(mHeaders.get(curHeader));
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000587 } else if (!mSinglePane && initialFragment == null) {
588 switchToHeader(onGetInitialHeader());
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700589 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000590 } else {
591 // This will for instance hide breadcrumbs for single pane.
592 showBreadCrumbs(getTitle(), null);
593 }
594 } else {
595 if (!onIsHidingHeaders()) {
596 onBuildHeaders(mHeaders);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700597 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700598
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000599 if (initialFragment != null) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700600 switchToHeader(initialFragment, initialArguments);
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000601 } else if (!mSinglePane && mHeaders.size() > 0) {
602 switchToHeader(onGetInitialHeader());
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700603 }
604 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700605
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000606 if (mHeaders.size() > 0) {
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700607 setListAdapter(new HeaderAdapter(this, mHeaders, mPreferenceHeaderItemResId,
608 mPreferenceHeaderRemoveEmptyIcon));
Filip Pavliseadd5a92017-02-10 15:51:41 +0000609 if (!mSinglePane) {
610 getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
611 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000612 }
613
614 if (mSinglePane && initialFragment != null && initialTitle != 0) {
615 CharSequence initialTitleStr = getText(initialTitle);
616 CharSequence initialShortTitleStr = initialShortTitle != 0
617 ? getText(initialShortTitle) : null;
618 showBreadCrumbs(initialTitleStr, initialShortTitleStr);
619 }
620
621 if (mHeaders.size() == 0 && initialFragment == null) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700622 // If there are no headers, we are in the old "just show a screen
623 // of preferences" mode.
Amith Yamasani405c1af2011-05-26 13:08:25 -0700624 setContentView(com.android.internal.R.layout.preference_list_content_single);
Amith Yamasani8da35292010-11-05 09:15:51 -0700625 mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800626 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700627 mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
628 mPreferenceManager.setOnPreferenceTreeClickListener(this);
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000629 mHeadersContainer = null;
630 } else if (mSinglePane) {
631 // Single-pane so one of the header or prefs containers must be hidden.
632 if (initialFragment != null || mCurHeader != null) {
633 mHeadersContainer.setVisibility(View.GONE);
634 } else {
635 mPrefsContainer.setVisibility(View.GONE);
636 }
637
638 // This animates our manual transitions between headers and prefs panel in single-pane.
639 // It also comes last so we don't animate any initial layout changes done above.
640 ViewGroup container = (ViewGroup) findViewById(
641 com.android.internal.R.id.prefs_container);
642 container.setLayoutTransition(new LayoutTransition());
643 } else {
644 // Multi-pane
645 if (mHeaders.size() > 0 && mCurHeader != null) {
646 setSelectedHeader(mCurHeader);
647 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700648 }
649
Freeman Ng19ea2e02010-03-25 15:09:00 -0700650 // see if we should show Back/Next buttons
651 Intent intent = getIntent();
652 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
653
654 findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
655
656 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
657 backButton.setOnClickListener(new OnClickListener() {
658 public void onClick(View v) {
659 setResult(RESULT_CANCELED);
660 finish();
661 }
662 });
Freeman Ng09dbf182010-08-11 15:45:01 -0700663 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
664 skipButton.setOnClickListener(new OnClickListener() {
665 public void onClick(View v) {
666 setResult(RESULT_OK);
667 finish();
668 }
669 });
Freeman Ng19ea2e02010-03-25 15:09:00 -0700670 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
671 mNextButton.setOnClickListener(new OnClickListener() {
672 public void onClick(View v) {
673 setResult(RESULT_OK);
674 finish();
675 }
676 });
677
678 // set our various button parameters
679 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
680 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
681 if (TextUtils.isEmpty(buttonText)) {
682 mNextButton.setVisibility(View.GONE);
683 }
684 else {
685 mNextButton.setText(buttonText);
686 }
687 }
688 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
689 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
690 if (TextUtils.isEmpty(buttonText)) {
691 backButton.setVisibility(View.GONE);
692 }
693 else {
694 backButton.setText(buttonText);
695 }
696 }
Freeman Ng09dbf182010-08-11 15:45:01 -0700697 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
698 skipButton.setVisibility(View.VISIBLE);
699 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700700 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700701 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700702
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000703 @Override
704 public void onBackPressed() {
705 if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
706 && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
707 mCurHeader = null;
708
709 mPrefsContainer.setVisibility(View.GONE);
710 mHeadersContainer.setVisibility(View.VISIBLE);
Filip Pavlisb07c6f62017-02-17 19:23:04 +0000711 if (mActivityTitle != null) {
712 showBreadCrumbs(mActivityTitle, null);
713 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000714 getListView().clearChoices();
715 } else {
716 super.onBackPressed();
717 }
718 }
719
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700720 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700721 * Returns true if this activity is currently showing the header list.
722 */
723 public boolean hasHeaders() {
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000724 return mHeadersContainer != null && mHeadersContainer.getVisibility() == View.VISIBLE;
Dianne Hackborn291905e2010-08-17 15:17:15 -0700725 }
726
727 /**
Amith Yamasani423d48b2012-06-20 13:54:53 -0700728 * Returns the Header list
729 * @hide
730 */
Mathew Inwoodf2217132018-08-17 13:41:55 +0100731 @UnsupportedAppUsage
Amith Yamasani423d48b2012-06-20 13:54:53 -0700732 public List<Header> getHeaders() {
733 return mHeaders;
734 }
735
736 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700737 * Returns true if this activity is showing multiple panes -- the headers
738 * and a preference fragment.
739 */
740 public boolean isMultiPane() {
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000741 return !mSinglePane;
Dianne Hackborn291905e2010-08-17 15:17:15 -0700742 }
743
744 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700745 * Called to determine if the activity should run in multi-pane mode.
746 * The default implementation returns true if the screen is large
747 * enough.
748 */
749 public boolean onIsMultiPane() {
Amith Yamasani405c1af2011-05-26 13:08:25 -0700750 boolean preferMultiPane = getResources().getBoolean(
751 com.android.internal.R.bool.preferences_prefer_dual_pane);
752 return preferMultiPane;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700753 }
754
755 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700756 * Called to determine whether the header list should be hidden.
757 * The default implementation returns the
758 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
759 * This is set to false, for example, when the activity is being re-launched
760 * to show a particular preference activity.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700761 */
762 public boolean onIsHidingHeaders() {
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700763 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700764 }
765
766 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700767 * Called to determine the initial header to be shown. The default
768 * implementation simply returns the fragment of the first header. Note
769 * that the returned Header object does not actually need to exist in
770 * your header list -- whatever its fragment is will simply be used to
771 * show for the initial UI.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700772 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700773 public Header onGetInitialHeader() {
Dianne Hackborn19470ec2013-02-12 11:42:51 -0800774 for (int i=0; i<mHeaders.size(); i++) {
775 Header h = mHeaders.get(i);
776 if (h.fragment != null) {
777 return h;
778 }
779 }
780 throw new IllegalStateException("Must have at least one header with a fragment");
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700781 }
782
783 /**
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700784 * Called after the header list has been updated ({@link #onBuildHeaders}
785 * has been called and returned due to {@link #invalidateHeaders()}) to
786 * specify the header that should now be selected. The default implementation
787 * returns null to keep whatever header is currently selected.
788 */
789 public Header onGetNewHeader() {
790 return null;
791 }
792
793 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700794 * Called when the activity needs its list of headers build. By
795 * implementing this and adding at least one item to the list, you
796 * will cause the activity to run in its modern fragment mode. Note
797 * that this function may not always be called; for example, if the
798 * activity has been asked to display a particular fragment without
799 * the header list, there is no need to build the headers.
800 *
801 * <p>Typical implementations will use {@link #loadHeadersFromResource}
802 * to fill in the list from a resource.
803 *
804 * @param target The list in which to place the headers.
805 */
806 public void onBuildHeaders(List<Header> target) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700807 // Should be overloaded by subclasses
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700808 }
809
810 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700811 * Call when you need to change the headers being displayed. Will result
812 * in onBuildHeaders() later being called to retrieve the new list.
813 */
814 public void invalidateHeaders() {
815 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
816 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
817 }
818 }
819
820 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700821 * Parse the given XML file as a header description, adding each
822 * parsed Header into the target list.
823 *
824 * @param resid The XML resource to load and parse.
825 * @param target The list in which the parsed headers should be placed.
826 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700827 public void loadHeadersFromResource(@XmlRes int resid, List<Header> target) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700828 XmlResourceParser parser = null;
829 try {
830 parser = getResources().getXml(resid);
831 AttributeSet attrs = Xml.asAttributeSet(parser);
832
833 int type;
834 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
835 && type != XmlPullParser.START_TAG) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700836 // Parse next until start tag is found
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700837 }
838
839 String nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700840 if (!"preference-headers".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700841 throw new RuntimeException(
Dianne Hackborndef15372010-08-15 12:43:52 -0700842 "XML document must start with <preference-headers> tag; found"
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000843 + nodeName + " at " + parser.getPositionDescription());
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700844 }
845
Dianne Hackborndef15372010-08-15 12:43:52 -0700846 Bundle curBundle = null;
847
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700848 final int outerDepth = parser.getDepth();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700849 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000850 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700851 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
852 continue;
853 }
854
855 nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700856 if ("header".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700857 Header header = new Header();
858
Alan Viverettef878c292013-12-02 18:36:00 -0800859 TypedArray sa = obtainStyledAttributes(
860 attrs, com.android.internal.R.styleable.PreferenceHeader);
Amith Yamasanied13cde2010-09-17 16:56:47 -0700861 header.id = sa.getResourceId(
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700862 com.android.internal.R.styleable.PreferenceHeader_id,
863 (int)HEADER_ID_UNDEFINED);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800864 TypedValue tv = sa.peekValue(
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700865 com.android.internal.R.styleable.PreferenceHeader_title);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800866 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
867 if (tv.resourceId != 0) {
868 header.titleRes = tv.resourceId;
869 } else {
870 header.title = tv.string;
871 }
872 }
873 tv = sa.peekValue(
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700874 com.android.internal.R.styleable.PreferenceHeader_summary);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800875 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
876 if (tv.resourceId != 0) {
877 header.summaryRes = tv.resourceId;
878 } else {
879 header.summary = tv.string;
880 }
881 }
882 tv = sa.peekValue(
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700883 com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800884 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
885 if (tv.resourceId != 0) {
886 header.breadCrumbTitleRes = tv.resourceId;
887 } else {
888 header.breadCrumbTitle = tv.string;
889 }
890 }
891 tv = sa.peekValue(
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700892 com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800893 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
894 if (tv.resourceId != 0) {
895 header.breadCrumbShortTitleRes = tv.resourceId;
896 } else {
897 header.breadCrumbShortTitle = tv.string;
898 }
899 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700900 header.iconRes = sa.getResourceId(
901 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
902 header.fragment = sa.getString(
903 com.android.internal.R.styleable.PreferenceHeader_fragment);
904 sa.recycle();
905
Dianne Hackborndef15372010-08-15 12:43:52 -0700906 if (curBundle == null) {
907 curBundle = new Bundle();
908 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700909
910 final int innerDepth = parser.getDepth();
911 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000912 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700913 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
914 continue;
915 }
916
917 String innerNodeName = parser.getName();
918 if (innerNodeName.equals("extra")) {
919 getResources().parseBundleExtra("extra", attrs, curBundle);
920 XmlUtils.skipCurrentTag(parser);
921
922 } else if (innerNodeName.equals("intent")) {
923 header.intent = Intent.parseIntent(getResources(), parser, attrs);
924
925 } else {
926 XmlUtils.skipCurrentTag(parser);
927 }
928 }
929
Dianne Hackborndef15372010-08-15 12:43:52 -0700930 if (curBundle.size() > 0) {
931 header.fragmentArguments = curBundle;
932 curBundle = null;
933 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700934
Dianne Hackborndef15372010-08-15 12:43:52 -0700935 target.add(header);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700936 } else {
937 XmlUtils.skipCurrentTag(parser);
938 }
939 }
940
941 } catch (XmlPullParserException e) {
942 throw new RuntimeException("Error parsing headers", e);
943 } catch (IOException e) {
944 throw new RuntimeException("Error parsing headers", e);
945 } finally {
946 if (parser != null) parser.close();
947 }
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700948 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700949
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700950 /**
951 * Subclasses should override this method and verify that the given fragment is a valid type
Amith Yamasanic2be0d62013-09-23 11:16:28 -0700952 * to be attached to this activity. The default implementation returns <code>true</code> for
953 * apps built for <code>android:targetSdkVersion</code> older than
954 * {@link android.os.Build.VERSION_CODES#KITKAT}. For later versions, it will throw an exception.
Amith Yamasani20915daf2013-08-01 12:44:01 -0700955 * @param fragmentName the class name of the Fragment about to be attached to this activity.
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700956 * @return true if the fragment class name is valid for this Activity and false otherwise.
957 */
958 protected boolean isValidFragment(String fragmentName) {
Chet Haasee8222dd2013-09-05 07:44:18 -0700959 if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.KITKAT) {
Amith Yamasania5001b92013-09-06 14:18:00 -0700960 throw new RuntimeException(
961 "Subclasses of PreferenceActivity must override isValidFragment(String)"
Filip Pavlis61fc0d42017-01-06 18:15:51 +0000962 + " to verify that the Fragment class is valid! "
963 + this.getClass().getName()
964 + " has not checked if fragment " + fragmentName + " is valid.");
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700965 } else {
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700966 return true;
967 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800968 }
969
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700970 /**
971 * Set a footer that should be shown at the bottom of the header list.
972 */
973 public void setListFooter(View view) {
974 mListFooter.removeAllViews();
975 mListFooter.addView(view, new FrameLayout.LayoutParams(
976 FrameLayout.LayoutParams.MATCH_PARENT,
977 FrameLayout.LayoutParams.WRAP_CONTENT));
978 }
979
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800980 @Override
981 protected void onStop() {
982 super.onStop();
Freeman Ng19ea2e02010-03-25 15:09:00 -0700983
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700984 if (mPreferenceManager != null) {
985 mPreferenceManager.dispatchActivityStop();
986 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800987 }
988
989 @Override
990 protected void onDestroy() {
Hiroaki Kuriyama1ebf13e2012-11-16 18:46:49 +0900991 mHandler.removeMessages(MSG_BIND_PREFERENCES);
992 mHandler.removeMessages(MSG_BUILD_HEADERS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800993 super.onDestroy();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700994
995 if (mPreferenceManager != null) {
996 mPreferenceManager.dispatchActivityDestroy();
997 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800998 }
999
1000 @Override
1001 protected void onSaveInstanceState(Bundle outState) {
1002 super.onSaveInstanceState(outState);
1003
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001004 if (mHeaders.size() > 0) {
1005 outState.putParcelableArrayList(HEADERS_TAG, mHeaders);
1006 if (mCurHeader != null) {
1007 int index = mHeaders.indexOf(mCurHeader);
1008 if (index >= 0) {
1009 outState.putInt(CUR_HEADER_TAG, index);
1010 }
1011 }
1012 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001013
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001014 if (mPreferenceManager != null) {
1015 final PreferenceScreen preferenceScreen = getPreferenceScreen();
1016 if (preferenceScreen != null) {
1017 Bundle container = new Bundle();
1018 preferenceScreen.saveHierarchyState(container);
1019 outState.putBundle(PREFERENCES_TAG, container);
1020 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001021 }
1022 }
1023
1024 @Override
1025 protected void onRestoreInstanceState(Bundle state) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001026 if (mPreferenceManager != null) {
1027 Bundle container = state.getBundle(PREFERENCES_TAG);
1028 if (container != null) {
1029 final PreferenceScreen preferenceScreen = getPreferenceScreen();
1030 if (preferenceScreen != null) {
1031 preferenceScreen.restoreHierarchyState(container);
1032 mSavedInstanceState = state;
1033 return;
1034 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001035 }
1036 }
Adam Powelle7fea452010-03-18 14:51:39 -07001037
1038 // Only call this if we didn't save the instance state for later.
1039 // If we did save it, it will be restored when we bind the adapter.
1040 super.onRestoreInstanceState(state);
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001041
1042 if (!mSinglePane) {
1043 // Multi-pane.
1044 if (mCurHeader != null) {
1045 setSelectedHeader(mCurHeader);
1046 }
1047 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001048 }
1049
1050 @Override
1051 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1052 super.onActivityResult(requestCode, resultCode, data);
Freeman Ng19ea2e02010-03-25 15:09:00 -07001053
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001054 if (mPreferenceManager != null) {
1055 mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
1056 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001057 }
1058
1059 @Override
1060 public void onContentChanged() {
1061 super.onContentChanged();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001062
1063 if (mPreferenceManager != null) {
1064 postBindPreferences();
1065 }
1066 }
1067
1068 @Override
1069 protected void onListItemClick(ListView l, View v, int position, long id) {
Amith Yamasani49bdc162013-07-17 15:52:45 -07001070 if (!isResumed()) {
1071 return;
1072 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001073 super.onListItemClick(l, v, position, id);
1074
1075 if (mAdapter != null) {
Gilles Debunne39725ac2011-06-14 18:52:41 -07001076 Object item = mAdapter.getItem(position);
1077 if (item instanceof Header) onHeaderClick((Header) item, position);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001078 }
1079 }
1080
1081 /**
1082 * Called when the user selects an item in the header list. The default
Dianne Hackborne72f2372011-03-16 10:43:18 -07001083 * implementation will call either
1084 * {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001085 * or {@link #switchToHeader(Header)} as appropriate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001086 *
1087 * @param header The header that was selected.
1088 * @param position The header's position in the list.
1089 */
1090 public void onHeaderClick(Header header, int position) {
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001091 if (header.fragment != null) {
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001092 switchToHeader(header);
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001093 } else if (header.intent != null) {
1094 startActivity(header.intent);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001095 }
1096 }
1097
1098 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -07001099 * Called by {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} when
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001100 * in single-pane mode, to build an Intent to launch a new activity showing
1101 * the selected fragment. The default implementation constructs an Intent
1102 * that re-launches the current activity with the appropriate arguments to
1103 * display the fragment.
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001104 *
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001105 * @param fragmentName The name of the fragment to display.
1106 * @param args Optional arguments to supply to the fragment.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001107 * @param titleRes Optional resource ID of title to show for this item.
Gilles Debunne39725ac2011-06-14 18:52:41 -07001108 * @param shortTitleRes Optional resource ID of short title to show for this item.
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001109 * @return Returns an Intent that can be launched to display the given
1110 * fragment.
1111 */
Dianne Hackborne72f2372011-03-16 10:43:18 -07001112 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
Tor Norbye7b9c9122013-05-30 16:48:33 -07001113 @StringRes int titleRes, int shortTitleRes) {
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001114 Intent intent = new Intent(Intent.ACTION_MAIN);
1115 intent.setClass(this, getClass());
1116 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
1117 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
Dianne Hackborne72f2372011-03-16 10:43:18 -07001118 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, titleRes);
1119 intent.putExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, shortTitleRes);
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001120 intent.putExtra(EXTRA_NO_HEADERS, true);
1121 return intent;
1122 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001123
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001124 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -07001125 * Like {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
1126 * but uses a 0 titleRes.
1127 */
1128 public void startWithFragment(String fragmentName, Bundle args,
1129 Fragment resultTo, int resultRequestCode) {
1130 startWithFragment(fragmentName, args, resultTo, resultRequestCode, 0, 0);
1131 }
1132
1133 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001134 * Start a new instance of this activity, showing only the given
1135 * preference fragment. When launched in this mode, the header list
1136 * will be hidden and the given preference fragment will be instantiated
1137 * and fill the entire activity.
1138 *
1139 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001140 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001141 * @param resultTo Option fragment that should receive the result of
1142 * the activity launch.
1143 * @param resultRequestCode If resultTo is non-null, this is the request
1144 * code in which to report the result.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001145 * @param titleRes Resource ID of string to display for the title of
1146 * this set of preferences.
Gilles Debunne39725ac2011-06-14 18:52:41 -07001147 * @param shortTitleRes Resource ID of string to display for the short title of
Dianne Hackborne72f2372011-03-16 10:43:18 -07001148 * this set of preferences.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001149 */
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001150 public void startWithFragment(String fragmentName, Bundle args,
Tor Norbye7b9c9122013-05-30 16:48:33 -07001151 Fragment resultTo, int resultRequestCode, @StringRes int titleRes,
1152 @StringRes int shortTitleRes) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001153 Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001154 if (resultTo == null) {
1155 startActivity(intent);
1156 } else {
1157 resultTo.startActivityForResult(intent, resultRequestCode);
1158 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001159 }
1160
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001161 /**
1162 * Change the base title of the bread crumbs for the current preferences.
1163 * This will normally be called for you. See
1164 * {@link android.app.FragmentBreadCrumbs} for more information.
1165 */
1166 public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) {
1167 if (mFragmentBreadCrumbs == null) {
Amith Yamasani3e860402010-12-10 14:20:51 -08001168 View crumbs = findViewById(android.R.id.title);
1169 // For screens with a different kind of title, don't create breadcrumbs.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001170 try {
1171 mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs;
1172 } catch (ClassCastException e) {
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001173 setTitle(title);
Dianne Hackborne72f2372011-03-16 10:43:18 -07001174 return;
1175 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001176 if (mFragmentBreadCrumbs == null) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001177 if (title != null) {
1178 setTitle(title);
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001179 }
Dianne Hackborne72f2372011-03-16 10:43:18 -07001180 return;
Jim Millerc57406c2010-12-08 16:01:05 -08001181 }
Amith Yamasani3ec7bac2012-10-03 16:02:51 -07001182 if (mSinglePane) {
1183 mFragmentBreadCrumbs.setVisibility(View.GONE);
1184 // Hide the breadcrumb section completely for single-pane
1185 View bcSection = findViewById(com.android.internal.R.id.breadcrumb_section);
1186 if (bcSection != null) bcSection.setVisibility(View.GONE);
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001187 setTitle(title);
Amith Yamasani3ec7bac2012-10-03 16:02:51 -07001188 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001189 mFragmentBreadCrumbs.setMaxVisible(2);
1190 mFragmentBreadCrumbs.setActivity(this);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001191 }
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001192 if (mFragmentBreadCrumbs.getVisibility() != View.VISIBLE) {
1193 setTitle(title);
1194 } else {
1195 mFragmentBreadCrumbs.setTitle(title, shortTitle);
1196 mFragmentBreadCrumbs.setParentTitle(null, null, null);
1197 }
Amith Yamasanic9ecb732010-12-14 14:23:21 -08001198 }
1199
1200 /**
1201 * Should be called after onCreate to ensure that the breadcrumbs, if any, were created.
1202 * This prepends a title to the fragment breadcrumbs and attaches a listener to any clicks
1203 * on the parent entry.
1204 * @param title the title for the breadcrumb
1205 * @param shortTitle the short title for the breadcrumb
1206 */
1207 public void setParentTitle(CharSequence title, CharSequence shortTitle,
1208 OnClickListener listener) {
1209 if (mFragmentBreadCrumbs != null) {
1210 mFragmentBreadCrumbs.setParentTitle(title, shortTitle, listener);
1211 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001212 }
1213
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001214 void setSelectedHeader(Header header) {
1215 mCurHeader = header;
1216 int index = mHeaders.indexOf(header);
1217 if (index >= 0) {
1218 getListView().setItemChecked(index, true);
1219 } else {
1220 getListView().clearChoices();
1221 }
Dianne Hackborn34905a92011-07-21 17:30:07 -07001222 showBreadCrumbs(header);
1223 }
1224
1225 void showBreadCrumbs(Header header) {
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001226 if (header != null) {
Dianne Hackborn50ed8292010-12-03 12:30:21 -08001227 CharSequence title = header.getBreadCrumbTitle(getResources());
1228 if (title == null) title = header.getTitle(getResources());
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001229 if (title == null) title = getTitle();
Dianne Hackborn50ed8292010-12-03 12:30:21 -08001230 showBreadCrumbs(title, header.getBreadCrumbShortTitle(getResources()));
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001231 } else {
1232 showBreadCrumbs(getTitle(), null);
1233 }
1234 }
1235
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001236 private void switchToHeaderInner(String fragmentName, Bundle args) {
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001237 getFragmentManager().popBackStack(BACK_STACK_PREFS,
1238 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Amith Yamasani364ed4d2013-07-26 13:37:56 -07001239 if (!isValidFragment(fragmentName)) {
1240 throw new IllegalArgumentException("Invalid fragment for this activity: "
1241 + fragmentName);
1242 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001243
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001244 Fragment f = Fragment.instantiate(this, fragmentName, args);
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001245 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001246 transaction.setTransition(mSinglePane
1247 ? FragmentTransaction.TRANSIT_NONE
1248 : FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Chet Haase9ff82bf2010-10-05 14:30:51 -07001249 transaction.replace(com.android.internal.R.id.prefs, f);
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001250 transaction.commitAllowingStateLoss();
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001251
1252 if (mSinglePane && mPrefsContainer.getVisibility() == View.GONE) {
1253 // We are transitioning from headers to preferences panel in single-pane so we need
1254 // to hide headers and show the prefs container.
1255 mPrefsContainer.setVisibility(View.VISIBLE);
1256 mHeadersContainer.setVisibility(View.GONE);
1257 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001258 }
1259
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001260 /**
1261 * When in two-pane mode, switch the fragment pane to show the given
1262 * preference fragment.
1263 *
1264 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001265 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001266 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001267 public void switchToHeader(String fragmentName, Bundle args) {
Brian Attwell02bc31e2014-06-16 17:06:13 -07001268 Header selectedHeader = null;
1269 for (int i = 0; i < mHeaders.size(); i++) {
1270 if (fragmentName.equals(mHeaders.get(i).fragment)) {
1271 selectedHeader = mHeaders.get(i);
1272 break;
1273 }
1274 }
1275 setSelectedHeader(selectedHeader);
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001276 switchToHeaderInner(fragmentName, args);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001277 }
1278
Andrew Stadleraa904f42010-09-02 14:50:08 -07001279 /**
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001280 * When in two-pane mode, switch to the fragment pane to show the given
1281 * preference fragment.
1282 *
1283 * @param header The new header to display.
1284 */
1285 public void switchToHeader(Header header) {
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001286 if (mCurHeader == header) {
1287 // This is the header we are currently displaying. Just make sure
1288 // to pop the stack up to its root state.
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001289 getFragmentManager().popBackStack(BACK_STACK_PREFS,
1290 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001291 } else {
Dianne Hackborn19470ec2013-02-12 11:42:51 -08001292 if (header.fragment == null) {
1293 throw new IllegalStateException("can't switch to header that has no fragment");
1294 }
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001295 switchToHeaderInner(header.fragment, header.fragmentArguments);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001296 setSelectedHeader(header);
1297 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001298 }
1299
1300 Header findBestMatchingHeader(Header cur, ArrayList<Header> from) {
1301 ArrayList<Header> matches = new ArrayList<Header>();
1302 for (int j=0; j<from.size(); j++) {
1303 Header oh = from.get(j);
1304 if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) {
1305 // Must be this one.
1306 matches.clear();
1307 matches.add(oh);
1308 break;
1309 }
1310 if (cur.fragment != null) {
1311 if (cur.fragment.equals(oh.fragment)) {
1312 matches.add(oh);
1313 }
1314 } else if (cur.intent != null) {
1315 if (cur.intent.equals(oh.intent)) {
1316 matches.add(oh);
1317 }
1318 } else if (cur.title != null) {
1319 if (cur.title.equals(oh.title)) {
1320 matches.add(oh);
1321 }
1322 }
1323 }
1324 final int NM = matches.size();
1325 if (NM == 1) {
1326 return matches.get(0);
1327 } else if (NM > 1) {
1328 for (int j=0; j<NM; j++) {
1329 Header oh = matches.get(j);
1330 if (cur.fragmentArguments != null &&
1331 cur.fragmentArguments.equals(oh.fragmentArguments)) {
1332 return oh;
1333 }
1334 if (cur.extras != null && cur.extras.equals(oh.extras)) {
1335 return oh;
1336 }
1337 if (cur.title != null && cur.title.equals(oh.title)) {
1338 return oh;
1339 }
1340 }
1341 }
1342 return null;
1343 }
1344
1345 /**
Andrew Stadleraa904f42010-09-02 14:50:08 -07001346 * Start a new fragment.
1347 *
1348 * @param fragment The fragment to start
1349 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1350 * the current fragment will be replaced.
1351 */
1352 public void startPreferenceFragment(Fragment fragment, boolean push) {
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001353 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001354 transaction.replace(com.android.internal.R.id.prefs, fragment);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001355 if (push) {
Chet Haase9ff82bf2010-10-05 14:30:51 -07001356 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001357 transaction.addToBackStack(BACK_STACK_PREFS);
Chet Haase9ff82bf2010-10-05 14:30:51 -07001358 } else {
Dianne Hackborn327fbd22011-01-17 14:38:50 -08001359 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001360 }
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001361 transaction.commitAllowingStateLoss();
Andrew Stadleraa904f42010-09-02 14:50:08 -07001362 }
1363
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001364 /**
Amith Yamasania47372c2013-08-05 10:55:28 -07001365 * Start a new fragment containing a preference panel. If the preferences
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001366 * are being displayed in multi-pane mode, the given fragment class will
1367 * be instantiated and placed in the appropriate pane. If running in
1368 * single-pane mode, a new activity will be launched in which to show the
1369 * fragment.
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001370 *
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001371 * @param fragmentClass Full name of the class implementing the fragment.
1372 * @param args Any desired arguments to supply to the fragment.
1373 * @param titleRes Optional resource identifier of the title of this
1374 * fragment.
1375 * @param titleText Optional text of the title of this fragment.
1376 * @param resultTo Optional fragment that result data should be sent to.
1377 * If non-null, resultTo.onActivityResult() will be called when this
1378 * preference panel is done. The launched panel must use
1379 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
1380 * @param resultRequestCode If resultTo is non-null, this is the caller's
Tor Norbye7b9c9122013-05-30 16:48:33 -07001381 * request code to be received with the result.
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001382 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07001383 public void startPreferencePanel(String fragmentClass, Bundle args, @StringRes int titleRes,
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001384 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001385 Fragment f = Fragment.instantiate(this, fragmentClass, args);
1386 if (resultTo != null) {
1387 f.setTargetFragment(resultTo, resultRequestCode);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001388 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001389 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1390 transaction.replace(com.android.internal.R.id.prefs, f);
1391 if (titleRes != 0) {
1392 transaction.setBreadCrumbTitle(titleRes);
1393 } else if (titleText != null) {
1394 transaction.setBreadCrumbTitle(titleText);
1395 }
1396 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1397 transaction.addToBackStack(BACK_STACK_PREFS);
1398 transaction.commitAllowingStateLoss();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001399 }
Amith Yamasani364ed4d2013-07-26 13:37:56 -07001400
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001401 /**
1402 * Called by a preference panel fragment to finish itself.
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001403 *
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001404 * @param caller The fragment that is asking to be finished.
1405 * @param resultCode Optional result code to send back to the original
1406 * launching fragment.
1407 * @param resultData Optional result data to send back to the original
1408 * launching fragment.
1409 */
1410 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001411 // TODO: be smarter about popping the stack.
1412 onBackPressed();
1413 if (caller != null) {
1414 if (caller.getTargetFragment() != null) {
1415 caller.getTargetFragment().onActivityResult(caller.getTargetRequestCode(),
1416 resultCode, resultData);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001417 }
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001418 }
1419 }
Filip Pavlis61fc0d42017-01-06 18:15:51 +00001420
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -07001421 @Override
1422 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001423 startPreferencePanel(pref.getFragment(), pref.getExtras(), pref.getTitleRes(),
1424 pref.getTitle(), null, 0);
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -07001425 return true;
1426 }
1427
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001428 /**
1429 * Posts a message to bind the preferences to the list view.
1430 * <p>
1431 * Binding late is preferred as any custom preference types created in
1432 * {@link #onCreate(Bundle)} are able to have their views recycled.
1433 */
Mathew Inwoodf2217132018-08-17 13:41:55 +01001434 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001435 private void postBindPreferences() {
1436 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
1437 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
1438 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001439
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001440 private void bindPreferences() {
1441 final PreferenceScreen preferenceScreen = getPreferenceScreen();
1442 if (preferenceScreen != null) {
1443 preferenceScreen.bind(getListView());
Adam Powelle7fea452010-03-18 14:51:39 -07001444 if (mSavedInstanceState != null) {
1445 super.onRestoreInstanceState(mSavedInstanceState);
1446 mSavedInstanceState = null;
1447 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001448 }
1449 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001450
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001451 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001452 * Returns the {@link PreferenceManager} used by this activity.
1453 * @return The {@link PreferenceManager}.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001454 *
1455 * @deprecated This function is not relevant for a modern fragment-based
1456 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001457 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001458 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001459 public PreferenceManager getPreferenceManager() {
1460 return mPreferenceManager;
1461 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001462
Mathew Inwoodf2217132018-08-17 13:41:55 +01001463 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001464 private void requirePreferenceManager() {
1465 if (mPreferenceManager == null) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001466 if (mAdapter == null) {
1467 throw new RuntimeException("This should be called after super.onCreate.");
1468 }
1469 throw new RuntimeException(
1470 "Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001471 }
1472 }
1473
1474 /**
1475 * Sets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001476 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001477 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001478 *
1479 * @deprecated This function is not relevant for a modern fragment-based
1480 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001481 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001482 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001483 public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001484 requirePreferenceManager();
1485
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001486 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
1487 postBindPreferences();
1488 CharSequence title = getPreferenceScreen().getTitle();
1489 // Set the title of the activity
1490 if (title != null) {
1491 setTitle(title);
1492 }
1493 }
1494 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001495
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001496 /**
1497 * Gets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001498 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001499 * @return The {@link PreferenceScreen} that is the root of the preference
1500 * hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001501 *
1502 * @deprecated This function is not relevant for a modern fragment-based
1503 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001504 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001505 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001506 public PreferenceScreen getPreferenceScreen() {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001507 if (mPreferenceManager != null) {
1508 return mPreferenceManager.getPreferenceScreen();
1509 }
1510 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001511 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001512
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001513 /**
1514 * Adds preferences from activities that match the given {@link Intent}.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001515 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001516 * @param intent The {@link Intent} to query activities.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001517 *
1518 * @deprecated This function is not relevant for a modern fragment-based
1519 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001520 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001521 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001522 public void addPreferencesFromIntent(Intent intent) {
1523 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -07001524
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001525 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
1526 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001527
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001528 /**
1529 * Inflates the given XML resource and adds the preference hierarchy to the current
1530 * preference hierarchy.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001531 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001532 * @param preferencesResId The XML resource ID to inflate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001533 *
1534 * @deprecated This function is not relevant for a modern fragment-based
1535 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001536 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001537 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001538 public void addPreferencesFromResource(int preferencesResId) {
1539 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -07001540
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001541 setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
1542 getPreferenceScreen()));
1543 }
1544
1545 /**
1546 * {@inheritDoc}
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 boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
1553 return false;
1554 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001555
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001556 /**
1557 * Finds a {@link Preference} based on its key.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001558 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001559 * @param key The key of the preference to retrieve.
1560 * @return The {@link Preference} with the key, or null.
1561 * @see PreferenceGroup#findPreference(CharSequence)
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001562 *
1563 * @deprecated This function is not relevant for a modern fragment-based
1564 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001565 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001566 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001567 public Preference findPreference(CharSequence key) {
Freeman Ng19ea2e02010-03-25 15:09:00 -07001568
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001569 if (mPreferenceManager == null) {
1570 return null;
1571 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001572
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001573 return mPreferenceManager.findPreference(key);
1574 }
1575
1576 @Override
1577 protected void onNewIntent(Intent intent) {
1578 if (mPreferenceManager != null) {
1579 mPreferenceManager.dispatchNewIntent(intent);
1580 }
1581 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001582
1583 // give subclasses access to the Next button
1584 /** @hide */
1585 protected boolean hasNextButton() {
1586 return mNextButton != null;
1587 }
1588 /** @hide */
1589 protected Button getNextButton() {
1590 return mNextButton;
1591 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001592}