blob: 34c57043cbbd78272e264439ee100fc51758cea3 [file] [log] [blame]
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001/*
2 * Copyright (C) 2014 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 com.android.settings;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
21import android.accounts.OnAccountsUpdateListener;
22import android.app.ActionBar;
23import android.app.Activity;
24import android.app.Fragment;
25import android.app.FragmentManager;
26import android.app.FragmentTransaction;
27import android.app.AlertDialog;
28import android.app.Dialog;
29import android.app.DialogFragment;
30import android.app.admin.DevicePolicyManager;
31import android.content.BroadcastReceiver;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.SharedPreferences;
36import android.content.pm.ActivityInfo;
37import android.content.pm.PackageManager;
38import android.content.pm.PackageManager.NameNotFoundException;
39import android.content.pm.ResolveInfo;
40import android.content.res.Configuration;
41import android.content.res.Resources;
42import android.content.res.TypedArray;
43import android.content.res.XmlResourceParser;
44import android.graphics.drawable.Drawable;
45import android.nfc.NfcAdapter;
46import android.os.Bundle;
47import android.os.Handler;
48import android.os.INetworkManagementService;
49import android.os.Message;
50import android.os.Parcel;
51import android.os.Parcelable;
52import android.os.RemoteException;
53import android.os.ServiceManager;
54import android.os.UserHandle;
55import android.os.UserManager;
56import android.preference.Preference;
57import android.preference.PreferenceFragment;
58import android.preference.PreferenceManager;
59import android.preference.PreferenceScreen;
60import android.support.v4.app.ActionBarDrawerToggle;
61import android.support.v4.widget.DrawerLayout;
62import android.text.TextUtils;
63import android.util.AttributeSet;
64import android.util.Log;
65import android.util.Pair;
66import android.util.TypedValue;
67import android.util.Xml;
Fabrice Di Megliodca28062014-02-21 17:42:56 -080068import android.view.ContextThemeWrapper;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080069import android.view.LayoutInflater;
70import android.view.MenuItem;
71import android.view.View;
72import android.view.View.OnClickListener;
73import android.view.ViewGroup;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +000074import android.widget.AbsListView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080075import android.widget.AdapterView;
76import android.widget.ArrayAdapter;
77import android.widget.Button;
78import android.widget.ImageButton;
79import android.widget.ImageView;
80import android.widget.ListView;
81import android.widget.Switch;
82import android.widget.TextView;
83
84import com.android.internal.util.ArrayUtils;
85import com.android.internal.util.XmlUtils;
86import com.android.settings.accessibility.AccessibilitySettings;
87import com.android.settings.accessibility.CaptionPropertiesFragment;
88import com.android.settings.accounts.AccountSyncSettings;
89import com.android.settings.accounts.AuthenticatorHelper;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080090import com.android.settings.accounts.ManageAccountsSettings;
91import com.android.settings.applications.ManageApplications;
92import com.android.settings.applications.ProcessStatsUi;
93import com.android.settings.bluetooth.BluetoothEnabler;
94import com.android.settings.bluetooth.BluetoothSettings;
95import com.android.settings.dashboard.DashboardSummary;
96import com.android.settings.deviceinfo.Memory;
97import com.android.settings.deviceinfo.UsbSettings;
98import com.android.settings.fuelgauge.PowerUsageSummary;
99import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
100import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
101import com.android.settings.inputmethod.SpellCheckersSettings;
102import com.android.settings.inputmethod.UserDictionaryList;
103import com.android.settings.location.LocationSettings;
104import com.android.settings.nfc.AndroidBeam;
105import com.android.settings.nfc.PaymentSettings;
106import com.android.settings.print.PrintJobSettingsFragment;
107import com.android.settings.print.PrintSettingsFragment;
108import com.android.settings.tts.TextToSpeechSettings;
109import com.android.settings.users.UserSettings;
110import com.android.settings.vpn2.VpnSettings;
111import com.android.settings.wfd.WifiDisplaySettings;
112import com.android.settings.wifi.AdvancedWifiSettings;
113import com.android.settings.wifi.WifiEnabler;
114import com.android.settings.wifi.WifiSettings;
115import com.android.settings.wifi.p2p.WifiP2pSettings;
116import org.xmlpull.v1.XmlPullParser;
117import org.xmlpull.v1.XmlPullParserException;
118
119import java.io.IOException;
120import java.util.ArrayList;
121import java.util.Collections;
122import java.util.Comparator;
123import java.util.HashMap;
124import java.util.List;
125
126public class SettingsActivity extends Activity
127 implements PreferenceManager.OnPreferenceTreeClickListener,
128 PreferenceFragment.OnPreferenceStartFragmentCallback,
129 ButtonBarHandler, OnAccountsUpdateListener, FragmentManager.OnBackStackChangedListener {
130
131 private static final String LOG_TAG = "Settings";
132
133 // Constants for state save/restore
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800134 private static final String SAVE_KEY_HEADERS_TAG = ":settings:headers";
135 private static final String SAVE_KEY_CURRENT_HEADER_TAG = ":settings:cur_header";
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800136 private static final String SAVE_KEY_TITLES_TAG = ":settings:titles";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800137
138 /**
139 * When starting this activity, the invoking Intent can contain this extra
140 * string to specify which fragment should be initially displayed.
141 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
142 * will call isValidFragment() to confirm that the fragment class name is valid for this
143 * activity.
144 */
145 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
146
147 /**
148 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
149 * this extra can also be specified to supply a Bundle of arguments to pass
150 * to that fragment when it is instantiated during the initial creation
151 * of the activity.
152 */
153 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
154
155 /**
156 * When starting this activity, the invoking Intent can contain this extra
157 * boolean that the header list should not be displayed. This is most often
158 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
159 * the activity to display a specific fragment that the user has navigated
160 * to.
161 */
162 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
163
164 // extras that allow any preference activity to be launched as part of a wizard
165
166 // show Back and Next buttons? takes boolean parameter
167 // Back will then return RESULT_CANCELED and Next RESULT_OK
168 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
169
170 // add a Skip button?
171 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
172
173 // specify custom text for the Back or Next buttons, or cause a button to not appear
174 // at all by setting it to null
175 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
176 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
177
178 /**
179 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
180 * this extra can also be specify to supply the title to be shown for
181 * that fragment.
182 */
183 protected static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
184
185 private static final String BACK_STACK_PREFS = ":settings:prefs";
186
187 private static final String META_DATA_KEY_HEADER_ID =
188 "com.android.settings.TOP_LEVEL_HEADER_ID";
189
190 private static final String META_DATA_KEY_FRAGMENT_CLASS =
191 "com.android.settings.FRAGMENT_CLASS";
192
193 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
194
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800195 private static boolean sShowNoHomeNotice = false;
196
197 private String mFragmentClass;
198 private int mTopLevelHeaderId;
199 private Header mFirstHeader;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800200 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800201 private Header mCurrentHeader;
202
203 // Show only these settings for restricted users
204 private int[] SETTINGS_FOR_RESTRICTED = {
205 R.id.wireless_section,
206 R.id.wifi_settings,
207 R.id.bluetooth_settings,
208 R.id.data_usage_settings,
209 R.id.wireless_settings,
210 R.id.device_section,
211 R.id.sound_settings,
212 R.id.display_settings,
213 R.id.storage_settings,
214 R.id.application_settings,
215 R.id.battery_settings,
216 R.id.personal_section,
217 R.id.location_settings,
218 R.id.security_settings,
219 R.id.language_settings,
220 R.id.user_settings,
221 R.id.account_settings,
222 R.id.account_add,
223 R.id.system_section,
224 R.id.date_time_settings,
225 R.id.about_settings,
226 R.id.accessibility_settings,
227 R.id.print_settings,
228 R.id.nfc_payment_settings,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800229 R.id.home_settings,
230 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800231 };
232
233 private static final String[] ENTRY_FRAGMENTS = {
234 WirelessSettings.class.getName(),
235 WifiSettings.class.getName(),
236 AdvancedWifiSettings.class.getName(),
237 BluetoothSettings.class.getName(),
238 TetherSettings.class.getName(),
239 WifiP2pSettings.class.getName(),
240 VpnSettings.class.getName(),
241 DateTimeSettings.class.getName(),
242 LocalePicker.class.getName(),
243 InputMethodAndLanguageSettings.class.getName(),
244 SpellCheckersSettings.class.getName(),
245 UserDictionaryList.class.getName(),
246 UserDictionarySettings.class.getName(),
247 SoundSettings.class.getName(),
248 DisplaySettings.class.getName(),
249 DeviceInfoSettings.class.getName(),
250 ManageApplications.class.getName(),
251 ProcessStatsUi.class.getName(),
252 NotificationStation.class.getName(),
253 LocationSettings.class.getName(),
254 SecuritySettings.class.getName(),
255 PrivacySettings.class.getName(),
256 DeviceAdminSettings.class.getName(),
257 AccessibilitySettings.class.getName(),
258 CaptionPropertiesFragment.class.getName(),
259 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
260 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
261 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
262 TextToSpeechSettings.class.getName(),
263 Memory.class.getName(),
264 DevelopmentSettings.class.getName(),
265 UsbSettings.class.getName(),
266 AndroidBeam.class.getName(),
267 WifiDisplaySettings.class.getName(),
268 PowerUsageSummary.class.getName(),
269 AccountSyncSettings.class.getName(),
270 CryptKeeperSettings.class.getName(),
271 DataUsageSummary.class.getName(),
272 DreamSettings.class.getName(),
273 UserSettings.class.getName(),
274 NotificationAccessSettings.class.getName(),
275 ManageAccountsSettings.class.getName(),
276 PrintSettingsFragment.class.getName(),
277 PrintJobSettingsFragment.class.getName(),
278 TrustedCredentialsSettings.class.getName(),
279 PaymentSettings.class.getName(),
280 KeyboardLayoutPickerFragment.class.getName(),
Fabrice Di Meglioe6c9a5d2014-02-11 19:03:27 -0800281 DashboardSummary.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800282 };
283
284 private SharedPreferences mDevelopmentPreferences;
285 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
286
287 // TODO: Update Call Settings based on airplane mode state.
288
289 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
290
291 private AuthenticatorHelper mAuthenticatorHelper;
292 private boolean mListeningToAccountUpdates;
293
294 private Button mNextButton;
295
296 private boolean mBatteryPresent = true;
297 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
298
299 @Override
300 public void onReceive(Context context, Intent intent) {
301 String action = intent.getAction();
302 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
303 boolean batteryPresent = Utils.isBatteryPresent(intent);
304
305 if (mBatteryPresent != batteryPresent) {
306 mBatteryPresent = batteryPresent;
307 invalidateHeaders();
308 }
309 }
310 }
311 };
312
313 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800314 private HeaderAdapter mHeaderAdapter;
315
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800316 static private class TitlePair extends Pair<Integer, CharSequence> implements Parcelable {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800317
318 public TitlePair(Integer first, CharSequence second) {
319 super(first, second);
320 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800321
322 @Override
323 public int describeContents() {
324 return 0;
325 }
326
327 @Override
328 public void writeToParcel(Parcel dest, int flags) {
329 dest.writeInt(first);
330 TextUtils.writeToParcel(second, dest, flags);
331 }
332
333 TitlePair(Parcel in) {
334 super(in.readInt(), TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
335 }
336
337 public static final Creator<TitlePair> CREATOR = new Creator<TitlePair>() {
338 public TitlePair createFromParcel(Parcel source) {
339 return new TitlePair(source);
340 }
341 public TitlePair[] newArray(int size) {
342 return new TitlePair[size];
343 }
344 };
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800345 }
346
347 private final ArrayList<TitlePair> mTitleStack = new ArrayList<TitlePair>();
348
349 private DrawerLayout mDrawerLayout;
350 private ListView mDrawer;
351 private ActionBarDrawerToggle mDrawerToggle;
352 private ActionBar mActionBar;
353
354 private static final int MSG_BUILD_HEADERS = 1;
355 private Handler mHandler = new Handler() {
356 @Override
357 public void handleMessage(Message msg) {
358 switch (msg.what) {
359 case MSG_BUILD_HEADERS: {
360 mHeaders.clear();
361 onBuildHeaders(mHeaders);
362 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800363 if (mCurrentHeader != null) {
364 Header mappedHeader = findBestMatchingHeader(mCurrentHeader, mHeaders);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800365 if (mappedHeader != null) {
366 setSelectedHeader(mappedHeader);
367 }
368 }
369 } break;
370 }
371 }
372 };
373
374 @Override
375 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
376 // Override the fragment title for Wallpaper settings
377 int titleRes = pref.getTitleRes();
378 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
379 titleRes = R.string.wallpaper_settings_fragment_title;
380 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
381 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
382 if (UserManager.get(this).isLinkedUser()) {
383 titleRes = R.string.profile_info_settings_title;
384 } else {
385 titleRes = R.string.user_info_settings_title;
386 }
387 }
388 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
389 null, 0);
390 return true;
391 }
392
393 @Override
394 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
395 return false;
396 }
397
398 private class DrawerListener implements DrawerLayout.DrawerListener {
399 @Override
400 public void onDrawerOpened(View drawerView) {
401 mDrawerToggle.onDrawerOpened(drawerView);
402 }
403
404 @Override
405 public void onDrawerClosed(View drawerView) {
406 mDrawerToggle.onDrawerClosed(drawerView);
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800407 // Cannot process clicks when the App is finishing
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800408 if (isFinishing() || mSelectedHeader == null) {
409 return;
410 }
411 switchToHeader(mSelectedHeader, false);
412 mSelectedHeader = null;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800413 }
414
415 @Override
416 public void onDrawerSlide(View drawerView, float slideOffset) {
417 mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
418 }
419
420 @Override
421 public void onDrawerStateChanged(int newState) {
422 mDrawerToggle.onDrawerStateChanged(newState);
423 }
424 }
425
426 private class DrawerItemClickListener implements ListView.OnItemClickListener {
427 @Override
428 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
429 mDrawerLayout.closeDrawer(mDrawer);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000430 onListItemClick((ListView)parent, view, position, id);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800431 }
432 }
433
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800434 private Header findBestMatchingHeader(Header current, ArrayList<Header> from) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800435 ArrayList<Header> matches = new ArrayList<Header>();
436 for (int j=0; j<from.size(); j++) {
437 Header oh = from.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800438 if (current == oh || (current.id != HEADER_ID_UNDEFINED && current.id == oh.id)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800439 // Must be this one.
440 matches.clear();
441 matches.add(oh);
442 break;
443 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800444 if (current.fragment != null) {
445 if (current.fragment.equals(oh.fragment)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800446 matches.add(oh);
447 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800448 } else if (current.intent != null) {
449 if (current.intent.equals(oh.intent)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800450 matches.add(oh);
451 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800452 } else if (current.title != null) {
453 if (current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800454 matches.add(oh);
455 }
456 }
457 }
458 final int NM = matches.size();
459 if (NM == 1) {
460 return matches.get(0);
461 } else if (NM > 1) {
462 for (int j=0; j<NM; j++) {
463 Header oh = matches.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800464 if (current.fragmentArguments != null &&
465 current.fragmentArguments.equals(oh.fragmentArguments)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800466 return oh;
467 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800468 if (current.extras != null && current.extras.equals(oh.extras)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800469 return oh;
470 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800471 if (current.title != null && current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800472 return oh;
473 }
474 }
475 }
476 return null;
477 }
478
479 private void invalidateHeaders() {
480 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
481 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
482 }
483 }
484
485 @Override
486 protected void onPostCreate(Bundle savedInstanceState) {
487 super.onPostCreate(savedInstanceState);
488
489 // Sync the toggle state after onRestoreInstanceState has occurred.
490 if (mDrawerToggle != null) {
491 mDrawerToggle.syncState();
492 }
493 }
494
495 @Override
496 public void onConfigurationChanged(Configuration newConfig) {
497 super.onConfigurationChanged(newConfig);
498 if (mDrawerToggle != null) {
499 mDrawerToggle.onConfigurationChanged(newConfig);
500 }
501 }
502
503 @Override
504 public boolean onOptionsItemSelected(MenuItem item) {
505 if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
506 return true;
507 }
508 return super.onOptionsItemSelected(item);
509 }
510
511 @Override
512 protected void onCreate(Bundle savedInstanceState) {
513 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
514 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
515 }
516
517 mAuthenticatorHelper = new AuthenticatorHelper();
518 mAuthenticatorHelper.updateAuthDescriptions(this);
519 mAuthenticatorHelper.onAccountsUpdated(this, null);
520
521 DevicePolicyManager dpm =
522 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
Fabrice Di Megliodca28062014-02-21 17:42:56 -0800523
524 // As the Settings Theme is now Holo Light, the primary text color is "Black" ... but
525 // we want the text color of the Drawer items to be "White", so use the inverse Theme (Holo)
526 // for the Header adapter (and thus making the TextView appearance to have a white color.
527 Context headersContext = new ContextThemeWrapper(this,
528 com.android.internal.R.style.Theme_Holo);
529
530 mHeaderAdapter= new HeaderAdapter(headersContext, getHeaders(), mAuthenticatorHelper, dpm);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800531
532 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
533 Context.MODE_PRIVATE);
534
535 getMetaData();
536
537 super.onCreate(savedInstanceState);
538
539 setContentView(R.layout.settings_main);
540
541 getFragmentManager().addOnBackStackChangedListener(this);
542
543 mActionBar = getActionBar();
544 if (mActionBar != null) {
545 mActionBar.setDisplayHomeAsUpEnabled(true);
546 mActionBar.setHomeButtonEnabled(true);
547
548 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800549
550 mDrawer = (ListView) findViewById(R.id.headers_drawer);
551 mDrawer.setAdapter(mHeaderAdapter);
552 mDrawer.setOnItemClickListener(new DrawerItemClickListener());
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000553 mDrawer.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800554
555 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
556 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
557 }
558
559 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
560 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
561
562 if (savedInstanceState != null) {
563 // We are restarting from a previous saved state; used that to
564 // initialize, instead of starting fresh.
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800565
566 ArrayList<TitlePair> titles =
567 savedInstanceState.getParcelableArrayList(SAVE_KEY_TITLES_TAG);
568 if (titles != null) {
569 mTitleStack.addAll(titles);
570 }
571 final int lastTitle = mTitleStack.size() - 1;
572 if (lastTitle >= 0) {
573 final TitlePair last = mTitleStack.get(lastTitle);
574 setTitleFromPair(last);
575 }
576
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800577 ArrayList<Header> headers =
578 savedInstanceState.getParcelableArrayList(SAVE_KEY_HEADERS_TAG);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800579 if (headers != null) {
580 mHeaders.addAll(headers);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800581 int curHeader = savedInstanceState.getInt(SAVE_KEY_CURRENT_HEADER_TAG,
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800582 (int) HEADER_ID_UNDEFINED);
583 if (curHeader >= 0 && curHeader < mHeaders.size()) {
584 setSelectedHeader(mHeaders.get(curHeader));
585 }
586 }
587
588 } else {
589 if (initialFragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000590 // If we are just showing a fragment, we want to run in
591 // new fragment mode, but don't need to compute and show
592 // the headers.
593 switchToHeader(initialFragment, initialArguments, true);
594
595 final int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
596 if (initialTitle != 0) {
597 setTitle(getText(initialTitle));
598 }
599 } else {
600 // We need to try to build the headers.
601 onBuildHeaders(mHeaders);
602
603 // If there are headers, then at this point we need to show
604 // them and, depending on the screen, we may also show in-line
605 // the currently selected preference fragment.
606 if (mHeaders.size() > 0) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800607 Header h = onGetInitialHeader();
608 switchToHeader(h, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000609 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800610 }
611 }
612
613 // see if we should show Back/Next buttons
614 Intent intent = getIntent();
615 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
616
617 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
618 if (buttonBar != null) {
619 buttonBar.setVisibility(View.VISIBLE);
620
621 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
622 backButton.setOnClickListener(new OnClickListener() {
623 public void onClick(View v) {
624 setResult(RESULT_CANCELED);
625 finish();
626 }
627 });
628 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
629 skipButton.setOnClickListener(new OnClickListener() {
630 public void onClick(View v) {
631 setResult(RESULT_OK);
632 finish();
633 }
634 });
635 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
636 mNextButton.setOnClickListener(new OnClickListener() {
637 public void onClick(View v) {
638 setResult(RESULT_OK);
639 finish();
640 }
641 });
642
643 // set our various button parameters
644 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
645 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
646 if (TextUtils.isEmpty(buttonText)) {
647 mNextButton.setVisibility(View.GONE);
648 }
649 else {
650 mNextButton.setText(buttonText);
651 }
652 }
653 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
654 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
655 if (TextUtils.isEmpty(buttonText)) {
656 backButton.setVisibility(View.GONE);
657 }
658 else {
659 backButton.setText(buttonText);
660 }
661 }
662 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
663 skipButton.setVisibility(View.VISIBLE);
664 }
665 }
666 }
667
668 if (!onIsHidingHeaders()) {
669 highlightHeader(mTopLevelHeaderId);
670 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800671 }
672
673 @Override
674 public void onBackStackChanged() {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000675 final int count = getFragmentManager().getBackStackEntryCount() + 1;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800676 TitlePair pair = null;
677 int last;
678 while (mTitleStack.size() > count) {
679 last = mTitleStack.size() - 1;
680 pair = mTitleStack.remove(last);
681 }
682 // Check if we go back
683 if (pair != null) {
684 int size = mTitleStack.size();
685 if (size > 0) {
686 last = mTitleStack.size() - 1;
687 pair = mTitleStack.get(last);
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800688 setTitleFromPair(pair);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800689 }
690 }
691 }
692
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800693 private void setTitleFromPair(TitlePair pair) {
694 final CharSequence title;
695 if (pair.first > 0) {
696 title = getText(pair.first);
697 } else {
698 title = pair.second;
699 }
700 setTitle(title);
701 }
702
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800703 /**
704 * Returns the Header list
705 */
706 private List<Header> getHeaders() {
707 return mHeaders;
708 }
709
710 @Override
711 protected void onSaveInstanceState(Bundle outState) {
712 super.onSaveInstanceState(outState);
713
714 if (mHeaders.size() > 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800715 outState.putParcelableArrayList(SAVE_KEY_HEADERS_TAG, mHeaders);
716 if (mCurrentHeader != null) {
717 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800718 if (index >= 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800719 outState.putInt(SAVE_KEY_CURRENT_HEADER_TAG, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800720 }
721 }
722 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800723
724 if (mTitleStack.size() > 0) {
725 outState.putParcelableList(SAVE_KEY_TITLES_TAG, mTitleStack);
726 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800727 }
728
729 @Override
730 public void onResume() {
731 super.onResume();
732
733 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
734 @Override
735 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
736 invalidateHeaders();
737 }
738 };
739 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
740 mDevelopmentPreferencesListener);
741
742 mHeaderAdapter.resume();
743 invalidateHeaders();
744
745 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800746
747 mDrawerLayout.setDrawerListener(new DrawerListener());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800748 }
749
750 @Override
751 public void onPause() {
752 super.onPause();
753
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800754 mDrawerLayout.setDrawerListener(null);
755
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800756 unregisterReceiver(mBatteryInfoReceiver);
757
758 mHeaderAdapter.pause();
759
760 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
761 mDevelopmentPreferencesListener);
762
763 mDevelopmentPreferencesListener = null;
764 }
765
766 @Override
767 public void onDestroy() {
768 super.onDestroy();
769 if (mListeningToAccountUpdates) {
770 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
771 }
772 }
773
774 /**
775 * @hide
776 */
777 protected boolean isValidFragment(String fragmentName) {
778 // Almost all fragments are wrapped in this,
779 // except for a few that have their own activities.
780 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
781 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
782 }
783 return false;
784 }
785
786 /**
787 * When in two-pane mode, switch to the fragment pane to show the given
788 * preference fragment.
789 *
790 * @param header The new header to display.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000791 * @param validate true means that the fragment's Header needs to be validated
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800792 */
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000793 private void switchToHeader(Header header, boolean validate) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800794 if (header == null) {
795 return;
796 }
797 if (header != null && mCurrentHeader != null && header.id == mCurrentHeader.id &&
Fabrice Di Meglio61a77ab2014-02-19 14:07:18 -0800798 header.id != R.id.account_add &&
799 !header.fragment.equals(ManageAccountsSettings.class.getName())) {
800 // This is the header we are currently displaying (except "Add Account" or
801 // "Corporate"/"Google" Account entries that share the same fragment). Just make sure
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000802 // to pop the stack up to its root state.
803 getFragmentManager().popBackStack(BACK_STACK_PREFS,
804 FragmentManager.POP_BACK_STACK_INCLUSIVE);
805 } else {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800806 if (header.fragment != null) {
807 mTitleStack.clear();
808 switchToHeaderInner(header.fragment, header.fragmentArguments, validate);
809 setSelectedHeader(header);
810 final TitlePair pair = new TitlePair(0, getHeaderTitle(header));
811 mTitleStack.add(pair);
812 setTitle(pair.second);
813 } else if (header.intent != null) {
814 setSelectedHeader(header);
815 mTitleStack.clear();
816 startActivity(header.intent);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800817 } else {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800818 throw new IllegalStateException(
819 "Can't switch to header that has no Fragment nor Intent");
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800820 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800821 }
822 }
823
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800824 private CharSequence getHeaderTitle(Header header) {
825 final CharSequence title;
826 if (header.fragment.equals(DashboardSummary.class.getName())) {
827 title = getResources().getString(R.string.settings_label);
828 } else {
829 title = header.getTitle(getResources());
830 }
831 return title;
832 }
833
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800834 private void setSelectedHeader(Header header) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800835 if (header == null) {
836 mCurrentHeader = null;
837 return;
838 }
839 // Update selected Header into Drawer only if it is not "Add Account"
840 if (header.id == R.id.account_add) {
841 mDrawer.clearChoices();
842 return;
843 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800844 mCurrentHeader = header;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800845 int index = mHeaders.indexOf(header);
846 if (mDrawer != null) {
847 if (index >= 0) {
848 mDrawer.setItemChecked(index, true);
849 } else {
850 mDrawer.clearChoices();
851 }
852 }
853 }
854
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000855 public Header onGetInitialHeader() {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800856 String fragmentClass = getStartingFragmentClass(super.getIntent());
857 if (fragmentClass != null) {
858 Header header = new Header();
859 header.fragment = fragmentClass;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000860 header.title = getTitle();
861 header.fragmentArguments = getIntent().getExtras();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800862 return header;
863 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000864
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800865 return mFirstHeader;
866 }
867
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000868 /**
869 * When in two-pane mode, switch the fragment pane to show the given
870 * preference fragment.
871 *
872 * @param fragmentName The name of the fragment to display.
873 * @param args Optional arguments to supply to the fragment.
874 * @param validate true means that the fragment's Header needs to be validated
875 */
876 private void switchToHeader(String fragmentName, Bundle args, boolean validate) {
877 setSelectedHeader(null);
878 switchToHeaderInner(fragmentName, args, validate);
879 }
880
881 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate) {
882 getFragmentManager().popBackStack(BACK_STACK_PREFS,
883 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800884 if (validate && !isValidFragment(fragmentName)) {
885 throw new IllegalArgumentException("Invalid fragment for this activity: "
886 + fragmentName);
887 }
888 Fragment f = Fragment.instantiate(this, fragmentName, args);
889 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800890 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800891 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800892 transaction.commitAllowingStateLoss();
893 }
894
895 @Override
896 public void onNewIntent(Intent intent) {
897 super.onNewIntent(intent);
898
899 // If it is not launched from history, then reset to top-level
900 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
901 if (mDrawer != null) {
902 mDrawer.setSelectionFromTop(0, 0);
903 }
904 }
905 }
906
907 /**
908 * Called to determine whether the header list should be hidden.
909 * The default implementation returns the
910 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
911 * This is set to false, for example, when the activity is being re-launched
912 * to show a particular preference activity.
913 */
914 public boolean onIsHidingHeaders() {
915 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
916 }
917
918 private void highlightHeader(int id) {
919 if (id != 0) {
920 Integer index = mHeaderIndexMap.get(id);
921 if (index != null && mDrawer != null) {
922 mDrawer.setItemChecked(index, true);
923 if (mDrawer.getVisibility() == View.VISIBLE) {
924 mDrawer.smoothScrollToPosition(index);
925 }
926 }
927 }
928 }
929
930 @Override
931 public Intent getIntent() {
932 Intent superIntent = super.getIntent();
933 String startingFragment = getStartingFragmentClass(superIntent);
934 // This is called from super.onCreate, isMultiPane() is not yet reliable
935 // Do not use onIsHidingHeaders either, which relies itself on this method
936 if (startingFragment != null) {
937 Intent modIntent = new Intent(superIntent);
938 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
939 Bundle args = superIntent.getExtras();
940 if (args != null) {
941 args = new Bundle(args);
942 } else {
943 args = new Bundle();
944 }
945 args.putParcelable("intent", superIntent);
946 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
947 return modIntent;
948 }
949 return superIntent;
950 }
951
952 /**
953 * Checks if the component name in the intent is different from the Settings class and
954 * returns the class name to load as a fragment.
955 */
956 private String getStartingFragmentClass(Intent intent) {
957 if (mFragmentClass != null) return mFragmentClass;
958
959 String intentClass = intent.getComponent().getClassName();
960 if (intentClass.equals(getClass().getName())) return null;
961
962 if ("com.android.settings.ManageApplications".equals(intentClass)
963 || "com.android.settings.RunningServices".equals(intentClass)
964 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
965 // Old names of manage apps.
966 intentClass = com.android.settings.applications.ManageApplications.class.getName();
967 }
968
969 return intentClass;
970 }
971
972 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000973 * Start a new fragment containing a preference panel. If the preferences
974 * are being displayed in multi-pane mode, the given fragment class will
975 * be instantiated and placed in the appropriate pane. If running in
976 * single-pane mode, a new activity will be launched in which to show the
977 * fragment.
978 *
979 * @param fragmentClass Full name of the class implementing the fragment.
980 * @param args Any desired arguments to supply to the fragment.
981 * @param titleRes Optional resource identifier of the title of this
982 * fragment.
983 * @param titleText Optional text of the title of this fragment.
984 * @param resultTo Optional fragment that result data should be sent to.
985 * If non-null, resultTo.onActivityResult() will be called when this
986 * preference panel is done. The launched panel must use
987 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
988 * @param resultRequestCode If resultTo is non-null, this is the caller's
989 * request code to be received with the resut.
990 */
991 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
992 CharSequence titleText, Fragment resultTo,
993 int resultRequestCode) {
994 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
995 }
996
997 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800998 * Called by a preference panel fragment to finish itself.
999 *
1000 * @param caller The fragment that is asking to be finished.
1001 * @param resultCode Optional result code to send back to the original
1002 * launching fragment.
1003 * @param resultData Optional result data to send back to the original
1004 * launching fragment.
1005 */
1006 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
1007 setResult(resultCode, resultData);
1008 }
1009
1010 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001011 * Start a new fragment.
1012 *
1013 * @param fragment The fragment to start
1014 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1015 * the current fragment will be replaced.
1016 */
1017 public void startPreferenceFragment(Fragment fragment, boolean push) {
1018 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1019 transaction.replace(R.id.prefs, fragment);
1020 if (push) {
1021 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1022 transaction.addToBackStack(BACK_STACK_PREFS);
1023 } else {
1024 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
1025 }
1026 transaction.commitAllowingStateLoss();
1027 }
1028
1029 /**
1030 * Start a new fragment.
1031 *
1032 * @param fragmentName The name of the fragment to display.
1033 * @param args Optional arguments to supply to the fragment.
1034 * @param resultTo Option fragment that should receive the result of
1035 * the activity launch.
1036 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
1037 * report the result.
1038 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
1039 * valid one then it will be used to get the title. Otherwise the titleText
1040 * argument will be used as the title.
1041 * @param titleText string to display for the title of.
1042 */
1043 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
1044 int resultRequestCode, int titleRes, CharSequence titleText) {
1045 Fragment f = Fragment.instantiate(this, fragmentName, args);
1046 if (resultTo != null) {
1047 f.setTargetFragment(resultTo, resultRequestCode);
1048 }
1049 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1050 transaction.replace(R.id.prefs, f);
1051 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1052 transaction.addToBackStack(BACK_STACK_PREFS);
1053 transaction.commitAllowingStateLoss();
1054
1055 final TitlePair pair;
1056 final CharSequence cs;
1057 if (titleRes != 0) {
1058 pair = new TitlePair(titleRes, null);
1059 cs = getText(titleRes);
1060 } else {
1061 pair = new TitlePair(0, titleText);
1062 cs = titleText;
1063 }
1064 setTitle(cs);
1065 mTitleStack.add(pair);
1066 }
1067
1068 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001069 * Called when the activity needs its list of headers build. By
1070 * implementing this and adding at least one item to the list, you
1071 * will cause the activity to run in its modern fragment mode. Note
1072 * that this function may not always be called; for example, if the
1073 * activity has been asked to display a particular fragment without
1074 * the header list, there is no need to build the headers.
1075 *
1076 * <p>Typical implementations will use {@link #loadHeadersFromResource}
1077 * to fill in the list from a resource.
1078 *
1079 * @param headers The list in which to place the headers.
1080 */
1081 private void onBuildHeaders(List<Header> headers) {
1082 loadHeadersFromResource(R.xml.settings_headers, headers);
1083 updateHeaderList(headers);
1084 }
1085
1086 /**
1087 * Parse the given XML file as a header description, adding each
1088 * parsed Header into the target list.
1089 *
1090 * @param resid The XML resource to load and parse.
1091 * @param target The list in which the parsed headers should be placed.
1092 */
1093 private void loadHeadersFromResource(int resid, List<Header> target) {
1094 XmlResourceParser parser = null;
1095 try {
1096 parser = getResources().getXml(resid);
1097 AttributeSet attrs = Xml.asAttributeSet(parser);
1098
1099 int type;
1100 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1101 && type != XmlPullParser.START_TAG) {
1102 // Parse next until start tag is found
1103 }
1104
1105 String nodeName = parser.getName();
1106 if (!"preference-headers".equals(nodeName)) {
1107 throw new RuntimeException(
1108 "XML document must start with <preference-headers> tag; found"
1109 + nodeName + " at " + parser.getPositionDescription());
1110 }
1111
1112 Bundle curBundle = null;
1113
1114 final int outerDepth = parser.getDepth();
1115 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1116 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1117 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1118 continue;
1119 }
1120
1121 nodeName = parser.getName();
1122 if ("header".equals(nodeName)) {
1123 Header header = new Header();
1124
1125 TypedArray sa = obtainStyledAttributes(
1126 attrs, com.android.internal.R.styleable.PreferenceHeader);
1127 header.id = sa.getResourceId(
1128 com.android.internal.R.styleable.PreferenceHeader_id,
1129 (int)HEADER_ID_UNDEFINED);
1130 TypedValue tv = sa.peekValue(
1131 com.android.internal.R.styleable.PreferenceHeader_title);
1132 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1133 if (tv.resourceId != 0) {
1134 header.titleRes = tv.resourceId;
1135 } else {
1136 header.title = tv.string;
1137 }
1138 }
1139 tv = sa.peekValue(
1140 com.android.internal.R.styleable.PreferenceHeader_summary);
1141 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1142 if (tv.resourceId != 0) {
1143 header.summaryRes = tv.resourceId;
1144 } else {
1145 header.summary = tv.string;
1146 }
1147 }
1148 header.iconRes = sa.getResourceId(
1149 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1150 header.fragment = sa.getString(
1151 com.android.internal.R.styleable.PreferenceHeader_fragment);
1152 sa.recycle();
1153
1154 if (curBundle == null) {
1155 curBundle = new Bundle();
1156 }
1157
1158 final int innerDepth = parser.getDepth();
1159 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1160 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1161 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1162 continue;
1163 }
1164
1165 String innerNodeName = parser.getName();
1166 if (innerNodeName.equals("extra")) {
1167 getResources().parseBundleExtra("extra", attrs, curBundle);
1168 XmlUtils.skipCurrentTag(parser);
1169
1170 } else if (innerNodeName.equals("intent")) {
1171 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1172
1173 } else {
1174 XmlUtils.skipCurrentTag(parser);
1175 }
1176 }
1177
1178 if (curBundle.size() > 0) {
1179 header.fragmentArguments = curBundle;
1180 curBundle = null;
1181 }
1182
1183 target.add(header);
1184 } else {
1185 XmlUtils.skipCurrentTag(parser);
1186 }
1187 }
1188
1189 } catch (XmlPullParserException e) {
1190 throw new RuntimeException("Error parsing headers", e);
1191 } catch (IOException e) {
1192 throw new RuntimeException("Error parsing headers", e);
1193 } finally {
1194 if (parser != null) parser.close();
1195 }
1196 }
1197
1198 private void updateHeaderList(List<Header> target) {
1199 final boolean showDev = mDevelopmentPreferences.getBoolean(
1200 DevelopmentSettings.PREF_SHOW,
1201 android.os.Build.TYPE.equals("eng"));
1202 int i = 0;
1203
1204 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1205 mHeaderIndexMap.clear();
1206 while (i < target.size()) {
1207 Header header = target.get(i);
1208 // Ids are integers, so downcasting
1209 int id = (int) header.id;
1210 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1211 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1212 } else if (id == R.id.wifi_settings) {
1213 // Remove WiFi Settings if WiFi service is not available.
1214 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1215 target.remove(i);
1216 }
1217 } else if (id == R.id.bluetooth_settings) {
1218 // Remove Bluetooth Settings if Bluetooth service is not available.
1219 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1220 target.remove(i);
1221 }
1222 } else if (id == R.id.data_usage_settings) {
1223 // Remove data usage when kernel module not enabled
1224 final INetworkManagementService netManager = INetworkManagementService.Stub
1225 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1226 try {
1227 if (!netManager.isBandwidthControlEnabled()) {
1228 target.remove(i);
1229 }
1230 } catch (RemoteException e) {
1231 // ignored
1232 }
1233 } else if (id == R.id.battery_settings) {
1234 // Remove battery settings when battery is not available. (e.g. TV)
1235
1236 if (!mBatteryPresent) {
1237 target.remove(i);
1238 }
1239 } else if (id == R.id.account_settings) {
1240 int headerIndex = i + 1;
1241 i = insertAccountsHeaders(target, headerIndex);
1242 } else if (id == R.id.home_settings) {
1243 if (!updateHomeSettingHeaders(header)) {
1244 target.remove(i);
1245 }
1246 } else if (id == R.id.user_settings) {
1247 if (!UserHandle.MU_ENABLED
1248 || !UserManager.supportsMultipleUsers()
1249 || Utils.isMonkeyRunning()) {
1250 target.remove(i);
1251 }
1252 } else if (id == R.id.nfc_payment_settings) {
1253 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1254 target.remove(i);
1255 } else {
1256 // Only show if NFC is on and we have the HCE feature
1257 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1258 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1259 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1260 target.remove(i);
1261 }
1262 }
1263 } else if (id == R.id.development_settings) {
1264 if (!showDev) {
1265 target.remove(i);
1266 }
1267 } else if (id == R.id.account_add) {
1268 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1269 target.remove(i);
1270 }
1271 }
1272
1273 if (i < target.size() && target.get(i) == header
1274 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1275 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1276 target.remove(i);
1277 }
1278
1279 // Increment if the current one wasn't removed by the Utils code.
1280 if (i < target.size() && target.get(i) == header) {
1281 // Hold on to the first header, when we need to reset to the top-level
1282 if (mFirstHeader == null &&
1283 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1284 mFirstHeader = header;
1285 }
1286 mHeaderIndexMap.put(id, i);
1287 i++;
1288 }
1289 }
1290 }
1291
1292 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1293 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1294 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1295 for (String accountType : accountTypes) {
1296 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1297 if (label == null) {
1298 continue;
1299 }
1300
1301 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1302 boolean skipToAccount = accounts.length == 1
1303 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1304 Header accHeader = new Header();
1305 accHeader.title = label;
1306 if (accHeader.extras == null) {
1307 accHeader.extras = new Bundle();
1308 }
1309 if (skipToAccount) {
1310 accHeader.fragment = AccountSyncSettings.class.getName();
1311 accHeader.fragmentArguments = new Bundle();
1312 // Need this for the icon
1313 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1314 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1315 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1316 accounts[0]);
1317 } else {
1318 accHeader.fragment = ManageAccountsSettings.class.getName();
1319 accHeader.fragmentArguments = new Bundle();
1320 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1321 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1322 accountType);
1323 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1324 label.toString());
1325 }
1326 accountHeaders.add(accHeader);
1327 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1328 }
1329
1330 // Sort by label
1331 Collections.sort(accountHeaders, new Comparator<Header>() {
1332 @Override
1333 public int compare(Header h1, Header h2) {
1334 return h1.title.toString().compareTo(h2.title.toString());
1335 }
1336 });
1337
1338 for (Header header : accountHeaders) {
1339 target.add(headerIndex++, header);
1340 }
1341 if (!mListeningToAccountUpdates) {
1342 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1343 mListeningToAccountUpdates = true;
1344 }
1345 return headerIndex;
1346 }
1347
1348 private boolean updateHomeSettingHeaders(Header header) {
1349 // Once we decide to show Home settings, keep showing it forever
1350 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1351 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1352 return true;
1353 }
1354
1355 try {
1356 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1357 getPackageManager().getHomeActivities(homeApps);
1358 if (homeApps.size() < 2) {
1359 // When there's only one available home app, omit this settings
1360 // category entirely at the top level UI. If the user just
1361 // uninstalled the penultimate home app candidiate, we also
1362 // now tell them about why they aren't seeing 'Home' in the list.
1363 if (sShowNoHomeNotice) {
1364 sShowNoHomeNotice = false;
1365 NoHomeDialogFragment.show(this);
1366 }
1367 return false;
1368 } else {
1369 // Okay, we're allowing the Home settings category. Tell it, when
1370 // invoked via this front door, that we'll need to be told about the
1371 // case when the user uninstalls all but one home app.
1372 if (header.fragmentArguments == null) {
1373 header.fragmentArguments = new Bundle();
1374 }
1375 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1376 }
1377 } catch (Exception e) {
1378 // Can't look up the home activity; bail on configuring the icon
1379 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1380 }
1381
1382 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1383 return true;
1384 }
1385
1386 private void getMetaData() {
1387 try {
1388 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1389 PackageManager.GET_META_DATA);
1390 if (ai == null || ai.metaData == null) return;
1391 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1392 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1393 } catch (NameNotFoundException nnfe) {
1394 // No recovery
1395 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1396 }
1397 }
1398
1399 // give subclasses access to the Next button
1400 public boolean hasNextButton() {
1401 return mNextButton != null;
1402 }
1403
1404 public Button getNextButton() {
1405 return mNextButton;
1406 }
1407
1408 public static class NoHomeDialogFragment extends DialogFragment {
1409 public static void show(Activity parent) {
1410 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1411 dialog.show(parent.getFragmentManager(), null);
1412 }
1413
1414 @Override
1415 public Dialog onCreateDialog(Bundle savedInstanceState) {
1416 return new AlertDialog.Builder(getActivity())
1417 .setMessage(R.string.only_one_home_message)
1418 .setPositiveButton(android.R.string.ok, null)
1419 .create();
1420 }
1421 }
1422
1423 private static class HeaderAdapter extends ArrayAdapter<Header> {
1424 static final int HEADER_TYPE_CATEGORY = 0;
1425 static final int HEADER_TYPE_NORMAL = 1;
1426 static final int HEADER_TYPE_SWITCH = 2;
1427 static final int HEADER_TYPE_BUTTON = 3;
1428 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1429
1430 private final WifiEnabler mWifiEnabler;
1431 private final BluetoothEnabler mBluetoothEnabler;
1432 private AuthenticatorHelper mAuthHelper;
1433 private DevicePolicyManager mDevicePolicyManager;
1434
1435 private static class HeaderViewHolder {
1436 ImageView mIcon;
1437 TextView mTitle;
1438 TextView mSummary;
1439 Switch mSwitch;
1440 ImageButton mButton;
1441 View mDivider;
1442 }
1443
1444 private LayoutInflater mInflater;
1445
1446 static int getHeaderType(Header header) {
1447 if (header.fragment == null && header.intent == null) {
1448 return HEADER_TYPE_CATEGORY;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001449 } else if (header.id == R.id.security_settings) {
1450 return HEADER_TYPE_BUTTON;
1451 } else {
1452 return HEADER_TYPE_NORMAL;
1453 }
1454 }
1455
1456 @Override
1457 public int getItemViewType(int position) {
1458 Header header = getItem(position);
1459 return getHeaderType(header);
1460 }
1461
1462 @Override
1463 public boolean areAllItemsEnabled() {
1464 return false; // because of categories
1465 }
1466
1467 @Override
1468 public boolean isEnabled(int position) {
1469 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1470 }
1471
1472 @Override
1473 public int getViewTypeCount() {
1474 return HEADER_TYPE_COUNT;
1475 }
1476
1477 @Override
1478 public boolean hasStableIds() {
1479 return true;
1480 }
1481
1482 public HeaderAdapter(Context context, List<Header> objects,
1483 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1484 super(context, 0, objects);
1485
1486 mAuthHelper = authenticatorHelper;
1487 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1488
1489 // Temp Switches provided as placeholder until the adapter replaces these with actual
1490 // Switches inflated from their layouts. Must be done before adapter is set in super
1491 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1492 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1493 mDevicePolicyManager = dpm;
1494 }
1495
1496 @Override
1497 public View getView(int position, View convertView, ViewGroup parent) {
1498 HeaderViewHolder holder;
1499 Header header = getItem(position);
1500 int headerType = getHeaderType(header);
1501 View view = null;
1502
1503 if (convertView == null) {
1504 holder = new HeaderViewHolder();
1505 switch (headerType) {
1506 case HEADER_TYPE_CATEGORY:
1507 view = new TextView(getContext(), null,
1508 android.R.attr.listSeparatorTextViewStyle);
1509 holder.mTitle = (TextView) view;
1510 break;
1511
1512 case HEADER_TYPE_SWITCH:
1513 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1514 false);
1515 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1516 holder.mTitle = (TextView)
1517 view.findViewById(com.android.internal.R.id.title);
1518 holder.mSummary = (TextView)
1519 view.findViewById(com.android.internal.R.id.summary);
1520 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1521 break;
1522
1523 case HEADER_TYPE_BUTTON:
1524 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1525 false);
1526 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1527 holder.mTitle = (TextView)
1528 view.findViewById(com.android.internal.R.id.title);
1529 holder.mSummary = (TextView)
1530 view.findViewById(com.android.internal.R.id.summary);
1531 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1532 holder.mDivider = view.findViewById(R.id.divider);
1533 break;
1534
1535 case HEADER_TYPE_NORMAL:
1536 view = mInflater.inflate(
1537 R.layout.preference_header_item, parent,
1538 false);
1539 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1540 holder.mTitle = (TextView)
1541 view.findViewById(com.android.internal.R.id.title);
1542 holder.mSummary = (TextView)
1543 view.findViewById(com.android.internal.R.id.summary);
1544 break;
1545 }
1546 view.setTag(holder);
1547 } else {
1548 view = convertView;
1549 holder = (HeaderViewHolder) view.getTag();
1550 }
1551
1552 // All view fields must be updated every time, because the view may be recycled
1553 switch (headerType) {
1554 case HEADER_TYPE_CATEGORY:
1555 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1556 break;
1557
1558 case HEADER_TYPE_SWITCH:
1559 // Would need a different treatment if the main menu had more switches
1560 if (header.id == R.id.wifi_settings) {
1561 mWifiEnabler.setSwitch(holder.mSwitch);
1562 } else {
1563 mBluetoothEnabler.setSwitch(holder.mSwitch);
1564 }
1565 updateCommonHeaderView(header, holder);
1566 break;
1567
1568 case HEADER_TYPE_BUTTON:
1569 if (header.id == R.id.security_settings) {
1570 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1571 if (hasCert) {
1572 holder.mButton.setVisibility(View.VISIBLE);
1573 holder.mDivider.setVisibility(View.VISIBLE);
1574 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1575 if (isManaged) {
1576 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1577 } else {
1578 holder.mButton.setImageResource(
1579 android.R.drawable.stat_notify_error);
1580 }
1581 holder.mButton.setOnClickListener(new OnClickListener() {
1582 @Override
1583 public void onClick(View v) {
1584 Intent intent = new Intent(
1585 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1586 getContext().startActivity(intent);
1587 }
1588 });
1589 } else {
1590 holder.mButton.setVisibility(View.GONE);
1591 holder.mDivider.setVisibility(View.GONE);
1592 }
1593 }
1594 updateCommonHeaderView(header, holder);
1595 break;
1596
1597 case HEADER_TYPE_NORMAL:
1598 updateCommonHeaderView(header, holder);
1599 break;
1600 }
1601
1602 return view;
1603 }
1604
1605 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1606 if (header.extras != null
1607 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1608 String accType = header.extras.getString(
1609 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1610 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1611 setHeaderIcon(holder, icon);
1612 } else {
1613 holder.mIcon.setImageResource(header.iconRes);
1614 }
1615 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1616 CharSequence summary = header.getSummary(getContext().getResources());
1617 if (!TextUtils.isEmpty(summary)) {
1618 holder.mSummary.setVisibility(View.VISIBLE);
1619 holder.mSummary.setText(summary);
1620 } else {
1621 holder.mSummary.setVisibility(View.GONE);
1622 }
1623 }
1624
1625 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1626 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1627 lp.width = getContext().getResources().getDimensionPixelSize(
1628 R.dimen.header_icon_width);
1629 lp.height = lp.width;
1630 holder.mIcon.setLayoutParams(lp);
1631 holder.mIcon.setImageDrawable(icon);
1632 }
1633
1634 public void resume() {
1635 mWifiEnabler.resume();
1636 mBluetoothEnabler.resume();
1637 }
1638
1639 public void pause() {
1640 mWifiEnabler.pause();
1641 mBluetoothEnabler.pause();
1642 }
1643 }
1644
1645 private void onListItemClick(ListView l, View v, int position, long id) {
1646 if (!isResumed()) {
1647 return;
1648 }
1649 Object item = mHeaderAdapter.getItem(position);
1650 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001651 mSelectedHeader = (Header) item;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001652 }
1653 }
1654
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001655 @Override
1656 public boolean shouldUpRecreateTask(Intent targetIntent) {
1657 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1658 }
1659
1660 @Override
1661 public void onAccountsUpdated(Account[] accounts) {
1662 // TODO: watch for package upgrades to invalidate cache; see 7206643
1663 mAuthenticatorHelper.updateAuthDescriptions(this);
1664 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1665 invalidateHeaders();
1666 }
1667
1668 public static void requestHomeNotice() {
1669 sShowNoHomeNotice = true;
1670 }
1671
1672 /**
1673 * Default value for {@link Header#id Header.id} indicating that no
1674 * identifier value is set. All other values (including those below -1)
1675 * are valid.
1676 */
1677 private static final long HEADER_ID_UNDEFINED = -1;
1678
1679 /**
1680 * Description of a single Header item that the user can select.
1681 */
1682 static final class Header implements Parcelable {
1683 /**
1684 * Identifier for this header, to correlate with a new list when
1685 * it is updated. The default value is
1686 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1687 * @attr ref android.R.styleable#PreferenceHeader_id
1688 */
1689 public long id = HEADER_ID_UNDEFINED;
1690
1691 /**
1692 * Resource ID of title of the header that is shown to the user.
1693 * @attr ref android.R.styleable#PreferenceHeader_title
1694 */
1695 public int titleRes;
1696
1697 /**
1698 * Title of the header that is shown to the user.
1699 * @attr ref android.R.styleable#PreferenceHeader_title
1700 */
1701 public CharSequence title;
1702
1703 /**
1704 * Resource ID of optional summary describing what this header controls.
1705 * @attr ref android.R.styleable#PreferenceHeader_summary
1706 */
1707 public int summaryRes;
1708
1709 /**
1710 * Optional summary describing what this header controls.
1711 * @attr ref android.R.styleable#PreferenceHeader_summary
1712 */
1713 public CharSequence summary;
1714
1715 /**
1716 * Optional icon resource to show for this header.
1717 * @attr ref android.R.styleable#PreferenceHeader_icon
1718 */
1719 public int iconRes;
1720
1721 /**
1722 * Full class name of the fragment to display when this header is
1723 * selected.
1724 * @attr ref android.R.styleable#PreferenceHeader_fragment
1725 */
1726 public String fragment;
1727
1728 /**
1729 * Optional arguments to supply to the fragment when it is
1730 * instantiated.
1731 */
1732 public Bundle fragmentArguments;
1733
1734 /**
1735 * Intent to launch when the preference is selected.
1736 */
1737 public Intent intent;
1738
1739 /**
1740 * Optional additional data for use by subclasses of the activity
1741 */
1742 public Bundle extras;
1743
1744 public Header() {
1745 // Empty
1746 }
1747
1748 /**
1749 * Return the currently set title. If {@link #titleRes} is set,
1750 * this resource is loaded from <var>res</var> and returned. Otherwise
1751 * {@link #title} is returned.
1752 */
1753 public CharSequence getTitle(Resources res) {
1754 if (titleRes != 0) {
1755 return res.getText(titleRes);
1756 }
1757 return title;
1758 }
1759
1760 /**
1761 * Return the currently set summary. If {@link #summaryRes} is set,
1762 * this resource is loaded from <var>res</var> and returned. Otherwise
1763 * {@link #summary} is returned.
1764 */
1765 public CharSequence getSummary(Resources res) {
1766 if (summaryRes != 0) {
1767 return res.getText(summaryRes);
1768 }
1769 return summary;
1770 }
1771
1772 @Override
1773 public int describeContents() {
1774 return 0;
1775 }
1776
1777 @Override
1778 public void writeToParcel(Parcel dest, int flags) {
1779 dest.writeLong(id);
1780 dest.writeInt(titleRes);
1781 TextUtils.writeToParcel(title, dest, flags);
1782 dest.writeInt(summaryRes);
1783 TextUtils.writeToParcel(summary, dest, flags);
1784 dest.writeInt(iconRes);
1785 dest.writeString(fragment);
1786 dest.writeBundle(fragmentArguments);
1787 if (intent != null) {
1788 dest.writeInt(1);
1789 intent.writeToParcel(dest, flags);
1790 } else {
1791 dest.writeInt(0);
1792 }
1793 dest.writeBundle(extras);
1794 }
1795
1796 public void readFromParcel(Parcel in) {
1797 id = in.readLong();
1798 titleRes = in.readInt();
1799 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1800 summaryRes = in.readInt();
1801 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1802 iconRes = in.readInt();
1803 fragment = in.readString();
1804 fragmentArguments = in.readBundle();
1805 if (in.readInt() != 0) {
1806 intent = Intent.CREATOR.createFromParcel(in);
1807 }
1808 extras = in.readBundle();
1809 }
1810
1811 Header(Parcel in) {
1812 readFromParcel(in);
1813 }
1814
1815 public static final Creator<Header> CREATOR = new Creator<Header>() {
1816 public Header createFromParcel(Parcel source) {
1817 return new Header(source);
1818 }
1819 public Header[] newArray(int size) {
1820 return new Header[size];
1821 }
1822 };
1823 }
1824}