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