blob: a362ee622f600cce633174303ab3a6bf76911375 [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) {
Fabrice Di Megliodc77b732014-02-04 12:41:30 -0800927 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800928 }
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
Fabrice Di Megliodc77b732014-02-04 12:41:30 -0800968 * the activity launch.
969 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
970 * report the result.
971 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
972 * valid one then it will be used to get the title. Otherwise the titleText
973 * argument will be used as the title.
974 * @param titleText string to display for the title of.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800975 */
976 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
Fabrice Di Megliodc77b732014-02-04 12:41:30 -0800977 int resultRequestCode, int titleRes, CharSequence titleText) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800978 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
Fabrice Di Megliodc77b732014-02-04 12:41:30 -0800988 final TitlePair pair;
989 final CharSequence cs;
990 if (titleRes != 0) {
991 pair = new TitlePair(titleRes, null);
992 cs = getText(titleRes);
993 } else {
994 pair = new TitlePair(0, titleText);
995 cs = titleText;
996 }
997 setTitle(cs);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800998 mTitleStack.add(pair);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800999 }
1000
1001 /**
1002 * Called when the activity needs its list of headers build. By
1003 * implementing this and adding at least one item to the list, you
1004 * will cause the activity to run in its modern fragment mode. Note
1005 * that this function may not always be called; for example, if the
1006 * activity has been asked to display a particular fragment without
1007 * the header list, there is no need to build the headers.
1008 *
1009 * <p>Typical implementations will use {@link #loadHeadersFromResource}
1010 * to fill in the list from a resource.
1011 *
1012 * @param headers The list in which to place the headers.
1013 */
1014 private void onBuildHeaders(List<Header> headers) {
1015 loadHeadersFromResource(R.xml.settings_headers, headers);
1016 updateHeaderList(headers);
1017 }
1018
1019 /**
1020 * Parse the given XML file as a header description, adding each
1021 * parsed Header into the target list.
1022 *
1023 * @param resid The XML resource to load and parse.
1024 * @param target The list in which the parsed headers should be placed.
1025 */
1026 private void loadHeadersFromResource(int resid, List<Header> target) {
1027 XmlResourceParser parser = null;
1028 try {
1029 parser = getResources().getXml(resid);
1030 AttributeSet attrs = Xml.asAttributeSet(parser);
1031
1032 int type;
1033 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1034 && type != XmlPullParser.START_TAG) {
1035 // Parse next until start tag is found
1036 }
1037
1038 String nodeName = parser.getName();
1039 if (!"preference-headers".equals(nodeName)) {
1040 throw new RuntimeException(
1041 "XML document must start with <preference-headers> tag; found"
1042 + nodeName + " at " + parser.getPositionDescription());
1043 }
1044
1045 Bundle curBundle = null;
1046
1047 final int outerDepth = parser.getDepth();
1048 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1049 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1050 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1051 continue;
1052 }
1053
1054 nodeName = parser.getName();
1055 if ("header".equals(nodeName)) {
1056 Header header = new Header();
1057
1058 TypedArray sa = obtainStyledAttributes(
1059 attrs, com.android.internal.R.styleable.PreferenceHeader);
1060 header.id = sa.getResourceId(
1061 com.android.internal.R.styleable.PreferenceHeader_id,
1062 (int)HEADER_ID_UNDEFINED);
1063 TypedValue tv = sa.peekValue(
1064 com.android.internal.R.styleable.PreferenceHeader_title);
1065 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1066 if (tv.resourceId != 0) {
1067 header.titleRes = tv.resourceId;
1068 } else {
1069 header.title = tv.string;
1070 }
1071 }
1072 tv = sa.peekValue(
1073 com.android.internal.R.styleable.PreferenceHeader_summary);
1074 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1075 if (tv.resourceId != 0) {
1076 header.summaryRes = tv.resourceId;
1077 } else {
1078 header.summary = tv.string;
1079 }
1080 }
1081 header.iconRes = sa.getResourceId(
1082 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1083 header.fragment = sa.getString(
1084 com.android.internal.R.styleable.PreferenceHeader_fragment);
1085 sa.recycle();
1086
1087 if (curBundle == null) {
1088 curBundle = new Bundle();
1089 }
1090
1091 final int innerDepth = parser.getDepth();
1092 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1093 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1094 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1095 continue;
1096 }
1097
1098 String innerNodeName = parser.getName();
1099 if (innerNodeName.equals("extra")) {
1100 getResources().parseBundleExtra("extra", attrs, curBundle);
1101 XmlUtils.skipCurrentTag(parser);
1102
1103 } else if (innerNodeName.equals("intent")) {
1104 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1105
1106 } else {
1107 XmlUtils.skipCurrentTag(parser);
1108 }
1109 }
1110
1111 if (curBundle.size() > 0) {
1112 header.fragmentArguments = curBundle;
1113 curBundle = null;
1114 }
1115
1116 target.add(header);
1117 } else {
1118 XmlUtils.skipCurrentTag(parser);
1119 }
1120 }
1121
1122 } catch (XmlPullParserException e) {
1123 throw new RuntimeException("Error parsing headers", e);
1124 } catch (IOException e) {
1125 throw new RuntimeException("Error parsing headers", e);
1126 } finally {
1127 if (parser != null) parser.close();
1128 }
1129 }
1130
1131 private void updateHeaderList(List<Header> target) {
1132 final boolean showDev = mDevelopmentPreferences.getBoolean(
1133 DevelopmentSettings.PREF_SHOW,
1134 android.os.Build.TYPE.equals("eng"));
1135 int i = 0;
1136
1137 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1138 mHeaderIndexMap.clear();
1139 while (i < target.size()) {
1140 Header header = target.get(i);
1141 // Ids are integers, so downcasting
1142 int id = (int) header.id;
1143 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1144 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1145 } else if (id == R.id.wifi_settings) {
1146 // Remove WiFi Settings if WiFi service is not available.
1147 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1148 target.remove(i);
1149 }
1150 } else if (id == R.id.bluetooth_settings) {
1151 // Remove Bluetooth Settings if Bluetooth service is not available.
1152 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1153 target.remove(i);
1154 }
1155 } else if (id == R.id.data_usage_settings) {
1156 // Remove data usage when kernel module not enabled
1157 final INetworkManagementService netManager = INetworkManagementService.Stub
1158 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1159 try {
1160 if (!netManager.isBandwidthControlEnabled()) {
1161 target.remove(i);
1162 }
1163 } catch (RemoteException e) {
1164 // ignored
1165 }
1166 } else if (id == R.id.battery_settings) {
1167 // Remove battery settings when battery is not available. (e.g. TV)
1168
1169 if (!mBatteryPresent) {
1170 target.remove(i);
1171 }
1172 } else if (id == R.id.account_settings) {
1173 int headerIndex = i + 1;
1174 i = insertAccountsHeaders(target, headerIndex);
1175 } else if (id == R.id.home_settings) {
1176 if (!updateHomeSettingHeaders(header)) {
1177 target.remove(i);
1178 }
1179 } else if (id == R.id.user_settings) {
1180 if (!UserHandle.MU_ENABLED
1181 || !UserManager.supportsMultipleUsers()
1182 || Utils.isMonkeyRunning()) {
1183 target.remove(i);
1184 }
1185 } else if (id == R.id.nfc_payment_settings) {
1186 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1187 target.remove(i);
1188 } else {
1189 // Only show if NFC is on and we have the HCE feature
1190 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1191 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1192 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1193 target.remove(i);
1194 }
1195 }
1196 } else if (id == R.id.development_settings) {
1197 if (!showDev) {
1198 target.remove(i);
1199 }
1200 } else if (id == R.id.account_add) {
1201 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1202 target.remove(i);
1203 }
1204 }
1205
1206 if (i < target.size() && target.get(i) == header
1207 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1208 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1209 target.remove(i);
1210 }
1211
1212 // Increment if the current one wasn't removed by the Utils code.
1213 if (i < target.size() && target.get(i) == header) {
1214 // Hold on to the first header, when we need to reset to the top-level
1215 if (mFirstHeader == null &&
1216 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1217 mFirstHeader = header;
1218 }
1219 mHeaderIndexMap.put(id, i);
1220 i++;
1221 }
1222 }
1223 }
1224
1225 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1226 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1227 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1228 for (String accountType : accountTypes) {
1229 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1230 if (label == null) {
1231 continue;
1232 }
1233
1234 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1235 boolean skipToAccount = accounts.length == 1
1236 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1237 Header accHeader = new Header();
1238 accHeader.title = label;
1239 if (accHeader.extras == null) {
1240 accHeader.extras = new Bundle();
1241 }
1242 if (skipToAccount) {
1243 accHeader.fragment = AccountSyncSettings.class.getName();
1244 accHeader.fragmentArguments = new Bundle();
1245 // Need this for the icon
1246 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1247 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1248 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1249 accounts[0]);
1250 } else {
1251 accHeader.fragment = ManageAccountsSettings.class.getName();
1252 accHeader.fragmentArguments = new Bundle();
1253 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1254 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1255 accountType);
1256 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1257 label.toString());
1258 }
1259 accountHeaders.add(accHeader);
1260 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1261 }
1262
1263 // Sort by label
1264 Collections.sort(accountHeaders, new Comparator<Header>() {
1265 @Override
1266 public int compare(Header h1, Header h2) {
1267 return h1.title.toString().compareTo(h2.title.toString());
1268 }
1269 });
1270
1271 for (Header header : accountHeaders) {
1272 target.add(headerIndex++, header);
1273 }
1274 if (!mListeningToAccountUpdates) {
1275 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1276 mListeningToAccountUpdates = true;
1277 }
1278 return headerIndex;
1279 }
1280
1281 private boolean updateHomeSettingHeaders(Header header) {
1282 // Once we decide to show Home settings, keep showing it forever
1283 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1284 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1285 return true;
1286 }
1287
1288 try {
1289 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1290 getPackageManager().getHomeActivities(homeApps);
1291 if (homeApps.size() < 2) {
1292 // When there's only one available home app, omit this settings
1293 // category entirely at the top level UI. If the user just
1294 // uninstalled the penultimate home app candidiate, we also
1295 // now tell them about why they aren't seeing 'Home' in the list.
1296 if (sShowNoHomeNotice) {
1297 sShowNoHomeNotice = false;
1298 NoHomeDialogFragment.show(this);
1299 }
1300 return false;
1301 } else {
1302 // Okay, we're allowing the Home settings category. Tell it, when
1303 // invoked via this front door, that we'll need to be told about the
1304 // case when the user uninstalls all but one home app.
1305 if (header.fragmentArguments == null) {
1306 header.fragmentArguments = new Bundle();
1307 }
1308 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1309 }
1310 } catch (Exception e) {
1311 // Can't look up the home activity; bail on configuring the icon
1312 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1313 }
1314
1315 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1316 return true;
1317 }
1318
1319 private void getMetaData() {
1320 try {
1321 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1322 PackageManager.GET_META_DATA);
1323 if (ai == null || ai.metaData == null) return;
1324 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1325 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1326 } catch (NameNotFoundException nnfe) {
1327 // No recovery
1328 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1329 }
1330 }
1331
1332 // give subclasses access to the Next button
1333 public boolean hasNextButton() {
1334 return mNextButton != null;
1335 }
1336
1337 public Button getNextButton() {
1338 return mNextButton;
1339 }
1340
1341 public static class NoHomeDialogFragment extends DialogFragment {
1342 public static void show(Activity parent) {
1343 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1344 dialog.show(parent.getFragmentManager(), null);
1345 }
1346
1347 @Override
1348 public Dialog onCreateDialog(Bundle savedInstanceState) {
1349 return new AlertDialog.Builder(getActivity())
1350 .setMessage(R.string.only_one_home_message)
1351 .setPositiveButton(android.R.string.ok, null)
1352 .create();
1353 }
1354 }
1355
1356 private static class HeaderAdapter extends ArrayAdapter<Header> {
1357 static final int HEADER_TYPE_CATEGORY = 0;
1358 static final int HEADER_TYPE_NORMAL = 1;
1359 static final int HEADER_TYPE_SWITCH = 2;
1360 static final int HEADER_TYPE_BUTTON = 3;
1361 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1362
1363 private final WifiEnabler mWifiEnabler;
1364 private final BluetoothEnabler mBluetoothEnabler;
1365 private AuthenticatorHelper mAuthHelper;
1366 private DevicePolicyManager mDevicePolicyManager;
1367
1368 private static class HeaderViewHolder {
1369 ImageView mIcon;
1370 TextView mTitle;
1371 TextView mSummary;
1372 Switch mSwitch;
1373 ImageButton mButton;
1374 View mDivider;
1375 }
1376
1377 private LayoutInflater mInflater;
1378
1379 static int getHeaderType(Header header) {
1380 if (header.fragment == null && header.intent == null) {
1381 return HEADER_TYPE_CATEGORY;
1382 } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
1383 return HEADER_TYPE_SWITCH;
1384 } else if (header.id == R.id.security_settings) {
1385 return HEADER_TYPE_BUTTON;
1386 } else {
1387 return HEADER_TYPE_NORMAL;
1388 }
1389 }
1390
1391 @Override
1392 public int getItemViewType(int position) {
1393 Header header = getItem(position);
1394 return getHeaderType(header);
1395 }
1396
1397 @Override
1398 public boolean areAllItemsEnabled() {
1399 return false; // because of categories
1400 }
1401
1402 @Override
1403 public boolean isEnabled(int position) {
1404 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1405 }
1406
1407 @Override
1408 public int getViewTypeCount() {
1409 return HEADER_TYPE_COUNT;
1410 }
1411
1412 @Override
1413 public boolean hasStableIds() {
1414 return true;
1415 }
1416
1417 public HeaderAdapter(Context context, List<Header> objects,
1418 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1419 super(context, 0, objects);
1420
1421 mAuthHelper = authenticatorHelper;
1422 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1423
1424 // Temp Switches provided as placeholder until the adapter replaces these with actual
1425 // Switches inflated from their layouts. Must be done before adapter is set in super
1426 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1427 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1428 mDevicePolicyManager = dpm;
1429 }
1430
1431 @Override
1432 public View getView(int position, View convertView, ViewGroup parent) {
1433 HeaderViewHolder holder;
1434 Header header = getItem(position);
1435 int headerType = getHeaderType(header);
1436 View view = null;
1437
1438 if (convertView == null) {
1439 holder = new HeaderViewHolder();
1440 switch (headerType) {
1441 case HEADER_TYPE_CATEGORY:
1442 view = new TextView(getContext(), null,
1443 android.R.attr.listSeparatorTextViewStyle);
1444 holder.mTitle = (TextView) view;
1445 break;
1446
1447 case HEADER_TYPE_SWITCH:
1448 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1449 false);
1450 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1451 holder.mTitle = (TextView)
1452 view.findViewById(com.android.internal.R.id.title);
1453 holder.mSummary = (TextView)
1454 view.findViewById(com.android.internal.R.id.summary);
1455 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1456 break;
1457
1458 case HEADER_TYPE_BUTTON:
1459 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1460 false);
1461 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1462 holder.mTitle = (TextView)
1463 view.findViewById(com.android.internal.R.id.title);
1464 holder.mSummary = (TextView)
1465 view.findViewById(com.android.internal.R.id.summary);
1466 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1467 holder.mDivider = view.findViewById(R.id.divider);
1468 break;
1469
1470 case HEADER_TYPE_NORMAL:
1471 view = mInflater.inflate(
1472 R.layout.preference_header_item, parent,
1473 false);
1474 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1475 holder.mTitle = (TextView)
1476 view.findViewById(com.android.internal.R.id.title);
1477 holder.mSummary = (TextView)
1478 view.findViewById(com.android.internal.R.id.summary);
1479 break;
1480 }
1481 view.setTag(holder);
1482 } else {
1483 view = convertView;
1484 holder = (HeaderViewHolder) view.getTag();
1485 }
1486
1487 // All view fields must be updated every time, because the view may be recycled
1488 switch (headerType) {
1489 case HEADER_TYPE_CATEGORY:
1490 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1491 break;
1492
1493 case HEADER_TYPE_SWITCH:
1494 // Would need a different treatment if the main menu had more switches
1495 if (header.id == R.id.wifi_settings) {
1496 mWifiEnabler.setSwitch(holder.mSwitch);
1497 } else {
1498 mBluetoothEnabler.setSwitch(holder.mSwitch);
1499 }
1500 updateCommonHeaderView(header, holder);
1501 break;
1502
1503 case HEADER_TYPE_BUTTON:
1504 if (header.id == R.id.security_settings) {
1505 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1506 if (hasCert) {
1507 holder.mButton.setVisibility(View.VISIBLE);
1508 holder.mDivider.setVisibility(View.VISIBLE);
1509 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1510 if (isManaged) {
1511 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1512 } else {
1513 holder.mButton.setImageResource(
1514 android.R.drawable.stat_notify_error);
1515 }
1516 holder.mButton.setOnClickListener(new OnClickListener() {
1517 @Override
1518 public void onClick(View v) {
1519 Intent intent = new Intent(
1520 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1521 getContext().startActivity(intent);
1522 }
1523 });
1524 } else {
1525 holder.mButton.setVisibility(View.GONE);
1526 holder.mDivider.setVisibility(View.GONE);
1527 }
1528 }
1529 updateCommonHeaderView(header, holder);
1530 break;
1531
1532 case HEADER_TYPE_NORMAL:
1533 updateCommonHeaderView(header, holder);
1534 break;
1535 }
1536
1537 return view;
1538 }
1539
1540 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1541 if (header.extras != null
1542 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1543 String accType = header.extras.getString(
1544 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1545 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1546 setHeaderIcon(holder, icon);
1547 } else {
1548 holder.mIcon.setImageResource(header.iconRes);
1549 }
1550 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1551 CharSequence summary = header.getSummary(getContext().getResources());
1552 if (!TextUtils.isEmpty(summary)) {
1553 holder.mSummary.setVisibility(View.VISIBLE);
1554 holder.mSummary.setText(summary);
1555 } else {
1556 holder.mSummary.setVisibility(View.GONE);
1557 }
1558 }
1559
1560 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1561 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1562 lp.width = getContext().getResources().getDimensionPixelSize(
1563 R.dimen.header_icon_width);
1564 lp.height = lp.width;
1565 holder.mIcon.setLayoutParams(lp);
1566 holder.mIcon.setImageDrawable(icon);
1567 }
1568
1569 public void resume() {
1570 mWifiEnabler.resume();
1571 mBluetoothEnabler.resume();
1572 }
1573
1574 public void pause() {
1575 mWifiEnabler.pause();
1576 mBluetoothEnabler.pause();
1577 }
1578 }
1579
1580 private void onListItemClick(ListView l, View v, int position, long id) {
1581 if (!isResumed()) {
1582 return;
1583 }
1584 Object item = mHeaderAdapter.getItem(position);
1585 if (item instanceof Header) {
1586 mCurrentHeader = (Header) item;
1587 }
1588 }
1589
1590 /**
1591 * Called when the user selects an item in the header list. The default
1592 * implementation will call either
Fabrice Di Megliodc77b732014-02-04 12:41:30 -08001593 * {@link #startWithFragment(String, android.os.Bundle, android.app.Fragment, int, int, CharSequence)}
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001594 * or {@link #switchToHeader(com.android.settings.SettingsActivity.Header, boolean)}
1595 * as appropriate.
1596 *
1597 * @param header The header that was selected.
1598 */
1599 private void onHeaderClick(Header header) {
1600 if (header == null) return;
1601 if (header.fragment != null) {
1602 switchToHeader(header, false);
1603 } else if (header.intent != null) {
1604 startActivity(header.intent);
1605 }
1606 }
1607
1608 @Override
1609 public boolean shouldUpRecreateTask(Intent targetIntent) {
1610 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1611 }
1612
1613 @Override
1614 public void onAccountsUpdated(Account[] accounts) {
1615 // TODO: watch for package upgrades to invalidate cache; see 7206643
1616 mAuthenticatorHelper.updateAuthDescriptions(this);
1617 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1618 invalidateHeaders();
1619 }
1620
1621 public static void requestHomeNotice() {
1622 sShowNoHomeNotice = true;
1623 }
1624
1625 /**
1626 * Default value for {@link Header#id Header.id} indicating that no
1627 * identifier value is set. All other values (including those below -1)
1628 * are valid.
1629 */
1630 private static final long HEADER_ID_UNDEFINED = -1;
1631
1632 /**
1633 * Description of a single Header item that the user can select.
1634 */
1635 static final class Header implements Parcelable {
1636 /**
1637 * Identifier for this header, to correlate with a new list when
1638 * it is updated. The default value is
1639 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1640 * @attr ref android.R.styleable#PreferenceHeader_id
1641 */
1642 public long id = HEADER_ID_UNDEFINED;
1643
1644 /**
1645 * Resource ID of title of the header that is shown to the user.
1646 * @attr ref android.R.styleable#PreferenceHeader_title
1647 */
1648 public int titleRes;
1649
1650 /**
1651 * Title of the header that is shown to the user.
1652 * @attr ref android.R.styleable#PreferenceHeader_title
1653 */
1654 public CharSequence title;
1655
1656 /**
1657 * Resource ID of optional summary describing what this header controls.
1658 * @attr ref android.R.styleable#PreferenceHeader_summary
1659 */
1660 public int summaryRes;
1661
1662 /**
1663 * Optional summary describing what this header controls.
1664 * @attr ref android.R.styleable#PreferenceHeader_summary
1665 */
1666 public CharSequence summary;
1667
1668 /**
1669 * Optional icon resource to show for this header.
1670 * @attr ref android.R.styleable#PreferenceHeader_icon
1671 */
1672 public int iconRes;
1673
1674 /**
1675 * Full class name of the fragment to display when this header is
1676 * selected.
1677 * @attr ref android.R.styleable#PreferenceHeader_fragment
1678 */
1679 public String fragment;
1680
1681 /**
1682 * Optional arguments to supply to the fragment when it is
1683 * instantiated.
1684 */
1685 public Bundle fragmentArguments;
1686
1687 /**
1688 * Intent to launch when the preference is selected.
1689 */
1690 public Intent intent;
1691
1692 /**
1693 * Optional additional data for use by subclasses of the activity
1694 */
1695 public Bundle extras;
1696
1697 public Header() {
1698 // Empty
1699 }
1700
1701 /**
1702 * Return the currently set title. If {@link #titleRes} is set,
1703 * this resource is loaded from <var>res</var> and returned. Otherwise
1704 * {@link #title} is returned.
1705 */
1706 public CharSequence getTitle(Resources res) {
1707 if (titleRes != 0) {
1708 return res.getText(titleRes);
1709 }
1710 return title;
1711 }
1712
1713 /**
1714 * Return the currently set summary. If {@link #summaryRes} is set,
1715 * this resource is loaded from <var>res</var> and returned. Otherwise
1716 * {@link #summary} is returned.
1717 */
1718 public CharSequence getSummary(Resources res) {
1719 if (summaryRes != 0) {
1720 return res.getText(summaryRes);
1721 }
1722 return summary;
1723 }
1724
1725 @Override
1726 public int describeContents() {
1727 return 0;
1728 }
1729
1730 @Override
1731 public void writeToParcel(Parcel dest, int flags) {
1732 dest.writeLong(id);
1733 dest.writeInt(titleRes);
1734 TextUtils.writeToParcel(title, dest, flags);
1735 dest.writeInt(summaryRes);
1736 TextUtils.writeToParcel(summary, dest, flags);
1737 dest.writeInt(iconRes);
1738 dest.writeString(fragment);
1739 dest.writeBundle(fragmentArguments);
1740 if (intent != null) {
1741 dest.writeInt(1);
1742 intent.writeToParcel(dest, flags);
1743 } else {
1744 dest.writeInt(0);
1745 }
1746 dest.writeBundle(extras);
1747 }
1748
1749 public void readFromParcel(Parcel in) {
1750 id = in.readLong();
1751 titleRes = in.readInt();
1752 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1753 summaryRes = in.readInt();
1754 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1755 iconRes = in.readInt();
1756 fragment = in.readString();
1757 fragmentArguments = in.readBundle();
1758 if (in.readInt() != 0) {
1759 intent = Intent.CREATOR.createFromParcel(in);
1760 }
1761 extras = in.readBundle();
1762 }
1763
1764 Header(Parcel in) {
1765 readFromParcel(in);
1766 }
1767
1768 public static final Creator<Header> CREATOR = new Creator<Header>() {
1769 public Header createFromParcel(Parcel source) {
1770 return new Header(source);
1771 }
1772 public Header[] newArray(int size) {
1773 return new Header[size];
1774 }
1775 };
1776 }
1777}