blob: 213bc181e9770d04742fe55f2209b06f88508779 [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 Meglioca915662014-02-06 15:46:19 -0800281 DashboardSummary.class.getName(),
282 ApnSettings.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800283 };
284
285 private SharedPreferences mDevelopmentPreferences;
286 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
287
288 // TODO: Update Call Settings based on airplane mode state.
289
290 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
291
292 private AuthenticatorHelper mAuthenticatorHelper;
293 private boolean mListeningToAccountUpdates;
294
295 private Button mNextButton;
296
297 private boolean mBatteryPresent = true;
298 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
299
300 @Override
301 public void onReceive(Context context, Intent intent) {
302 String action = intent.getAction();
303 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
304 boolean batteryPresent = Utils.isBatteryPresent(intent);
305
306 if (mBatteryPresent != batteryPresent) {
307 mBatteryPresent = batteryPresent;
308 invalidateHeaders();
309 }
310 }
311 }
312 };
313
314 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800315 private HeaderAdapter mHeaderAdapter;
316
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800317 static private class TitlePair extends Pair<Integer, CharSequence> implements Parcelable {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800318
319 public TitlePair(Integer first, CharSequence second) {
320 super(first, second);
321 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800322
323 @Override
324 public int describeContents() {
325 return 0;
326 }
327
328 @Override
329 public void writeToParcel(Parcel dest, int flags) {
330 dest.writeInt(first);
331 TextUtils.writeToParcel(second, dest, flags);
332 }
333
334 TitlePair(Parcel in) {
335 super(in.readInt(), TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
336 }
337
338 public static final Creator<TitlePair> CREATOR = new Creator<TitlePair>() {
339 public TitlePair createFromParcel(Parcel source) {
340 return new TitlePair(source);
341 }
342 public TitlePair[] newArray(int size) {
343 return new TitlePair[size];
344 }
345 };
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800346 }
347
348 private final ArrayList<TitlePair> mTitleStack = new ArrayList<TitlePair>();
349
350 private DrawerLayout mDrawerLayout;
351 private ListView mDrawer;
352 private ActionBarDrawerToggle mDrawerToggle;
353 private ActionBar mActionBar;
354
355 private static final int MSG_BUILD_HEADERS = 1;
356 private Handler mHandler = new Handler() {
357 @Override
358 public void handleMessage(Message msg) {
359 switch (msg.what) {
360 case MSG_BUILD_HEADERS: {
361 mHeaders.clear();
362 onBuildHeaders(mHeaders);
363 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800364 if (mCurrentHeader != null) {
365 Header mappedHeader = findBestMatchingHeader(mCurrentHeader, mHeaders);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800366 if (mappedHeader != null) {
367 setSelectedHeader(mappedHeader);
368 }
369 }
370 } break;
371 }
372 }
373 };
374
375 @Override
376 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
377 // Override the fragment title for Wallpaper settings
378 int titleRes = pref.getTitleRes();
379 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
380 titleRes = R.string.wallpaper_settings_fragment_title;
381 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
382 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
383 if (UserManager.get(this).isLinkedUser()) {
384 titleRes = R.string.profile_info_settings_title;
385 } else {
386 titleRes = R.string.user_info_settings_title;
387 }
388 }
389 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
390 null, 0);
391 return true;
392 }
393
394 @Override
395 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
396 return false;
397 }
398
399 private class DrawerListener implements DrawerLayout.DrawerListener {
400 @Override
401 public void onDrawerOpened(View drawerView) {
402 mDrawerToggle.onDrawerOpened(drawerView);
403 }
404
405 @Override
406 public void onDrawerClosed(View drawerView) {
407 mDrawerToggle.onDrawerClosed(drawerView);
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800408 // Cannot process clicks when the App is finishing
409 if (isFinishing()) return;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800410 onHeaderClick(mSelectedHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800411 }
412
413 @Override
414 public void onDrawerSlide(View drawerView, float slideOffset) {
415 mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
416 }
417
418 @Override
419 public void onDrawerStateChanged(int newState) {
420 mDrawerToggle.onDrawerStateChanged(newState);
421 }
422 }
423
424 private class DrawerItemClickListener implements ListView.OnItemClickListener {
425 @Override
426 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
427 mDrawerLayout.closeDrawer(mDrawer);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000428 onListItemClick((ListView)parent, view, position, id);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800429 }
430 }
431
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800432 private Header findBestMatchingHeader(Header current, ArrayList<Header> from) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800433 ArrayList<Header> matches = new ArrayList<Header>();
434 for (int j=0; j<from.size(); j++) {
435 Header oh = from.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800436 if (current == oh || (current.id != HEADER_ID_UNDEFINED && current.id == oh.id)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800437 // Must be this one.
438 matches.clear();
439 matches.add(oh);
440 break;
441 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800442 if (current.fragment != null) {
443 if (current.fragment.equals(oh.fragment)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800444 matches.add(oh);
445 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800446 } else if (current.intent != null) {
447 if (current.intent.equals(oh.intent)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800448 matches.add(oh);
449 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800450 } else if (current.title != null) {
451 if (current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800452 matches.add(oh);
453 }
454 }
455 }
456 final int NM = matches.size();
457 if (NM == 1) {
458 return matches.get(0);
459 } else if (NM > 1) {
460 for (int j=0; j<NM; j++) {
461 Header oh = matches.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800462 if (current.fragmentArguments != null &&
463 current.fragmentArguments.equals(oh.fragmentArguments)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800464 return oh;
465 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800466 if (current.extras != null && current.extras.equals(oh.extras)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800467 return oh;
468 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800469 if (current.title != null && current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800470 return oh;
471 }
472 }
473 }
474 return null;
475 }
476
477 private void invalidateHeaders() {
478 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
479 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
480 }
481 }
482
483 @Override
484 protected void onPostCreate(Bundle savedInstanceState) {
485 super.onPostCreate(savedInstanceState);
486
487 // Sync the toggle state after onRestoreInstanceState has occurred.
488 if (mDrawerToggle != null) {
489 mDrawerToggle.syncState();
490 }
491 }
492
493 @Override
494 public void onConfigurationChanged(Configuration newConfig) {
495 super.onConfigurationChanged(newConfig);
496 if (mDrawerToggle != null) {
497 mDrawerToggle.onConfigurationChanged(newConfig);
498 }
499 }
500
501 @Override
502 public boolean onOptionsItemSelected(MenuItem item) {
503 if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
504 return true;
505 }
506 return super.onOptionsItemSelected(item);
507 }
508
509 @Override
510 protected void onCreate(Bundle savedInstanceState) {
511 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
512 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
513 }
514
515 mAuthenticatorHelper = new AuthenticatorHelper();
516 mAuthenticatorHelper.updateAuthDescriptions(this);
517 mAuthenticatorHelper.onAccountsUpdated(this, null);
518
519 DevicePolicyManager dpm =
520 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
521 mHeaderAdapter= new HeaderAdapter(this, getHeaders(), mAuthenticatorHelper, dpm);
522
523 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
524 Context.MODE_PRIVATE);
525
526 getMetaData();
527
528 super.onCreate(savedInstanceState);
529
530 setContentView(R.layout.settings_main);
531
532 getFragmentManager().addOnBackStackChangedListener(this);
533
534 mActionBar = getActionBar();
535 if (mActionBar != null) {
536 mActionBar.setDisplayHomeAsUpEnabled(true);
537 mActionBar.setHomeButtonEnabled(true);
538
539 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800540
541 mDrawer = (ListView) findViewById(R.id.headers_drawer);
542 mDrawer.setAdapter(mHeaderAdapter);
543 mDrawer.setOnItemClickListener(new DrawerItemClickListener());
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000544 mDrawer.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800545
546 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
547 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
548 }
549
550 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
551 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
552
553 if (savedInstanceState != null) {
554 // We are restarting from a previous saved state; used that to
555 // initialize, instead of starting fresh.
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800556
557 ArrayList<TitlePair> titles =
558 savedInstanceState.getParcelableArrayList(SAVE_KEY_TITLES_TAG);
559 if (titles != null) {
560 mTitleStack.addAll(titles);
561 }
562 final int lastTitle = mTitleStack.size() - 1;
563 if (lastTitle >= 0) {
564 final TitlePair last = mTitleStack.get(lastTitle);
565 setTitleFromPair(last);
566 }
567
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800568 ArrayList<Header> headers =
569 savedInstanceState.getParcelableArrayList(SAVE_KEY_HEADERS_TAG);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800570 if (headers != null) {
571 mHeaders.addAll(headers);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800572 int curHeader = savedInstanceState.getInt(SAVE_KEY_CURRENT_HEADER_TAG,
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800573 (int) HEADER_ID_UNDEFINED);
574 if (curHeader >= 0 && curHeader < mHeaders.size()) {
575 setSelectedHeader(mHeaders.get(curHeader));
576 }
577 }
578
579 } else {
580 if (initialFragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000581 // If we are just showing a fragment, we want to run in
582 // new fragment mode, but don't need to compute and show
583 // the headers.
584 switchToHeader(initialFragment, initialArguments, true);
585
586 final int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
587 if (initialTitle != 0) {
588 setTitle(getText(initialTitle));
589 }
590 } else {
591 // We need to try to build the headers.
592 onBuildHeaders(mHeaders);
593
594 // If there are headers, then at this point we need to show
595 // them and, depending on the screen, we may also show in-line
596 // the currently selected preference fragment.
597 if (mHeaders.size() > 0) {
598 if (initialFragment == null) {
599 Header h = onGetInitialHeader();
600 switchToHeader(h, false);
601 } else {
602 switchToHeader(initialFragment, initialArguments, false);
603 }
604 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800605 }
606 }
607
608 // see if we should show Back/Next buttons
609 Intent intent = getIntent();
610 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
611
612 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
613 if (buttonBar != null) {
614 buttonBar.setVisibility(View.VISIBLE);
615
616 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
617 backButton.setOnClickListener(new OnClickListener() {
618 public void onClick(View v) {
619 setResult(RESULT_CANCELED);
620 finish();
621 }
622 });
623 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
624 skipButton.setOnClickListener(new OnClickListener() {
625 public void onClick(View v) {
626 setResult(RESULT_OK);
627 finish();
628 }
629 });
630 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
631 mNextButton.setOnClickListener(new OnClickListener() {
632 public void onClick(View v) {
633 setResult(RESULT_OK);
634 finish();
635 }
636 });
637
638 // set our various button parameters
639 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
640 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
641 if (TextUtils.isEmpty(buttonText)) {
642 mNextButton.setVisibility(View.GONE);
643 }
644 else {
645 mNextButton.setText(buttonText);
646 }
647 }
648 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
649 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
650 if (TextUtils.isEmpty(buttonText)) {
651 backButton.setVisibility(View.GONE);
652 }
653 else {
654 backButton.setText(buttonText);
655 }
656 }
657 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
658 skipButton.setVisibility(View.VISIBLE);
659 }
660 }
661 }
662
663 if (!onIsHidingHeaders()) {
664 highlightHeader(mTopLevelHeaderId);
665 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800666 }
667
668 @Override
669 public void onBackStackChanged() {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000670 final int count = getFragmentManager().getBackStackEntryCount() + 1;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800671 TitlePair pair = null;
672 int last;
673 while (mTitleStack.size() > count) {
674 last = mTitleStack.size() - 1;
675 pair = mTitleStack.remove(last);
676 }
677 // Check if we go back
678 if (pair != null) {
679 int size = mTitleStack.size();
680 if (size > 0) {
681 last = mTitleStack.size() - 1;
682 pair = mTitleStack.get(last);
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800683 setTitleFromPair(pair);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800684 }
685 }
686 }
687
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800688 private void setTitleFromPair(TitlePair pair) {
689 final CharSequence title;
690 if (pair.first > 0) {
691 title = getText(pair.first);
692 } else {
693 title = pair.second;
694 }
695 setTitle(title);
696 }
697
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800698 /**
699 * Returns the Header list
700 */
701 private List<Header> getHeaders() {
702 return mHeaders;
703 }
704
705 @Override
706 protected void onSaveInstanceState(Bundle outState) {
707 super.onSaveInstanceState(outState);
708
709 if (mHeaders.size() > 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800710 outState.putParcelableArrayList(SAVE_KEY_HEADERS_TAG, mHeaders);
711 if (mCurrentHeader != null) {
712 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800713 if (index >= 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800714 outState.putInt(SAVE_KEY_CURRENT_HEADER_TAG, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800715 }
716 }
717 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800718
719 if (mTitleStack.size() > 0) {
720 outState.putParcelableList(SAVE_KEY_TITLES_TAG, mTitleStack);
721 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800722 }
723
724 @Override
725 public void onResume() {
726 super.onResume();
727
728 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
729 @Override
730 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
731 invalidateHeaders();
732 }
733 };
734 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
735 mDevelopmentPreferencesListener);
736
737 mHeaderAdapter.resume();
738 invalidateHeaders();
739
740 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800741
742 mDrawerLayout.setDrawerListener(new DrawerListener());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800743 }
744
745 @Override
746 public void onPause() {
747 super.onPause();
748
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800749 mDrawerLayout.setDrawerListener(null);
750
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800751 unregisterReceiver(mBatteryInfoReceiver);
752
753 mHeaderAdapter.pause();
754
755 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
756 mDevelopmentPreferencesListener);
757
758 mDevelopmentPreferencesListener = null;
759 }
760
761 @Override
762 public void onDestroy() {
763 super.onDestroy();
764 if (mListeningToAccountUpdates) {
765 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
766 }
767 }
768
769 /**
770 * @hide
771 */
772 protected boolean isValidFragment(String fragmentName) {
773 // Almost all fragments are wrapped in this,
774 // except for a few that have their own activities.
775 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
776 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
777 }
778 return false;
779 }
780
781 /**
782 * When in two-pane mode, switch to the fragment pane to show the given
783 * preference fragment.
784 *
785 * @param header The new header to display.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000786 * @param validate true means that the fragment's Header needs to be validated
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800787 */
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000788 private void switchToHeader(Header header, boolean validate) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800789 if (mCurrentHeader == header) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000790 // This is the header we are currently displaying. Just make sure
791 // to pop the stack up to its root state.
792 getFragmentManager().popBackStack(BACK_STACK_PREFS,
793 FragmentManager.POP_BACK_STACK_INCLUSIVE);
794 } else {
795 mTitleStack.clear();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800796 if (header.fragment == null) {
797 throw new IllegalStateException("can't switch to header that has no fragment");
798 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000799 switchToHeaderInner(header.fragment, header.fragmentArguments, validate);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800800 setSelectedHeader(header);
801 final CharSequence title;
802 if (header.fragment.equals("com.android.settings.dashboard.DashboardSummary")) {
803 title = getResources().getString(R.string.settings_label);
804 } else {
805 title = header.getTitle(getResources());
806 }
807 final TitlePair pair = new TitlePair(0, title);
808 mTitleStack.add(pair);
809 setTitle(title);
810 }
811 }
812
813 private void setSelectedHeader(Header header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800814 mCurrentHeader = header;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800815 int index = mHeaders.indexOf(header);
816 if (mDrawer != null) {
817 if (index >= 0) {
818 mDrawer.setItemChecked(index, true);
819 } else {
820 mDrawer.clearChoices();
821 }
822 }
823 }
824
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000825 public Header onGetInitialHeader() {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800826 String fragmentClass = getStartingFragmentClass(super.getIntent());
827 if (fragmentClass != null) {
828 Header header = new Header();
829 header.fragment = fragmentClass;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000830 header.title = getTitle();
831 header.fragmentArguments = getIntent().getExtras();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800832 return header;
833 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000834
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800835 return mFirstHeader;
836 }
837
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000838 /**
839 * When in two-pane mode, switch the fragment pane to show the given
840 * preference fragment.
841 *
842 * @param fragmentName The name of the fragment to display.
843 * @param args Optional arguments to supply to the fragment.
844 * @param validate true means that the fragment's Header needs to be validated
845 */
846 private void switchToHeader(String fragmentName, Bundle args, boolean validate) {
847 setSelectedHeader(null);
848 switchToHeaderInner(fragmentName, args, validate);
849 }
850
851 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate) {
852 getFragmentManager().popBackStack(BACK_STACK_PREFS,
853 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800854 if (validate && !isValidFragment(fragmentName)) {
855 throw new IllegalArgumentException("Invalid fragment for this activity: "
856 + fragmentName);
857 }
858 Fragment f = Fragment.instantiate(this, fragmentName, args);
859 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800860 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800861 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800862 transaction.commitAllowingStateLoss();
863 }
864
865 @Override
866 public void onNewIntent(Intent intent) {
867 super.onNewIntent(intent);
868
869 // If it is not launched from history, then reset to top-level
870 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
871 if (mDrawer != null) {
872 mDrawer.setSelectionFromTop(0, 0);
873 }
874 }
875 }
876
877 /**
878 * Called to determine whether the header list should be hidden.
879 * The default implementation returns the
880 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
881 * This is set to false, for example, when the activity is being re-launched
882 * to show a particular preference activity.
883 */
884 public boolean onIsHidingHeaders() {
885 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
886 }
887
888 private void highlightHeader(int id) {
889 if (id != 0) {
890 Integer index = mHeaderIndexMap.get(id);
891 if (index != null && mDrawer != null) {
892 mDrawer.setItemChecked(index, true);
893 if (mDrawer.getVisibility() == View.VISIBLE) {
894 mDrawer.smoothScrollToPosition(index);
895 }
896 }
897 }
898 }
899
900 @Override
901 public Intent getIntent() {
902 Intent superIntent = super.getIntent();
903 String startingFragment = getStartingFragmentClass(superIntent);
904 // This is called from super.onCreate, isMultiPane() is not yet reliable
905 // Do not use onIsHidingHeaders either, which relies itself on this method
906 if (startingFragment != null) {
907 Intent modIntent = new Intent(superIntent);
908 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
909 Bundle args = superIntent.getExtras();
910 if (args != null) {
911 args = new Bundle(args);
912 } else {
913 args = new Bundle();
914 }
915 args.putParcelable("intent", superIntent);
916 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
917 return modIntent;
918 }
919 return superIntent;
920 }
921
922 /**
923 * Checks if the component name in the intent is different from the Settings class and
924 * returns the class name to load as a fragment.
925 */
926 private String getStartingFragmentClass(Intent intent) {
927 if (mFragmentClass != null) return mFragmentClass;
928
929 String intentClass = intent.getComponent().getClassName();
930 if (intentClass.equals(getClass().getName())) return null;
931
932 if ("com.android.settings.ManageApplications".equals(intentClass)
933 || "com.android.settings.RunningServices".equals(intentClass)
934 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
935 // Old names of manage apps.
936 intentClass = com.android.settings.applications.ManageApplications.class.getName();
937 }
938
939 return intentClass;
940 }
941
942 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000943 * Start a new fragment containing a preference panel. If the preferences
944 * are being displayed in multi-pane mode, the given fragment class will
945 * be instantiated and placed in the appropriate pane. If running in
946 * single-pane mode, a new activity will be launched in which to show the
947 * fragment.
948 *
949 * @param fragmentClass Full name of the class implementing the fragment.
950 * @param args Any desired arguments to supply to the fragment.
951 * @param titleRes Optional resource identifier of the title of this
952 * fragment.
953 * @param titleText Optional text of the title of this fragment.
954 * @param resultTo Optional fragment that result data should be sent to.
955 * If non-null, resultTo.onActivityResult() will be called when this
956 * preference panel is done. The launched panel must use
957 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
958 * @param resultRequestCode If resultTo is non-null, this is the caller's
959 * request code to be received with the resut.
960 */
961 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
962 CharSequence titleText, Fragment resultTo,
963 int resultRequestCode) {
964 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
965 }
966
967 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800968 * Called by a preference panel fragment to finish itself.
969 *
970 * @param caller The fragment that is asking to be finished.
971 * @param resultCode Optional result code to send back to the original
972 * launching fragment.
973 * @param resultData Optional result data to send back to the original
974 * launching fragment.
975 */
976 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
977 setResult(resultCode, resultData);
978 }
979
980 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000981 * Start a new fragment.
982 *
983 * @param fragment The fragment to start
984 * @param push If true, the current fragment will be pushed onto the back stack. If false,
985 * the current fragment will be replaced.
986 */
987 public void startPreferenceFragment(Fragment fragment, boolean push) {
988 FragmentTransaction transaction = getFragmentManager().beginTransaction();
989 transaction.replace(R.id.prefs, fragment);
990 if (push) {
991 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
992 transaction.addToBackStack(BACK_STACK_PREFS);
993 } else {
994 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
995 }
996 transaction.commitAllowingStateLoss();
997 }
998
999 /**
1000 * Start a new fragment.
1001 *
1002 * @param fragmentName The name of the fragment to display.
1003 * @param args Optional arguments to supply to the fragment.
1004 * @param resultTo Option fragment that should receive the result of
1005 * the activity launch.
1006 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
1007 * report the result.
1008 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
1009 * valid one then it will be used to get the title. Otherwise the titleText
1010 * argument will be used as the title.
1011 * @param titleText string to display for the title of.
1012 */
1013 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
1014 int resultRequestCode, int titleRes, CharSequence titleText) {
1015 Fragment f = Fragment.instantiate(this, fragmentName, args);
1016 if (resultTo != null) {
1017 f.setTargetFragment(resultTo, resultRequestCode);
1018 }
1019 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1020 transaction.replace(R.id.prefs, f);
1021 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1022 transaction.addToBackStack(BACK_STACK_PREFS);
1023 transaction.commitAllowingStateLoss();
1024
1025 final TitlePair pair;
1026 final CharSequence cs;
1027 if (titleRes != 0) {
1028 pair = new TitlePair(titleRes, null);
1029 cs = getText(titleRes);
1030 } else {
1031 pair = new TitlePair(0, titleText);
1032 cs = titleText;
1033 }
1034 setTitle(cs);
1035 mTitleStack.add(pair);
1036 }
1037
1038 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001039 * Called when the activity needs its list of headers build. By
1040 * implementing this and adding at least one item to the list, you
1041 * will cause the activity to run in its modern fragment mode. Note
1042 * that this function may not always be called; for example, if the
1043 * activity has been asked to display a particular fragment without
1044 * the header list, there is no need to build the headers.
1045 *
1046 * <p>Typical implementations will use {@link #loadHeadersFromResource}
1047 * to fill in the list from a resource.
1048 *
1049 * @param headers The list in which to place the headers.
1050 */
1051 private void onBuildHeaders(List<Header> headers) {
1052 loadHeadersFromResource(R.xml.settings_headers, headers);
1053 updateHeaderList(headers);
1054 }
1055
1056 /**
1057 * Parse the given XML file as a header description, adding each
1058 * parsed Header into the target list.
1059 *
1060 * @param resid The XML resource to load and parse.
1061 * @param target The list in which the parsed headers should be placed.
1062 */
1063 private void loadHeadersFromResource(int resid, List<Header> target) {
1064 XmlResourceParser parser = null;
1065 try {
1066 parser = getResources().getXml(resid);
1067 AttributeSet attrs = Xml.asAttributeSet(parser);
1068
1069 int type;
1070 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1071 && type != XmlPullParser.START_TAG) {
1072 // Parse next until start tag is found
1073 }
1074
1075 String nodeName = parser.getName();
1076 if (!"preference-headers".equals(nodeName)) {
1077 throw new RuntimeException(
1078 "XML document must start with <preference-headers> tag; found"
1079 + nodeName + " at " + parser.getPositionDescription());
1080 }
1081
1082 Bundle curBundle = null;
1083
1084 final int outerDepth = parser.getDepth();
1085 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1086 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1087 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1088 continue;
1089 }
1090
1091 nodeName = parser.getName();
1092 if ("header".equals(nodeName)) {
1093 Header header = new Header();
1094
1095 TypedArray sa = obtainStyledAttributes(
1096 attrs, com.android.internal.R.styleable.PreferenceHeader);
1097 header.id = sa.getResourceId(
1098 com.android.internal.R.styleable.PreferenceHeader_id,
1099 (int)HEADER_ID_UNDEFINED);
1100 TypedValue tv = sa.peekValue(
1101 com.android.internal.R.styleable.PreferenceHeader_title);
1102 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1103 if (tv.resourceId != 0) {
1104 header.titleRes = tv.resourceId;
1105 } else {
1106 header.title = tv.string;
1107 }
1108 }
1109 tv = sa.peekValue(
1110 com.android.internal.R.styleable.PreferenceHeader_summary);
1111 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1112 if (tv.resourceId != 0) {
1113 header.summaryRes = tv.resourceId;
1114 } else {
1115 header.summary = tv.string;
1116 }
1117 }
1118 header.iconRes = sa.getResourceId(
1119 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1120 header.fragment = sa.getString(
1121 com.android.internal.R.styleable.PreferenceHeader_fragment);
1122 sa.recycle();
1123
1124 if (curBundle == null) {
1125 curBundle = new Bundle();
1126 }
1127
1128 final int innerDepth = parser.getDepth();
1129 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1130 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1131 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1132 continue;
1133 }
1134
1135 String innerNodeName = parser.getName();
1136 if (innerNodeName.equals("extra")) {
1137 getResources().parseBundleExtra("extra", attrs, curBundle);
1138 XmlUtils.skipCurrentTag(parser);
1139
1140 } else if (innerNodeName.equals("intent")) {
1141 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1142
1143 } else {
1144 XmlUtils.skipCurrentTag(parser);
1145 }
1146 }
1147
1148 if (curBundle.size() > 0) {
1149 header.fragmentArguments = curBundle;
1150 curBundle = null;
1151 }
1152
1153 target.add(header);
1154 } else {
1155 XmlUtils.skipCurrentTag(parser);
1156 }
1157 }
1158
1159 } catch (XmlPullParserException e) {
1160 throw new RuntimeException("Error parsing headers", e);
1161 } catch (IOException e) {
1162 throw new RuntimeException("Error parsing headers", e);
1163 } finally {
1164 if (parser != null) parser.close();
1165 }
1166 }
1167
1168 private void updateHeaderList(List<Header> target) {
1169 final boolean showDev = mDevelopmentPreferences.getBoolean(
1170 DevelopmentSettings.PREF_SHOW,
1171 android.os.Build.TYPE.equals("eng"));
1172 int i = 0;
1173
1174 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1175 mHeaderIndexMap.clear();
1176 while (i < target.size()) {
1177 Header header = target.get(i);
1178 // Ids are integers, so downcasting
1179 int id = (int) header.id;
1180 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1181 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1182 } else if (id == R.id.wifi_settings) {
1183 // Remove WiFi Settings if WiFi service is not available.
1184 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1185 target.remove(i);
1186 }
1187 } else if (id == R.id.bluetooth_settings) {
1188 // Remove Bluetooth Settings if Bluetooth service is not available.
1189 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1190 target.remove(i);
1191 }
1192 } else if (id == R.id.data_usage_settings) {
1193 // Remove data usage when kernel module not enabled
1194 final INetworkManagementService netManager = INetworkManagementService.Stub
1195 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1196 try {
1197 if (!netManager.isBandwidthControlEnabled()) {
1198 target.remove(i);
1199 }
1200 } catch (RemoteException e) {
1201 // ignored
1202 }
1203 } else if (id == R.id.battery_settings) {
1204 // Remove battery settings when battery is not available. (e.g. TV)
1205
1206 if (!mBatteryPresent) {
1207 target.remove(i);
1208 }
1209 } else if (id == R.id.account_settings) {
1210 int headerIndex = i + 1;
1211 i = insertAccountsHeaders(target, headerIndex);
1212 } else if (id == R.id.home_settings) {
1213 if (!updateHomeSettingHeaders(header)) {
1214 target.remove(i);
1215 }
1216 } else if (id == R.id.user_settings) {
1217 if (!UserHandle.MU_ENABLED
1218 || !UserManager.supportsMultipleUsers()
1219 || Utils.isMonkeyRunning()) {
1220 target.remove(i);
1221 }
1222 } else if (id == R.id.nfc_payment_settings) {
1223 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1224 target.remove(i);
1225 } else {
1226 // Only show if NFC is on and we have the HCE feature
1227 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1228 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1229 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1230 target.remove(i);
1231 }
1232 }
1233 } else if (id == R.id.development_settings) {
1234 if (!showDev) {
1235 target.remove(i);
1236 }
1237 } else if (id == R.id.account_add) {
1238 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1239 target.remove(i);
1240 }
1241 }
1242
1243 if (i < target.size() && target.get(i) == header
1244 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1245 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1246 target.remove(i);
1247 }
1248
1249 // Increment if the current one wasn't removed by the Utils code.
1250 if (i < target.size() && target.get(i) == header) {
1251 // Hold on to the first header, when we need to reset to the top-level
1252 if (mFirstHeader == null &&
1253 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1254 mFirstHeader = header;
1255 }
1256 mHeaderIndexMap.put(id, i);
1257 i++;
1258 }
1259 }
1260 }
1261
1262 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1263 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1264 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1265 for (String accountType : accountTypes) {
1266 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1267 if (label == null) {
1268 continue;
1269 }
1270
1271 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1272 boolean skipToAccount = accounts.length == 1
1273 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1274 Header accHeader = new Header();
1275 accHeader.title = label;
1276 if (accHeader.extras == null) {
1277 accHeader.extras = new Bundle();
1278 }
1279 if (skipToAccount) {
1280 accHeader.fragment = AccountSyncSettings.class.getName();
1281 accHeader.fragmentArguments = new Bundle();
1282 // Need this for the icon
1283 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1284 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1285 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1286 accounts[0]);
1287 } else {
1288 accHeader.fragment = ManageAccountsSettings.class.getName();
1289 accHeader.fragmentArguments = new Bundle();
1290 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1291 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1292 accountType);
1293 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1294 label.toString());
1295 }
1296 accountHeaders.add(accHeader);
1297 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1298 }
1299
1300 // Sort by label
1301 Collections.sort(accountHeaders, new Comparator<Header>() {
1302 @Override
1303 public int compare(Header h1, Header h2) {
1304 return h1.title.toString().compareTo(h2.title.toString());
1305 }
1306 });
1307
1308 for (Header header : accountHeaders) {
1309 target.add(headerIndex++, header);
1310 }
1311 if (!mListeningToAccountUpdates) {
1312 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1313 mListeningToAccountUpdates = true;
1314 }
1315 return headerIndex;
1316 }
1317
1318 private boolean updateHomeSettingHeaders(Header header) {
1319 // Once we decide to show Home settings, keep showing it forever
1320 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1321 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1322 return true;
1323 }
1324
1325 try {
1326 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1327 getPackageManager().getHomeActivities(homeApps);
1328 if (homeApps.size() < 2) {
1329 // When there's only one available home app, omit this settings
1330 // category entirely at the top level UI. If the user just
1331 // uninstalled the penultimate home app candidiate, we also
1332 // now tell them about why they aren't seeing 'Home' in the list.
1333 if (sShowNoHomeNotice) {
1334 sShowNoHomeNotice = false;
1335 NoHomeDialogFragment.show(this);
1336 }
1337 return false;
1338 } else {
1339 // Okay, we're allowing the Home settings category. Tell it, when
1340 // invoked via this front door, that we'll need to be told about the
1341 // case when the user uninstalls all but one home app.
1342 if (header.fragmentArguments == null) {
1343 header.fragmentArguments = new Bundle();
1344 }
1345 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1346 }
1347 } catch (Exception e) {
1348 // Can't look up the home activity; bail on configuring the icon
1349 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1350 }
1351
1352 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1353 return true;
1354 }
1355
1356 private void getMetaData() {
1357 try {
1358 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1359 PackageManager.GET_META_DATA);
1360 if (ai == null || ai.metaData == null) return;
1361 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1362 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1363 } catch (NameNotFoundException nnfe) {
1364 // No recovery
1365 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1366 }
1367 }
1368
1369 // give subclasses access to the Next button
1370 public boolean hasNextButton() {
1371 return mNextButton != null;
1372 }
1373
1374 public Button getNextButton() {
1375 return mNextButton;
1376 }
1377
1378 public static class NoHomeDialogFragment extends DialogFragment {
1379 public static void show(Activity parent) {
1380 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1381 dialog.show(parent.getFragmentManager(), null);
1382 }
1383
1384 @Override
1385 public Dialog onCreateDialog(Bundle savedInstanceState) {
1386 return new AlertDialog.Builder(getActivity())
1387 .setMessage(R.string.only_one_home_message)
1388 .setPositiveButton(android.R.string.ok, null)
1389 .create();
1390 }
1391 }
1392
1393 private static class HeaderAdapter extends ArrayAdapter<Header> {
1394 static final int HEADER_TYPE_CATEGORY = 0;
1395 static final int HEADER_TYPE_NORMAL = 1;
1396 static final int HEADER_TYPE_SWITCH = 2;
1397 static final int HEADER_TYPE_BUTTON = 3;
1398 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1399
1400 private final WifiEnabler mWifiEnabler;
1401 private final BluetoothEnabler mBluetoothEnabler;
1402 private AuthenticatorHelper mAuthHelper;
1403 private DevicePolicyManager mDevicePolicyManager;
1404
1405 private static class HeaderViewHolder {
1406 ImageView mIcon;
1407 TextView mTitle;
1408 TextView mSummary;
1409 Switch mSwitch;
1410 ImageButton mButton;
1411 View mDivider;
1412 }
1413
1414 private LayoutInflater mInflater;
1415
1416 static int getHeaderType(Header header) {
1417 if (header.fragment == null && header.intent == null) {
1418 return HEADER_TYPE_CATEGORY;
1419 } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
1420 return HEADER_TYPE_SWITCH;
1421 } else if (header.id == R.id.security_settings) {
1422 return HEADER_TYPE_BUTTON;
1423 } else {
1424 return HEADER_TYPE_NORMAL;
1425 }
1426 }
1427
1428 @Override
1429 public int getItemViewType(int position) {
1430 Header header = getItem(position);
1431 return getHeaderType(header);
1432 }
1433
1434 @Override
1435 public boolean areAllItemsEnabled() {
1436 return false; // because of categories
1437 }
1438
1439 @Override
1440 public boolean isEnabled(int position) {
1441 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1442 }
1443
1444 @Override
1445 public int getViewTypeCount() {
1446 return HEADER_TYPE_COUNT;
1447 }
1448
1449 @Override
1450 public boolean hasStableIds() {
1451 return true;
1452 }
1453
1454 public HeaderAdapter(Context context, List<Header> objects,
1455 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1456 super(context, 0, objects);
1457
1458 mAuthHelper = authenticatorHelper;
1459 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1460
1461 // Temp Switches provided as placeholder until the adapter replaces these with actual
1462 // Switches inflated from their layouts. Must be done before adapter is set in super
1463 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1464 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1465 mDevicePolicyManager = dpm;
1466 }
1467
1468 @Override
1469 public View getView(int position, View convertView, ViewGroup parent) {
1470 HeaderViewHolder holder;
1471 Header header = getItem(position);
1472 int headerType = getHeaderType(header);
1473 View view = null;
1474
1475 if (convertView == null) {
1476 holder = new HeaderViewHolder();
1477 switch (headerType) {
1478 case HEADER_TYPE_CATEGORY:
1479 view = new TextView(getContext(), null,
1480 android.R.attr.listSeparatorTextViewStyle);
1481 holder.mTitle = (TextView) view;
1482 break;
1483
1484 case HEADER_TYPE_SWITCH:
1485 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1486 false);
1487 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1488 holder.mTitle = (TextView)
1489 view.findViewById(com.android.internal.R.id.title);
1490 holder.mSummary = (TextView)
1491 view.findViewById(com.android.internal.R.id.summary);
1492 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1493 break;
1494
1495 case HEADER_TYPE_BUTTON:
1496 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1497 false);
1498 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1499 holder.mTitle = (TextView)
1500 view.findViewById(com.android.internal.R.id.title);
1501 holder.mSummary = (TextView)
1502 view.findViewById(com.android.internal.R.id.summary);
1503 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1504 holder.mDivider = view.findViewById(R.id.divider);
1505 break;
1506
1507 case HEADER_TYPE_NORMAL:
1508 view = mInflater.inflate(
1509 R.layout.preference_header_item, parent,
1510 false);
1511 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1512 holder.mTitle = (TextView)
1513 view.findViewById(com.android.internal.R.id.title);
1514 holder.mSummary = (TextView)
1515 view.findViewById(com.android.internal.R.id.summary);
1516 break;
1517 }
1518 view.setTag(holder);
1519 } else {
1520 view = convertView;
1521 holder = (HeaderViewHolder) view.getTag();
1522 }
1523
1524 // All view fields must be updated every time, because the view may be recycled
1525 switch (headerType) {
1526 case HEADER_TYPE_CATEGORY:
1527 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1528 break;
1529
1530 case HEADER_TYPE_SWITCH:
1531 // Would need a different treatment if the main menu had more switches
1532 if (header.id == R.id.wifi_settings) {
1533 mWifiEnabler.setSwitch(holder.mSwitch);
1534 } else {
1535 mBluetoothEnabler.setSwitch(holder.mSwitch);
1536 }
1537 updateCommonHeaderView(header, holder);
1538 break;
1539
1540 case HEADER_TYPE_BUTTON:
1541 if (header.id == R.id.security_settings) {
1542 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1543 if (hasCert) {
1544 holder.mButton.setVisibility(View.VISIBLE);
1545 holder.mDivider.setVisibility(View.VISIBLE);
1546 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1547 if (isManaged) {
1548 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1549 } else {
1550 holder.mButton.setImageResource(
1551 android.R.drawable.stat_notify_error);
1552 }
1553 holder.mButton.setOnClickListener(new OnClickListener() {
1554 @Override
1555 public void onClick(View v) {
1556 Intent intent = new Intent(
1557 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1558 getContext().startActivity(intent);
1559 }
1560 });
1561 } else {
1562 holder.mButton.setVisibility(View.GONE);
1563 holder.mDivider.setVisibility(View.GONE);
1564 }
1565 }
1566 updateCommonHeaderView(header, holder);
1567 break;
1568
1569 case HEADER_TYPE_NORMAL:
1570 updateCommonHeaderView(header, holder);
1571 break;
1572 }
1573
1574 return view;
1575 }
1576
1577 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1578 if (header.extras != null
1579 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1580 String accType = header.extras.getString(
1581 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1582 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1583 setHeaderIcon(holder, icon);
1584 } else {
1585 holder.mIcon.setImageResource(header.iconRes);
1586 }
1587 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1588 CharSequence summary = header.getSummary(getContext().getResources());
1589 if (!TextUtils.isEmpty(summary)) {
1590 holder.mSummary.setVisibility(View.VISIBLE);
1591 holder.mSummary.setText(summary);
1592 } else {
1593 holder.mSummary.setVisibility(View.GONE);
1594 }
1595 }
1596
1597 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1598 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1599 lp.width = getContext().getResources().getDimensionPixelSize(
1600 R.dimen.header_icon_width);
1601 lp.height = lp.width;
1602 holder.mIcon.setLayoutParams(lp);
1603 holder.mIcon.setImageDrawable(icon);
1604 }
1605
1606 public void resume() {
1607 mWifiEnabler.resume();
1608 mBluetoothEnabler.resume();
1609 }
1610
1611 public void pause() {
1612 mWifiEnabler.pause();
1613 mBluetoothEnabler.pause();
1614 }
1615 }
1616
1617 private void onListItemClick(ListView l, View v, int position, long id) {
1618 if (!isResumed()) {
1619 return;
1620 }
1621 Object item = mHeaderAdapter.getItem(position);
1622 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001623 mSelectedHeader = (Header) item;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001624 }
1625 }
1626
1627 /**
1628 * Called when the user selects an item in the header list. The default
1629 * implementation will call either
Fabrice Di Megliodc77b732014-02-04 12:41:30 -08001630 * {@link #startWithFragment(String, android.os.Bundle, android.app.Fragment, int, int, CharSequence)}
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001631 * or {@link #switchToHeader(com.android.settings.SettingsActivity.Header, boolean)}
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001632 * as appropriate.
1633 *
1634 * @param header The header that was selected.
1635 */
1636 private void onHeaderClick(Header header) {
1637 if (header == null) return;
1638 if (header.fragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001639 switchToHeader(header, false);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001640 } else if (header.intent != null) {
1641 startActivity(header.intent);
1642 }
1643 }
1644
1645 @Override
1646 public boolean shouldUpRecreateTask(Intent targetIntent) {
1647 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1648 }
1649
1650 @Override
1651 public void onAccountsUpdated(Account[] accounts) {
1652 // TODO: watch for package upgrades to invalidate cache; see 7206643
1653 mAuthenticatorHelper.updateAuthDescriptions(this);
1654 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1655 invalidateHeaders();
1656 }
1657
1658 public static void requestHomeNotice() {
1659 sShowNoHomeNotice = true;
1660 }
1661
1662 /**
1663 * Default value for {@link Header#id Header.id} indicating that no
1664 * identifier value is set. All other values (including those below -1)
1665 * are valid.
1666 */
1667 private static final long HEADER_ID_UNDEFINED = -1;
1668
1669 /**
1670 * Description of a single Header item that the user can select.
1671 */
1672 static final class Header implements Parcelable {
1673 /**
1674 * Identifier for this header, to correlate with a new list when
1675 * it is updated. The default value is
1676 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1677 * @attr ref android.R.styleable#PreferenceHeader_id
1678 */
1679 public long id = HEADER_ID_UNDEFINED;
1680
1681 /**
1682 * Resource ID of title of the header that is shown to the user.
1683 * @attr ref android.R.styleable#PreferenceHeader_title
1684 */
1685 public int titleRes;
1686
1687 /**
1688 * Title of the header that is shown to the user.
1689 * @attr ref android.R.styleable#PreferenceHeader_title
1690 */
1691 public CharSequence title;
1692
1693 /**
1694 * Resource ID of optional summary describing what this header controls.
1695 * @attr ref android.R.styleable#PreferenceHeader_summary
1696 */
1697 public int summaryRes;
1698
1699 /**
1700 * Optional summary describing what this header controls.
1701 * @attr ref android.R.styleable#PreferenceHeader_summary
1702 */
1703 public CharSequence summary;
1704
1705 /**
1706 * Optional icon resource to show for this header.
1707 * @attr ref android.R.styleable#PreferenceHeader_icon
1708 */
1709 public int iconRes;
1710
1711 /**
1712 * Full class name of the fragment to display when this header is
1713 * selected.
1714 * @attr ref android.R.styleable#PreferenceHeader_fragment
1715 */
1716 public String fragment;
1717
1718 /**
1719 * Optional arguments to supply to the fragment when it is
1720 * instantiated.
1721 */
1722 public Bundle fragmentArguments;
1723
1724 /**
1725 * Intent to launch when the preference is selected.
1726 */
1727 public Intent intent;
1728
1729 /**
1730 * Optional additional data for use by subclasses of the activity
1731 */
1732 public Bundle extras;
1733
1734 public Header() {
1735 // Empty
1736 }
1737
1738 /**
1739 * Return the currently set title. If {@link #titleRes} is set,
1740 * this resource is loaded from <var>res</var> and returned. Otherwise
1741 * {@link #title} is returned.
1742 */
1743 public CharSequence getTitle(Resources res) {
1744 if (titleRes != 0) {
1745 return res.getText(titleRes);
1746 }
1747 return title;
1748 }
1749
1750 /**
1751 * Return the currently set summary. If {@link #summaryRes} is set,
1752 * this resource is loaded from <var>res</var> and returned. Otherwise
1753 * {@link #summary} is returned.
1754 */
1755 public CharSequence getSummary(Resources res) {
1756 if (summaryRes != 0) {
1757 return res.getText(summaryRes);
1758 }
1759 return summary;
1760 }
1761
1762 @Override
1763 public int describeContents() {
1764 return 0;
1765 }
1766
1767 @Override
1768 public void writeToParcel(Parcel dest, int flags) {
1769 dest.writeLong(id);
1770 dest.writeInt(titleRes);
1771 TextUtils.writeToParcel(title, dest, flags);
1772 dest.writeInt(summaryRes);
1773 TextUtils.writeToParcel(summary, dest, flags);
1774 dest.writeInt(iconRes);
1775 dest.writeString(fragment);
1776 dest.writeBundle(fragmentArguments);
1777 if (intent != null) {
1778 dest.writeInt(1);
1779 intent.writeToParcel(dest, flags);
1780 } else {
1781 dest.writeInt(0);
1782 }
1783 dest.writeBundle(extras);
1784 }
1785
1786 public void readFromParcel(Parcel in) {
1787 id = in.readLong();
1788 titleRes = in.readInt();
1789 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1790 summaryRes = in.readInt();
1791 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1792 iconRes = in.readInt();
1793 fragment = in.readString();
1794 fragmentArguments = in.readBundle();
1795 if (in.readInt() != 0) {
1796 intent = Intent.CREATOR.createFromParcel(in);
1797 }
1798 extras = in.readBundle();
1799 }
1800
1801 Header(Parcel in) {
1802 readFromParcel(in);
1803 }
1804
1805 public static final Creator<Header> CREATOR = new Creator<Header>() {
1806 public Header createFromParcel(Parcel source) {
1807 return new Header(source);
1808 }
1809 public Header[] newArray(int size) {
1810 return new Header[size];
1811 }
1812 };
1813 }
1814}