blob: 511fd7d17f26206d11dc46de829036e7a5bb1b02 [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;
68import android.view.LayoutInflater;
69import android.view.MenuItem;
70import android.view.View;
71import android.view.View.OnClickListener;
72import android.view.ViewGroup;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +000073import android.widget.AbsListView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080074import android.widget.AdapterView;
75import android.widget.ArrayAdapter;
76import android.widget.Button;
77import android.widget.ImageButton;
78import android.widget.ImageView;
79import android.widget.ListView;
80import android.widget.Switch;
81import android.widget.TextView;
82
83import com.android.internal.util.ArrayUtils;
84import com.android.internal.util.XmlUtils;
85import com.android.settings.accessibility.AccessibilitySettings;
86import com.android.settings.accessibility.CaptionPropertiesFragment;
87import com.android.settings.accounts.AccountSyncSettings;
88import com.android.settings.accounts.AuthenticatorHelper;
89import com.android.settings.accounts.ChooseAccountFragment;
90import 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,
229 R.id.home_settings
230 };
231
232 private static final String[] ENTRY_FRAGMENTS = {
233 WirelessSettings.class.getName(),
234 WifiSettings.class.getName(),
235 AdvancedWifiSettings.class.getName(),
236 BluetoothSettings.class.getName(),
237 TetherSettings.class.getName(),
238 WifiP2pSettings.class.getName(),
239 VpnSettings.class.getName(),
240 DateTimeSettings.class.getName(),
241 LocalePicker.class.getName(),
242 InputMethodAndLanguageSettings.class.getName(),
243 SpellCheckersSettings.class.getName(),
244 UserDictionaryList.class.getName(),
245 UserDictionarySettings.class.getName(),
246 SoundSettings.class.getName(),
247 DisplaySettings.class.getName(),
248 DeviceInfoSettings.class.getName(),
249 ManageApplications.class.getName(),
250 ProcessStatsUi.class.getName(),
251 NotificationStation.class.getName(),
252 LocationSettings.class.getName(),
253 SecuritySettings.class.getName(),
254 PrivacySettings.class.getName(),
255 DeviceAdminSettings.class.getName(),
256 AccessibilitySettings.class.getName(),
257 CaptionPropertiesFragment.class.getName(),
258 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
259 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
260 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
261 TextToSpeechSettings.class.getName(),
262 Memory.class.getName(),
263 DevelopmentSettings.class.getName(),
264 UsbSettings.class.getName(),
265 AndroidBeam.class.getName(),
266 WifiDisplaySettings.class.getName(),
267 PowerUsageSummary.class.getName(),
268 AccountSyncSettings.class.getName(),
269 CryptKeeperSettings.class.getName(),
270 DataUsageSummary.class.getName(),
271 DreamSettings.class.getName(),
272 UserSettings.class.getName(),
273 NotificationAccessSettings.class.getName(),
274 ManageAccountsSettings.class.getName(),
275 PrintSettingsFragment.class.getName(),
276 PrintJobSettingsFragment.class.getName(),
277 TrustedCredentialsSettings.class.getName(),
278 PaymentSettings.class.getName(),
279 KeyboardLayoutPickerFragment.class.getName(),
280 ChooseAccountFragment.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);
523 mHeaderAdapter= new HeaderAdapter(this, getHeaders(), mAuthenticatorHelper, dpm);
524
525 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
526 Context.MODE_PRIVATE);
527
528 getMetaData();
529
530 super.onCreate(savedInstanceState);
531
532 setContentView(R.layout.settings_main);
533
534 getFragmentManager().addOnBackStackChangedListener(this);
535
536 mActionBar = getActionBar();
537 if (mActionBar != null) {
538 mActionBar.setDisplayHomeAsUpEnabled(true);
539 mActionBar.setHomeButtonEnabled(true);
540
541 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800542
543 mDrawer = (ListView) findViewById(R.id.headers_drawer);
544 mDrawer.setAdapter(mHeaderAdapter);
545 mDrawer.setOnItemClickListener(new DrawerItemClickListener());
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000546 mDrawer.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800547
548 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
549 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
550 }
551
552 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
553 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
554
555 if (savedInstanceState != null) {
556 // We are restarting from a previous saved state; used that to
557 // initialize, instead of starting fresh.
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800558
559 ArrayList<TitlePair> titles =
560 savedInstanceState.getParcelableArrayList(SAVE_KEY_TITLES_TAG);
561 if (titles != null) {
562 mTitleStack.addAll(titles);
563 }
564 final int lastTitle = mTitleStack.size() - 1;
565 if (lastTitle >= 0) {
566 final TitlePair last = mTitleStack.get(lastTitle);
567 setTitleFromPair(last);
568 }
569
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800570 ArrayList<Header> headers =
571 savedInstanceState.getParcelableArrayList(SAVE_KEY_HEADERS_TAG);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800572 if (headers != null) {
573 mHeaders.addAll(headers);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800574 int curHeader = savedInstanceState.getInt(SAVE_KEY_CURRENT_HEADER_TAG,
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800575 (int) HEADER_ID_UNDEFINED);
576 if (curHeader >= 0 && curHeader < mHeaders.size()) {
577 setSelectedHeader(mHeaders.get(curHeader));
578 }
579 }
580
581 } else {
582 if (initialFragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000583 // If we are just showing a fragment, we want to run in
584 // new fragment mode, but don't need to compute and show
585 // the headers.
586 switchToHeader(initialFragment, initialArguments, true);
587
588 final int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
589 if (initialTitle != 0) {
590 setTitle(getText(initialTitle));
591 }
592 } else {
593 // We need to try to build the headers.
594 onBuildHeaders(mHeaders);
595
596 // If there are headers, then at this point we need to show
597 // them and, depending on the screen, we may also show in-line
598 // the currently selected preference fragment.
599 if (mHeaders.size() > 0) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800600 Header h = onGetInitialHeader();
601 switchToHeader(h, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000602 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800603 }
604 }
605
606 // see if we should show Back/Next buttons
607 Intent intent = getIntent();
608 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
609
610 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
611 if (buttonBar != null) {
612 buttonBar.setVisibility(View.VISIBLE);
613
614 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
615 backButton.setOnClickListener(new OnClickListener() {
616 public void onClick(View v) {
617 setResult(RESULT_CANCELED);
618 finish();
619 }
620 });
621 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
622 skipButton.setOnClickListener(new OnClickListener() {
623 public void onClick(View v) {
624 setResult(RESULT_OK);
625 finish();
626 }
627 });
628 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
629 mNextButton.setOnClickListener(new OnClickListener() {
630 public void onClick(View v) {
631 setResult(RESULT_OK);
632 finish();
633 }
634 });
635
636 // set our various button parameters
637 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
638 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
639 if (TextUtils.isEmpty(buttonText)) {
640 mNextButton.setVisibility(View.GONE);
641 }
642 else {
643 mNextButton.setText(buttonText);
644 }
645 }
646 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
647 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
648 if (TextUtils.isEmpty(buttonText)) {
649 backButton.setVisibility(View.GONE);
650 }
651 else {
652 backButton.setText(buttonText);
653 }
654 }
655 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
656 skipButton.setVisibility(View.VISIBLE);
657 }
658 }
659 }
660
661 if (!onIsHidingHeaders()) {
662 highlightHeader(mTopLevelHeaderId);
663 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800664 }
665
666 @Override
667 public void onBackStackChanged() {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000668 final int count = getFragmentManager().getBackStackEntryCount() + 1;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800669 TitlePair pair = null;
670 int last;
671 while (mTitleStack.size() > count) {
672 last = mTitleStack.size() - 1;
673 pair = mTitleStack.remove(last);
674 }
675 // Check if we go back
676 if (pair != null) {
677 int size = mTitleStack.size();
678 if (size > 0) {
679 last = mTitleStack.size() - 1;
680 pair = mTitleStack.get(last);
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800681 setTitleFromPair(pair);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800682 }
683 }
684 }
685
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800686 private void setTitleFromPair(TitlePair pair) {
687 final CharSequence title;
688 if (pair.first > 0) {
689 title = getText(pair.first);
690 } else {
691 title = pair.second;
692 }
693 setTitle(title);
694 }
695
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800696 /**
697 * Returns the Header list
698 */
699 private List<Header> getHeaders() {
700 return mHeaders;
701 }
702
703 @Override
704 protected void onSaveInstanceState(Bundle outState) {
705 super.onSaveInstanceState(outState);
706
707 if (mHeaders.size() > 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800708 outState.putParcelableArrayList(SAVE_KEY_HEADERS_TAG, mHeaders);
709 if (mCurrentHeader != null) {
710 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800711 if (index >= 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800712 outState.putInt(SAVE_KEY_CURRENT_HEADER_TAG, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800713 }
714 }
715 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800716
717 if (mTitleStack.size() > 0) {
718 outState.putParcelableList(SAVE_KEY_TITLES_TAG, mTitleStack);
719 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800720 }
721
722 @Override
723 public void onResume() {
724 super.onResume();
725
726 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
727 @Override
728 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
729 invalidateHeaders();
730 }
731 };
732 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
733 mDevelopmentPreferencesListener);
734
Matthew Xiea504c4d2014-02-14 16:32:32 -0800735 mHeaderAdapter.resume(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800736 invalidateHeaders();
737
738 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800739
740 mDrawerLayout.setDrawerListener(new DrawerListener());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800741 }
742
743 @Override
744 public void onPause() {
745 super.onPause();
746
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800747 mDrawerLayout.setDrawerListener(null);
748
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800749 unregisterReceiver(mBatteryInfoReceiver);
750
751 mHeaderAdapter.pause();
752
753 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
754 mDevelopmentPreferencesListener);
755
756 mDevelopmentPreferencesListener = null;
757 }
758
759 @Override
760 public void onDestroy() {
761 super.onDestroy();
762 if (mListeningToAccountUpdates) {
763 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
764 }
765 }
766
767 /**
768 * @hide
769 */
770 protected boolean isValidFragment(String fragmentName) {
771 // Almost all fragments are wrapped in this,
772 // except for a few that have their own activities.
773 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
774 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
775 }
776 return false;
777 }
778
779 /**
780 * When in two-pane mode, switch to the fragment pane to show the given
781 * preference fragment.
782 *
783 * @param header The new header to display.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000784 * @param validate true means that the fragment's Header needs to be validated
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800785 */
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000786 private void switchToHeader(Header header, boolean validate) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800787 if (header == null) {
788 return;
789 }
790 if (header != null && mCurrentHeader != null && header.id == mCurrentHeader.id &&
791 header.id != R.id.account_add) {
792 // This is the header we are currently displaying (except "Add Account"). Just make sure
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000793 // to pop the stack up to its root state.
794 getFragmentManager().popBackStack(BACK_STACK_PREFS,
795 FragmentManager.POP_BACK_STACK_INCLUSIVE);
796 } else {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800797 if (header.fragment != null) {
798 mTitleStack.clear();
799 switchToHeaderInner(header.fragment, header.fragmentArguments, validate);
800 setSelectedHeader(header);
801 final TitlePair pair = new TitlePair(0, getHeaderTitle(header));
802 mTitleStack.add(pair);
803 setTitle(pair.second);
804 } else if (header.intent != null) {
805 setSelectedHeader(header);
806 mTitleStack.clear();
807 startActivity(header.intent);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800808 } else {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800809 throw new IllegalStateException(
810 "Can't switch to header that has no Fragment nor Intent");
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800811 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800812 }
813 }
814
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800815 private CharSequence getHeaderTitle(Header header) {
816 final CharSequence title;
817 if (header.fragment.equals(DashboardSummary.class.getName())) {
818 title = getResources().getString(R.string.settings_label);
819 } else {
820 title = header.getTitle(getResources());
821 }
822 return title;
823 }
824
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800825 private void setSelectedHeader(Header header) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800826 if (header == null) {
827 mCurrentHeader = null;
828 return;
829 }
830 // Update selected Header into Drawer only if it is not "Add Account"
831 if (header.id == R.id.account_add) {
832 mDrawer.clearChoices();
833 return;
834 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800835 mCurrentHeader = header;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800836 int index = mHeaders.indexOf(header);
837 if (mDrawer != null) {
838 if (index >= 0) {
839 mDrawer.setItemChecked(index, true);
840 } else {
841 mDrawer.clearChoices();
842 }
843 }
844 }
845
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000846 public Header onGetInitialHeader() {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800847 String fragmentClass = getStartingFragmentClass(super.getIntent());
848 if (fragmentClass != null) {
849 Header header = new Header();
850 header.fragment = fragmentClass;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000851 header.title = getTitle();
852 header.fragmentArguments = getIntent().getExtras();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800853 return header;
854 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000855
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800856 return mFirstHeader;
857 }
858
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000859 /**
860 * When in two-pane mode, switch the fragment pane to show the given
861 * preference fragment.
862 *
863 * @param fragmentName The name of the fragment to display.
864 * @param args Optional arguments to supply to the fragment.
865 * @param validate true means that the fragment's Header needs to be validated
866 */
867 private void switchToHeader(String fragmentName, Bundle args, boolean validate) {
868 setSelectedHeader(null);
869 switchToHeaderInner(fragmentName, args, validate);
870 }
871
872 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate) {
873 getFragmentManager().popBackStack(BACK_STACK_PREFS,
874 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800875 if (validate && !isValidFragment(fragmentName)) {
876 throw new IllegalArgumentException("Invalid fragment for this activity: "
877 + fragmentName);
878 }
879 Fragment f = Fragment.instantiate(this, fragmentName, args);
880 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800881 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800882 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800883 transaction.commitAllowingStateLoss();
884 }
885
886 @Override
887 public void onNewIntent(Intent intent) {
888 super.onNewIntent(intent);
889
890 // If it is not launched from history, then reset to top-level
891 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
892 if (mDrawer != null) {
893 mDrawer.setSelectionFromTop(0, 0);
894 }
895 }
896 }
897
898 /**
899 * Called to determine whether the header list should be hidden.
900 * The default implementation returns the
901 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
902 * This is set to false, for example, when the activity is being re-launched
903 * to show a particular preference activity.
904 */
905 public boolean onIsHidingHeaders() {
906 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
907 }
908
909 private void highlightHeader(int id) {
910 if (id != 0) {
911 Integer index = mHeaderIndexMap.get(id);
912 if (index != null && mDrawer != null) {
913 mDrawer.setItemChecked(index, true);
914 if (mDrawer.getVisibility() == View.VISIBLE) {
915 mDrawer.smoothScrollToPosition(index);
916 }
917 }
918 }
919 }
920
921 @Override
922 public Intent getIntent() {
923 Intent superIntent = super.getIntent();
924 String startingFragment = getStartingFragmentClass(superIntent);
925 // This is called from super.onCreate, isMultiPane() is not yet reliable
926 // Do not use onIsHidingHeaders either, which relies itself on this method
927 if (startingFragment != null) {
928 Intent modIntent = new Intent(superIntent);
929 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
930 Bundle args = superIntent.getExtras();
931 if (args != null) {
932 args = new Bundle(args);
933 } else {
934 args = new Bundle();
935 }
936 args.putParcelable("intent", superIntent);
937 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
938 return modIntent;
939 }
940 return superIntent;
941 }
942
943 /**
944 * Checks if the component name in the intent is different from the Settings class and
945 * returns the class name to load as a fragment.
946 */
947 private String getStartingFragmentClass(Intent intent) {
948 if (mFragmentClass != null) return mFragmentClass;
949
950 String intentClass = intent.getComponent().getClassName();
951 if (intentClass.equals(getClass().getName())) return null;
952
953 if ("com.android.settings.ManageApplications".equals(intentClass)
954 || "com.android.settings.RunningServices".equals(intentClass)
955 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
956 // Old names of manage apps.
957 intentClass = com.android.settings.applications.ManageApplications.class.getName();
958 }
959
960 return intentClass;
961 }
962
963 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000964 * Start a new fragment containing a preference panel. If the preferences
965 * are being displayed in multi-pane mode, the given fragment class will
966 * be instantiated and placed in the appropriate pane. If running in
967 * single-pane mode, a new activity will be launched in which to show the
968 * fragment.
969 *
970 * @param fragmentClass Full name of the class implementing the fragment.
971 * @param args Any desired arguments to supply to the fragment.
972 * @param titleRes Optional resource identifier of the title of this
973 * fragment.
974 * @param titleText Optional text of the title of this fragment.
975 * @param resultTo Optional fragment that result data should be sent to.
976 * If non-null, resultTo.onActivityResult() will be called when this
977 * preference panel is done. The launched panel must use
978 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
979 * @param resultRequestCode If resultTo is non-null, this is the caller's
980 * request code to be received with the resut.
981 */
982 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
983 CharSequence titleText, Fragment resultTo,
984 int resultRequestCode) {
985 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
986 }
987
988 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800989 * Called by a preference panel fragment to finish itself.
990 *
991 * @param caller The fragment that is asking to be finished.
992 * @param resultCode Optional result code to send back to the original
993 * launching fragment.
994 * @param resultData Optional result data to send back to the original
995 * launching fragment.
996 */
997 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
998 setResult(resultCode, resultData);
999 }
1000
1001 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001002 * Start a new fragment.
1003 *
1004 * @param fragment The fragment to start
1005 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1006 * the current fragment will be replaced.
1007 */
1008 public void startPreferenceFragment(Fragment fragment, boolean push) {
1009 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1010 transaction.replace(R.id.prefs, fragment);
1011 if (push) {
1012 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1013 transaction.addToBackStack(BACK_STACK_PREFS);
1014 } else {
1015 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
1016 }
1017 transaction.commitAllowingStateLoss();
1018 }
1019
1020 /**
1021 * Start a new fragment.
1022 *
1023 * @param fragmentName The name of the fragment to display.
1024 * @param args Optional arguments to supply to the fragment.
1025 * @param resultTo Option fragment that should receive the result of
1026 * the activity launch.
1027 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
1028 * report the result.
1029 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
1030 * valid one then it will be used to get the title. Otherwise the titleText
1031 * argument will be used as the title.
1032 * @param titleText string to display for the title of.
1033 */
1034 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
1035 int resultRequestCode, int titleRes, CharSequence titleText) {
1036 Fragment f = Fragment.instantiate(this, fragmentName, args);
1037 if (resultTo != null) {
1038 f.setTargetFragment(resultTo, resultRequestCode);
1039 }
1040 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1041 transaction.replace(R.id.prefs, f);
1042 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1043 transaction.addToBackStack(BACK_STACK_PREFS);
1044 transaction.commitAllowingStateLoss();
1045
1046 final TitlePair pair;
1047 final CharSequence cs;
1048 if (titleRes != 0) {
1049 pair = new TitlePair(titleRes, null);
1050 cs = getText(titleRes);
1051 } else {
1052 pair = new TitlePair(0, titleText);
1053 cs = titleText;
1054 }
1055 setTitle(cs);
1056 mTitleStack.add(pair);
1057 }
1058
1059 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001060 * Called when the activity needs its list of headers build. By
1061 * implementing this and adding at least one item to the list, you
1062 * will cause the activity to run in its modern fragment mode. Note
1063 * that this function may not always be called; for example, if the
1064 * activity has been asked to display a particular fragment without
1065 * the header list, there is no need to build the headers.
1066 *
1067 * <p>Typical implementations will use {@link #loadHeadersFromResource}
1068 * to fill in the list from a resource.
1069 *
1070 * @param headers The list in which to place the headers.
1071 */
1072 private void onBuildHeaders(List<Header> headers) {
1073 loadHeadersFromResource(R.xml.settings_headers, headers);
1074 updateHeaderList(headers);
1075 }
1076
1077 /**
1078 * Parse the given XML file as a header description, adding each
1079 * parsed Header into the target list.
1080 *
1081 * @param resid The XML resource to load and parse.
1082 * @param target The list in which the parsed headers should be placed.
1083 */
1084 private void loadHeadersFromResource(int resid, List<Header> target) {
1085 XmlResourceParser parser = null;
1086 try {
1087 parser = getResources().getXml(resid);
1088 AttributeSet attrs = Xml.asAttributeSet(parser);
1089
1090 int type;
1091 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1092 && type != XmlPullParser.START_TAG) {
1093 // Parse next until start tag is found
1094 }
1095
1096 String nodeName = parser.getName();
1097 if (!"preference-headers".equals(nodeName)) {
1098 throw new RuntimeException(
1099 "XML document must start with <preference-headers> tag; found"
1100 + nodeName + " at " + parser.getPositionDescription());
1101 }
1102
1103 Bundle curBundle = null;
1104
1105 final int outerDepth = parser.getDepth();
1106 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1107 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1108 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1109 continue;
1110 }
1111
1112 nodeName = parser.getName();
1113 if ("header".equals(nodeName)) {
1114 Header header = new Header();
1115
1116 TypedArray sa = obtainStyledAttributes(
1117 attrs, com.android.internal.R.styleable.PreferenceHeader);
1118 header.id = sa.getResourceId(
1119 com.android.internal.R.styleable.PreferenceHeader_id,
1120 (int)HEADER_ID_UNDEFINED);
1121 TypedValue tv = sa.peekValue(
1122 com.android.internal.R.styleable.PreferenceHeader_title);
1123 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1124 if (tv.resourceId != 0) {
1125 header.titleRes = tv.resourceId;
1126 } else {
1127 header.title = tv.string;
1128 }
1129 }
1130 tv = sa.peekValue(
1131 com.android.internal.R.styleable.PreferenceHeader_summary);
1132 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1133 if (tv.resourceId != 0) {
1134 header.summaryRes = tv.resourceId;
1135 } else {
1136 header.summary = tv.string;
1137 }
1138 }
1139 header.iconRes = sa.getResourceId(
1140 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1141 header.fragment = sa.getString(
1142 com.android.internal.R.styleable.PreferenceHeader_fragment);
1143 sa.recycle();
1144
1145 if (curBundle == null) {
1146 curBundle = new Bundle();
1147 }
1148
1149 final int innerDepth = parser.getDepth();
1150 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1151 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1152 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1153 continue;
1154 }
1155
1156 String innerNodeName = parser.getName();
1157 if (innerNodeName.equals("extra")) {
1158 getResources().parseBundleExtra("extra", attrs, curBundle);
1159 XmlUtils.skipCurrentTag(parser);
1160
1161 } else if (innerNodeName.equals("intent")) {
1162 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1163
1164 } else {
1165 XmlUtils.skipCurrentTag(parser);
1166 }
1167 }
1168
1169 if (curBundle.size() > 0) {
1170 header.fragmentArguments = curBundle;
1171 curBundle = null;
1172 }
1173
1174 target.add(header);
1175 } else {
1176 XmlUtils.skipCurrentTag(parser);
1177 }
1178 }
1179
1180 } catch (XmlPullParserException e) {
1181 throw new RuntimeException("Error parsing headers", e);
1182 } catch (IOException e) {
1183 throw new RuntimeException("Error parsing headers", e);
1184 } finally {
1185 if (parser != null) parser.close();
1186 }
1187 }
1188
1189 private void updateHeaderList(List<Header> target) {
1190 final boolean showDev = mDevelopmentPreferences.getBoolean(
1191 DevelopmentSettings.PREF_SHOW,
1192 android.os.Build.TYPE.equals("eng"));
1193 int i = 0;
1194
1195 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1196 mHeaderIndexMap.clear();
1197 while (i < target.size()) {
1198 Header header = target.get(i);
1199 // Ids are integers, so downcasting
1200 int id = (int) header.id;
1201 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1202 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1203 } else if (id == R.id.wifi_settings) {
1204 // Remove WiFi Settings if WiFi service is not available.
1205 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1206 target.remove(i);
1207 }
1208 } else if (id == R.id.bluetooth_settings) {
1209 // Remove Bluetooth Settings if Bluetooth service is not available.
1210 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1211 target.remove(i);
1212 }
1213 } else if (id == R.id.data_usage_settings) {
1214 // Remove data usage when kernel module not enabled
1215 final INetworkManagementService netManager = INetworkManagementService.Stub
1216 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1217 try {
1218 if (!netManager.isBandwidthControlEnabled()) {
1219 target.remove(i);
1220 }
1221 } catch (RemoteException e) {
1222 // ignored
1223 }
1224 } else if (id == R.id.battery_settings) {
1225 // Remove battery settings when battery is not available. (e.g. TV)
1226
1227 if (!mBatteryPresent) {
1228 target.remove(i);
1229 }
1230 } else if (id == R.id.account_settings) {
1231 int headerIndex = i + 1;
1232 i = insertAccountsHeaders(target, headerIndex);
1233 } else if (id == R.id.home_settings) {
1234 if (!updateHomeSettingHeaders(header)) {
1235 target.remove(i);
1236 }
1237 } else if (id == R.id.user_settings) {
1238 if (!UserHandle.MU_ENABLED
1239 || !UserManager.supportsMultipleUsers()
1240 || Utils.isMonkeyRunning()) {
1241 target.remove(i);
1242 }
1243 } else if (id == R.id.nfc_payment_settings) {
1244 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1245 target.remove(i);
1246 } else {
1247 // Only show if NFC is on and we have the HCE feature
1248 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1249 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1250 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1251 target.remove(i);
1252 }
1253 }
1254 } else if (id == R.id.development_settings) {
1255 if (!showDev) {
1256 target.remove(i);
1257 }
1258 } else if (id == R.id.account_add) {
1259 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1260 target.remove(i);
1261 }
1262 }
1263
1264 if (i < target.size() && target.get(i) == header
1265 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1266 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1267 target.remove(i);
1268 }
1269
1270 // Increment if the current one wasn't removed by the Utils code.
1271 if (i < target.size() && target.get(i) == header) {
1272 // Hold on to the first header, when we need to reset to the top-level
1273 if (mFirstHeader == null &&
1274 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1275 mFirstHeader = header;
1276 }
1277 mHeaderIndexMap.put(id, i);
1278 i++;
1279 }
1280 }
1281 }
1282
1283 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1284 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1285 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1286 for (String accountType : accountTypes) {
1287 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1288 if (label == null) {
1289 continue;
1290 }
1291
1292 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1293 boolean skipToAccount = accounts.length == 1
1294 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1295 Header accHeader = new Header();
1296 accHeader.title = label;
1297 if (accHeader.extras == null) {
1298 accHeader.extras = new Bundle();
1299 }
1300 if (skipToAccount) {
1301 accHeader.fragment = AccountSyncSettings.class.getName();
1302 accHeader.fragmentArguments = new Bundle();
1303 // Need this for the icon
1304 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1305 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1306 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1307 accounts[0]);
1308 } else {
1309 accHeader.fragment = ManageAccountsSettings.class.getName();
1310 accHeader.fragmentArguments = new Bundle();
1311 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1312 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1313 accountType);
1314 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1315 label.toString());
1316 }
1317 accountHeaders.add(accHeader);
1318 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1319 }
1320
1321 // Sort by label
1322 Collections.sort(accountHeaders, new Comparator<Header>() {
1323 @Override
1324 public int compare(Header h1, Header h2) {
1325 return h1.title.toString().compareTo(h2.title.toString());
1326 }
1327 });
1328
1329 for (Header header : accountHeaders) {
1330 target.add(headerIndex++, header);
1331 }
1332 if (!mListeningToAccountUpdates) {
1333 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1334 mListeningToAccountUpdates = true;
1335 }
1336 return headerIndex;
1337 }
1338
1339 private boolean updateHomeSettingHeaders(Header header) {
1340 // Once we decide to show Home settings, keep showing it forever
1341 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1342 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1343 return true;
1344 }
1345
1346 try {
1347 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1348 getPackageManager().getHomeActivities(homeApps);
1349 if (homeApps.size() < 2) {
1350 // When there's only one available home app, omit this settings
1351 // category entirely at the top level UI. If the user just
1352 // uninstalled the penultimate home app candidiate, we also
1353 // now tell them about why they aren't seeing 'Home' in the list.
1354 if (sShowNoHomeNotice) {
1355 sShowNoHomeNotice = false;
1356 NoHomeDialogFragment.show(this);
1357 }
1358 return false;
1359 } else {
1360 // Okay, we're allowing the Home settings category. Tell it, when
1361 // invoked via this front door, that we'll need to be told about the
1362 // case when the user uninstalls all but one home app.
1363 if (header.fragmentArguments == null) {
1364 header.fragmentArguments = new Bundle();
1365 }
1366 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1367 }
1368 } catch (Exception e) {
1369 // Can't look up the home activity; bail on configuring the icon
1370 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1371 }
1372
1373 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1374 return true;
1375 }
1376
1377 private void getMetaData() {
1378 try {
1379 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1380 PackageManager.GET_META_DATA);
1381 if (ai == null || ai.metaData == null) return;
1382 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1383 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1384 } catch (NameNotFoundException nnfe) {
1385 // No recovery
1386 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1387 }
1388 }
1389
1390 // give subclasses access to the Next button
1391 public boolean hasNextButton() {
1392 return mNextButton != null;
1393 }
1394
1395 public Button getNextButton() {
1396 return mNextButton;
1397 }
1398
1399 public static class NoHomeDialogFragment extends DialogFragment {
1400 public static void show(Activity parent) {
1401 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1402 dialog.show(parent.getFragmentManager(), null);
1403 }
1404
1405 @Override
1406 public Dialog onCreateDialog(Bundle savedInstanceState) {
1407 return new AlertDialog.Builder(getActivity())
1408 .setMessage(R.string.only_one_home_message)
1409 .setPositiveButton(android.R.string.ok, null)
1410 .create();
1411 }
1412 }
1413
1414 private static class HeaderAdapter extends ArrayAdapter<Header> {
1415 static final int HEADER_TYPE_CATEGORY = 0;
1416 static final int HEADER_TYPE_NORMAL = 1;
1417 static final int HEADER_TYPE_SWITCH = 2;
1418 static final int HEADER_TYPE_BUTTON = 3;
1419 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1420
1421 private final WifiEnabler mWifiEnabler;
1422 private final BluetoothEnabler mBluetoothEnabler;
1423 private AuthenticatorHelper mAuthHelper;
1424 private DevicePolicyManager mDevicePolicyManager;
1425
1426 private static class HeaderViewHolder {
1427 ImageView mIcon;
1428 TextView mTitle;
1429 TextView mSummary;
1430 Switch mSwitch;
1431 ImageButton mButton;
1432 View mDivider;
1433 }
1434
1435 private LayoutInflater mInflater;
1436
1437 static int getHeaderType(Header header) {
1438 if (header.fragment == null && header.intent == null) {
1439 return HEADER_TYPE_CATEGORY;
1440 } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
1441 return HEADER_TYPE_SWITCH;
1442 } else if (header.id == R.id.security_settings) {
1443 return HEADER_TYPE_BUTTON;
1444 } else {
1445 return HEADER_TYPE_NORMAL;
1446 }
1447 }
1448
1449 @Override
1450 public int getItemViewType(int position) {
1451 Header header = getItem(position);
1452 return getHeaderType(header);
1453 }
1454
1455 @Override
1456 public boolean areAllItemsEnabled() {
1457 return false; // because of categories
1458 }
1459
1460 @Override
1461 public boolean isEnabled(int position) {
1462 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1463 }
1464
1465 @Override
1466 public int getViewTypeCount() {
1467 return HEADER_TYPE_COUNT;
1468 }
1469
1470 @Override
1471 public boolean hasStableIds() {
1472 return true;
1473 }
1474
1475 public HeaderAdapter(Context context, List<Header> objects,
1476 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1477 super(context, 0, objects);
1478
1479 mAuthHelper = authenticatorHelper;
1480 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1481
1482 // Temp Switches provided as placeholder until the adapter replaces these with actual
1483 // Switches inflated from their layouts. Must be done before adapter is set in super
1484 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1485 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1486 mDevicePolicyManager = dpm;
1487 }
1488
1489 @Override
1490 public View getView(int position, View convertView, ViewGroup parent) {
1491 HeaderViewHolder holder;
1492 Header header = getItem(position);
1493 int headerType = getHeaderType(header);
1494 View view = null;
1495
1496 if (convertView == null) {
1497 holder = new HeaderViewHolder();
1498 switch (headerType) {
1499 case HEADER_TYPE_CATEGORY:
1500 view = new TextView(getContext(), null,
1501 android.R.attr.listSeparatorTextViewStyle);
1502 holder.mTitle = (TextView) view;
1503 break;
1504
1505 case HEADER_TYPE_SWITCH:
1506 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1507 false);
1508 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1509 holder.mTitle = (TextView)
1510 view.findViewById(com.android.internal.R.id.title);
1511 holder.mSummary = (TextView)
1512 view.findViewById(com.android.internal.R.id.summary);
1513 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1514 break;
1515
1516 case HEADER_TYPE_BUTTON:
1517 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1518 false);
1519 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1520 holder.mTitle = (TextView)
1521 view.findViewById(com.android.internal.R.id.title);
1522 holder.mSummary = (TextView)
1523 view.findViewById(com.android.internal.R.id.summary);
1524 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1525 holder.mDivider = view.findViewById(R.id.divider);
1526 break;
1527
1528 case HEADER_TYPE_NORMAL:
1529 view = mInflater.inflate(
1530 R.layout.preference_header_item, parent,
1531 false);
1532 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1533 holder.mTitle = (TextView)
1534 view.findViewById(com.android.internal.R.id.title);
1535 holder.mSummary = (TextView)
1536 view.findViewById(com.android.internal.R.id.summary);
1537 break;
1538 }
1539 view.setTag(holder);
1540 } else {
1541 view = convertView;
1542 holder = (HeaderViewHolder) view.getTag();
1543 }
1544
1545 // All view fields must be updated every time, because the view may be recycled
1546 switch (headerType) {
1547 case HEADER_TYPE_CATEGORY:
1548 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1549 break;
1550
1551 case HEADER_TYPE_SWITCH:
1552 // Would need a different treatment if the main menu had more switches
1553 if (header.id == R.id.wifi_settings) {
1554 mWifiEnabler.setSwitch(holder.mSwitch);
1555 } else {
1556 mBluetoothEnabler.setSwitch(holder.mSwitch);
1557 }
1558 updateCommonHeaderView(header, holder);
1559 break;
1560
1561 case HEADER_TYPE_BUTTON:
1562 if (header.id == R.id.security_settings) {
1563 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1564 if (hasCert) {
1565 holder.mButton.setVisibility(View.VISIBLE);
1566 holder.mDivider.setVisibility(View.VISIBLE);
1567 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1568 if (isManaged) {
1569 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1570 } else {
1571 holder.mButton.setImageResource(
1572 android.R.drawable.stat_notify_error);
1573 }
1574 holder.mButton.setOnClickListener(new OnClickListener() {
1575 @Override
1576 public void onClick(View v) {
1577 Intent intent = new Intent(
1578 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1579 getContext().startActivity(intent);
1580 }
1581 });
1582 } else {
1583 holder.mButton.setVisibility(View.GONE);
1584 holder.mDivider.setVisibility(View.GONE);
1585 }
1586 }
1587 updateCommonHeaderView(header, holder);
1588 break;
1589
1590 case HEADER_TYPE_NORMAL:
1591 updateCommonHeaderView(header, holder);
1592 break;
1593 }
1594
1595 return view;
1596 }
1597
1598 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1599 if (header.extras != null
1600 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1601 String accType = header.extras.getString(
1602 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1603 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1604 setHeaderIcon(holder, icon);
1605 } else {
1606 holder.mIcon.setImageResource(header.iconRes);
1607 }
1608 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1609 CharSequence summary = header.getSummary(getContext().getResources());
1610 if (!TextUtils.isEmpty(summary)) {
1611 holder.mSummary.setVisibility(View.VISIBLE);
1612 holder.mSummary.setText(summary);
1613 } else {
1614 holder.mSummary.setVisibility(View.GONE);
1615 }
1616 }
1617
1618 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1619 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1620 lp.width = getContext().getResources().getDimensionPixelSize(
1621 R.dimen.header_icon_width);
1622 lp.height = lp.width;
1623 holder.mIcon.setLayoutParams(lp);
1624 holder.mIcon.setImageDrawable(icon);
1625 }
1626
Matthew Xiea504c4d2014-02-14 16:32:32 -08001627 public void resume(Context context) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001628 mWifiEnabler.resume();
Matthew Xiea504c4d2014-02-14 16:32:32 -08001629 mBluetoothEnabler.resume(context);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001630 }
1631
1632 public void pause() {
1633 mWifiEnabler.pause();
1634 mBluetoothEnabler.pause();
1635 }
1636 }
1637
1638 private void onListItemClick(ListView l, View v, int position, long id) {
1639 if (!isResumed()) {
1640 return;
1641 }
1642 Object item = mHeaderAdapter.getItem(position);
1643 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001644 mSelectedHeader = (Header) item;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001645 }
1646 }
1647
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001648 @Override
1649 public boolean shouldUpRecreateTask(Intent targetIntent) {
1650 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1651 }
1652
1653 @Override
1654 public void onAccountsUpdated(Account[] accounts) {
1655 // TODO: watch for package upgrades to invalidate cache; see 7206643
1656 mAuthenticatorHelper.updateAuthDescriptions(this);
1657 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1658 invalidateHeaders();
1659 }
1660
1661 public static void requestHomeNotice() {
1662 sShowNoHomeNotice = true;
1663 }
1664
1665 /**
1666 * Default value for {@link Header#id Header.id} indicating that no
1667 * identifier value is set. All other values (including those below -1)
1668 * are valid.
1669 */
1670 private static final long HEADER_ID_UNDEFINED = -1;
1671
1672 /**
1673 * Description of a single Header item that the user can select.
1674 */
1675 static final class Header implements Parcelable {
1676 /**
1677 * Identifier for this header, to correlate with a new list when
1678 * it is updated. The default value is
1679 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1680 * @attr ref android.R.styleable#PreferenceHeader_id
1681 */
1682 public long id = HEADER_ID_UNDEFINED;
1683
1684 /**
1685 * Resource ID of title of the header that is shown to the user.
1686 * @attr ref android.R.styleable#PreferenceHeader_title
1687 */
1688 public int titleRes;
1689
1690 /**
1691 * Title of the header that is shown to the user.
1692 * @attr ref android.R.styleable#PreferenceHeader_title
1693 */
1694 public CharSequence title;
1695
1696 /**
1697 * Resource ID of optional summary describing what this header controls.
1698 * @attr ref android.R.styleable#PreferenceHeader_summary
1699 */
1700 public int summaryRes;
1701
1702 /**
1703 * Optional summary describing what this header controls.
1704 * @attr ref android.R.styleable#PreferenceHeader_summary
1705 */
1706 public CharSequence summary;
1707
1708 /**
1709 * Optional icon resource to show for this header.
1710 * @attr ref android.R.styleable#PreferenceHeader_icon
1711 */
1712 public int iconRes;
1713
1714 /**
1715 * Full class name of the fragment to display when this header is
1716 * selected.
1717 * @attr ref android.R.styleable#PreferenceHeader_fragment
1718 */
1719 public String fragment;
1720
1721 /**
1722 * Optional arguments to supply to the fragment when it is
1723 * instantiated.
1724 */
1725 public Bundle fragmentArguments;
1726
1727 /**
1728 * Intent to launch when the preference is selected.
1729 */
1730 public Intent intent;
1731
1732 /**
1733 * Optional additional data for use by subclasses of the activity
1734 */
1735 public Bundle extras;
1736
1737 public Header() {
1738 // Empty
1739 }
1740
1741 /**
1742 * Return the currently set title. If {@link #titleRes} is set,
1743 * this resource is loaded from <var>res</var> and returned. Otherwise
1744 * {@link #title} is returned.
1745 */
1746 public CharSequence getTitle(Resources res) {
1747 if (titleRes != 0) {
1748 return res.getText(titleRes);
1749 }
1750 return title;
1751 }
1752
1753 /**
1754 * Return the currently set summary. If {@link #summaryRes} is set,
1755 * this resource is loaded from <var>res</var> and returned. Otherwise
1756 * {@link #summary} is returned.
1757 */
1758 public CharSequence getSummary(Resources res) {
1759 if (summaryRes != 0) {
1760 return res.getText(summaryRes);
1761 }
1762 return summary;
1763 }
1764
1765 @Override
1766 public int describeContents() {
1767 return 0;
1768 }
1769
1770 @Override
1771 public void writeToParcel(Parcel dest, int flags) {
1772 dest.writeLong(id);
1773 dest.writeInt(titleRes);
1774 TextUtils.writeToParcel(title, dest, flags);
1775 dest.writeInt(summaryRes);
1776 TextUtils.writeToParcel(summary, dest, flags);
1777 dest.writeInt(iconRes);
1778 dest.writeString(fragment);
1779 dest.writeBundle(fragmentArguments);
1780 if (intent != null) {
1781 dest.writeInt(1);
1782 intent.writeToParcel(dest, flags);
1783 } else {
1784 dest.writeInt(0);
1785 }
1786 dest.writeBundle(extras);
1787 }
1788
1789 public void readFromParcel(Parcel in) {
1790 id = in.readLong();
1791 titleRes = in.readInt();
1792 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1793 summaryRes = in.readInt();
1794 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1795 iconRes = in.readInt();
1796 fragment = in.readString();
1797 fragmentArguments = in.readBundle();
1798 if (in.readInt() != 0) {
1799 intent = Intent.CREATOR.createFromParcel(in);
1800 }
1801 extras = in.readBundle();
1802 }
1803
1804 Header(Parcel in) {
1805 readFromParcel(in);
1806 }
1807
1808 public static final Creator<Header> CREATOR = new Creator<Header>() {
1809 public Header createFromParcel(Parcel source) {
1810 return new Header(source);
1811 }
1812 public Header[] newArray(int size) {
1813 return new Header[size];
1814 }
1815 };
1816 }
1817}