blob: 8bfde063c9ab3a2fbdfd6983c48feb7ae7200ebd [file] [log] [blame]
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
21import android.accounts.OnAccountsUpdateListener;
22import android.app.ActionBar;
23import android.app.Activity;
24import android.app.Fragment;
25import android.app.FragmentManager;
26import android.app.FragmentTransaction;
27import android.app.AlertDialog;
28import android.app.Dialog;
29import android.app.DialogFragment;
30import android.app.admin.DevicePolicyManager;
31import android.content.BroadcastReceiver;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.SharedPreferences;
36import android.content.pm.ActivityInfo;
37import android.content.pm.PackageManager;
38import android.content.pm.PackageManager.NameNotFoundException;
39import android.content.pm.ResolveInfo;
40import android.content.res.Configuration;
41import android.content.res.Resources;
42import android.content.res.TypedArray;
43import android.content.res.XmlResourceParser;
44import android.graphics.drawable.Drawable;
45import android.nfc.NfcAdapter;
46import android.os.Bundle;
47import android.os.Handler;
48import android.os.INetworkManagementService;
49import android.os.Message;
50import android.os.Parcel;
51import android.os.Parcelable;
52import android.os.RemoteException;
53import android.os.ServiceManager;
54import android.os.UserHandle;
55import android.os.UserManager;
56import android.preference.Preference;
57import android.preference.PreferenceFragment;
58import android.preference.PreferenceManager;
59import android.preference.PreferenceScreen;
60import android.support.v4.app.ActionBarDrawerToggle;
61import android.support.v4.widget.DrawerLayout;
62import android.text.TextUtils;
63import android.util.AttributeSet;
64import android.util.Log;
65import android.util.Pair;
66import android.util.TypedValue;
67import android.util.Xml;
68import android.view.LayoutInflater;
69import android.view.MenuItem;
70import android.view.View;
71import android.view.View.OnClickListener;
72import android.view.ViewGroup;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080073import android.widget.AdapterView;
74import android.widget.ArrayAdapter;
75import android.widget.Button;
76import android.widget.ImageButton;
77import android.widget.ImageView;
78import android.widget.ListView;
79import android.widget.Switch;
80import android.widget.TextView;
81
82import com.android.internal.util.ArrayUtils;
83import com.android.internal.util.XmlUtils;
84import com.android.settings.accessibility.AccessibilitySettings;
85import com.android.settings.accessibility.CaptionPropertiesFragment;
86import com.android.settings.accounts.AccountSyncSettings;
87import com.android.settings.accounts.AuthenticatorHelper;
88import com.android.settings.accounts.ChooseAccountFragment;
89import com.android.settings.accounts.ManageAccountsSettings;
90import com.android.settings.applications.ManageApplications;
91import com.android.settings.applications.ProcessStatsUi;
92import com.android.settings.bluetooth.BluetoothEnabler;
93import com.android.settings.bluetooth.BluetoothSettings;
94import com.android.settings.dashboard.DashboardSummary;
95import com.android.settings.deviceinfo.Memory;
96import com.android.settings.deviceinfo.UsbSettings;
97import com.android.settings.fuelgauge.PowerUsageSummary;
98import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
99import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
100import com.android.settings.inputmethod.SpellCheckersSettings;
101import com.android.settings.inputmethod.UserDictionaryList;
102import com.android.settings.location.LocationSettings;
103import com.android.settings.nfc.AndroidBeam;
104import com.android.settings.nfc.PaymentSettings;
105import com.android.settings.print.PrintJobSettingsFragment;
106import com.android.settings.print.PrintSettingsFragment;
107import com.android.settings.tts.TextToSpeechSettings;
108import com.android.settings.users.UserSettings;
109import com.android.settings.vpn2.VpnSettings;
110import com.android.settings.wfd.WifiDisplaySettings;
111import com.android.settings.wifi.AdvancedWifiSettings;
112import com.android.settings.wifi.WifiEnabler;
113import com.android.settings.wifi.WifiSettings;
114import com.android.settings.wifi.p2p.WifiP2pSettings;
115import org.xmlpull.v1.XmlPullParser;
116import org.xmlpull.v1.XmlPullParserException;
117
118import java.io.IOException;
119import java.util.ArrayList;
120import java.util.Collections;
121import java.util.Comparator;
122import java.util.HashMap;
123import java.util.List;
124
125public class SettingsActivity extends Activity
126 implements PreferenceManager.OnPreferenceTreeClickListener,
127 PreferenceFragment.OnPreferenceStartFragmentCallback,
128 ButtonBarHandler, OnAccountsUpdateListener, FragmentManager.OnBackStackChangedListener {
129
130 private static final String LOG_TAG = "Settings";
131
132 // Constants for state save/restore
133 private static final String HEADERS_TAG = ":settings:headers";
134 private static final String CUR_HEADER_TAG = ":settings:cur_header";
135
136 /**
137 * When starting this activity, the invoking Intent can contain this extra
138 * string to specify which fragment should be initially displayed.
139 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
140 * will call isValidFragment() to confirm that the fragment class name is valid for this
141 * activity.
142 */
143 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
144
145 /**
146 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
147 * this extra can also be specified to supply a Bundle of arguments to pass
148 * to that fragment when it is instantiated during the initial creation
149 * of the activity.
150 */
151 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
152
153 /**
154 * When starting this activity, the invoking Intent can contain this extra
155 * boolean that the header list should not be displayed. This is most often
156 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
157 * the activity to display a specific fragment that the user has navigated
158 * to.
159 */
160 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
161
162 // extras that allow any preference activity to be launched as part of a wizard
163
164 // show Back and Next buttons? takes boolean parameter
165 // Back will then return RESULT_CANCELED and Next RESULT_OK
166 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
167
168 // add a Skip button?
169 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
170
171 // specify custom text for the Back or Next buttons, or cause a button to not appear
172 // at all by setting it to null
173 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
174 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
175
176 /**
177 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
178 * this extra can also be specify to supply the title to be shown for
179 * that fragment.
180 */
181 protected static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
182
183 private static final String BACK_STACK_PREFS = ":settings:prefs";
184
185 private static final String META_DATA_KEY_HEADER_ID =
186 "com.android.settings.TOP_LEVEL_HEADER_ID";
187
188 private static final String META_DATA_KEY_FRAGMENT_CLASS =
189 "com.android.settings.FRAGMENT_CLASS";
190
191 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
192
193 private static final String SAVE_KEY_CURRENT_HEADER = "com.android.settings.CURRENT_HEADER";
194
195 private static boolean sShowNoHomeNotice = false;
196
197 private String mFragmentClass;
198 private int mTopLevelHeaderId;
199 private Header mFirstHeader;
200 private Header mCurrentHeader;
201
202 // Show only these settings for restricted users
203 private int[] SETTINGS_FOR_RESTRICTED = {
204 R.id.wireless_section,
205 R.id.wifi_settings,
206 R.id.bluetooth_settings,
207 R.id.data_usage_settings,
208 R.id.wireless_settings,
209 R.id.device_section,
210 R.id.sound_settings,
211 R.id.display_settings,
212 R.id.storage_settings,
213 R.id.application_settings,
214 R.id.battery_settings,
215 R.id.personal_section,
216 R.id.location_settings,
217 R.id.security_settings,
218 R.id.language_settings,
219 R.id.user_settings,
220 R.id.account_settings,
221 R.id.account_add,
222 R.id.system_section,
223 R.id.date_time_settings,
224 R.id.about_settings,
225 R.id.accessibility_settings,
226 R.id.print_settings,
227 R.id.nfc_payment_settings,
228 R.id.home_settings
229 };
230
231 private static final String[] ENTRY_FRAGMENTS = {
232 WirelessSettings.class.getName(),
233 WifiSettings.class.getName(),
234 AdvancedWifiSettings.class.getName(),
235 BluetoothSettings.class.getName(),
236 TetherSettings.class.getName(),
237 WifiP2pSettings.class.getName(),
238 VpnSettings.class.getName(),
239 DateTimeSettings.class.getName(),
240 LocalePicker.class.getName(),
241 InputMethodAndLanguageSettings.class.getName(),
242 SpellCheckersSettings.class.getName(),
243 UserDictionaryList.class.getName(),
244 UserDictionarySettings.class.getName(),
245 SoundSettings.class.getName(),
246 DisplaySettings.class.getName(),
247 DeviceInfoSettings.class.getName(),
248 ManageApplications.class.getName(),
249 ProcessStatsUi.class.getName(),
250 NotificationStation.class.getName(),
251 LocationSettings.class.getName(),
252 SecuritySettings.class.getName(),
253 PrivacySettings.class.getName(),
254 DeviceAdminSettings.class.getName(),
255 AccessibilitySettings.class.getName(),
256 CaptionPropertiesFragment.class.getName(),
257 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
258 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
259 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
260 TextToSpeechSettings.class.getName(),
261 Memory.class.getName(),
262 DevelopmentSettings.class.getName(),
263 UsbSettings.class.getName(),
264 AndroidBeam.class.getName(),
265 WifiDisplaySettings.class.getName(),
266 PowerUsageSummary.class.getName(),
267 AccountSyncSettings.class.getName(),
268 CryptKeeperSettings.class.getName(),
269 DataUsageSummary.class.getName(),
270 DreamSettings.class.getName(),
271 UserSettings.class.getName(),
272 NotificationAccessSettings.class.getName(),
273 ManageAccountsSettings.class.getName(),
274 PrintSettingsFragment.class.getName(),
275 PrintJobSettingsFragment.class.getName(),
276 TrustedCredentialsSettings.class.getName(),
277 PaymentSettings.class.getName(),
278 KeyboardLayoutPickerFragment.class.getName(),
279 ChooseAccountFragment.class.getName(),
Fabrice Di Meglioca915662014-02-06 15:46:19 -0800280 DashboardSummary.class.getName(),
281 ApnSettings.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800282 };
283
284 private SharedPreferences mDevelopmentPreferences;
285 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
286
287 // TODO: Update Call Settings based on airplane mode state.
288
289 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
290
291 private AuthenticatorHelper mAuthenticatorHelper;
292 private boolean mListeningToAccountUpdates;
293
294 private Button mNextButton;
295
296 private boolean mBatteryPresent = true;
297 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
298
299 @Override
300 public void onReceive(Context context, Intent intent) {
301 String action = intent.getAction();
302 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
303 boolean batteryPresent = Utils.isBatteryPresent(intent);
304
305 if (mBatteryPresent != batteryPresent) {
306 mBatteryPresent = batteryPresent;
307 invalidateHeaders();
308 }
309 }
310 }
311 };
312
313 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
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);
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800402 onListItemClick((ListView) parent, view, position, id);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800403 }
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());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800519
520 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
521 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
522 }
523
524 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
525 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
526
527 if (savedInstanceState != null) {
528 // We are restarting from a previous saved state; used that to
529 // initialize, instead of starting fresh.
530 ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG);
531 if (headers != null) {
532 mHeaders.addAll(headers);
533 int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG,
534 (int) HEADER_ID_UNDEFINED);
535 if (curHeader >= 0 && curHeader < mHeaders.size()) {
536 setSelectedHeader(mHeaders.get(curHeader));
537 }
538 }
539
540 } else {
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800541 // We need to first to build the headers.
542 onBuildHeaders(mHeaders);
543
544 final CharSequence initialTitle = getTitle();
545
546 // If there are headers, get the initial fragment and push it first so that pressing
547 // BACK will always go to it before exiting the app
548 if (mHeaders.size() > 0) {
549 switchToHeader(mFirstHeader, false, true);
550 }
551
552 // Got to the initial fragment if one is specified
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800553 if (initialFragment != null) {
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800554 final int initialTitleRes = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
555 final Header h =
556 onGetInitialHeader(initialTitle, initialTitleRes, initialArguments);
557 switchToHeader(h, false, false);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800558 }
559 }
560
561 // see if we should show Back/Next buttons
562 Intent intent = getIntent();
563 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
564
565 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
566 if (buttonBar != null) {
567 buttonBar.setVisibility(View.VISIBLE);
568
569 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
570 backButton.setOnClickListener(new OnClickListener() {
571 public void onClick(View v) {
572 setResult(RESULT_CANCELED);
573 finish();
574 }
575 });
576 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
577 skipButton.setOnClickListener(new OnClickListener() {
578 public void onClick(View v) {
579 setResult(RESULT_OK);
580 finish();
581 }
582 });
583 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
584 mNextButton.setOnClickListener(new OnClickListener() {
585 public void onClick(View v) {
586 setResult(RESULT_OK);
587 finish();
588 }
589 });
590
591 // set our various button parameters
592 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
593 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
594 if (TextUtils.isEmpty(buttonText)) {
595 mNextButton.setVisibility(View.GONE);
596 }
597 else {
598 mNextButton.setText(buttonText);
599 }
600 }
601 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
602 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
603 if (TextUtils.isEmpty(buttonText)) {
604 backButton.setVisibility(View.GONE);
605 }
606 else {
607 backButton.setText(buttonText);
608 }
609 }
610 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
611 skipButton.setVisibility(View.VISIBLE);
612 }
613 }
614 }
615
616 if (!onIsHidingHeaders()) {
617 highlightHeader(mTopLevelHeaderId);
618 }
619
620 // Retrieve any saved state
621 if (savedInstanceState != null) {
622 mCurrentHeader = savedInstanceState.getParcelable(SAVE_KEY_CURRENT_HEADER);
623 }
624 }
625
626 @Override
627 public void onBackStackChanged() {
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800628 final int count = getFragmentManager().getBackStackEntryCount();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800629 TitlePair pair = null;
630 int last;
631 while (mTitleStack.size() > count) {
632 last = mTitleStack.size() - 1;
633 pair = mTitleStack.remove(last);
634 }
635 // Check if we go back
636 if (pair != null) {
637 int size = mTitleStack.size();
638 if (size > 0) {
639 last = mTitleStack.size() - 1;
640 pair = mTitleStack.get(last);
641 if (pair != null) {
642 final CharSequence title;
643 if (pair.first > 0) {
644 title = getText(pair.first);
645 } else {
646 title = pair.second;
647 }
648 setTitle(title);
649 }
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800650 } else {
651 final String title = getResources().getString(R.string.settings_label);
652 setTitle(title);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800653 }
654 }
655 }
656
657 /**
658 * Returns the Header list
659 */
660 private List<Header> getHeaders() {
661 return mHeaders;
662 }
663
664 @Override
665 protected void onSaveInstanceState(Bundle outState) {
666 super.onSaveInstanceState(outState);
667
668 if (mHeaders.size() > 0) {
669 outState.putParcelableArrayList(HEADERS_TAG, mHeaders);
670 if (mCurHeader != null) {
671 int index = mHeaders.indexOf(mCurHeader);
672 if (index >= 0) {
673 outState.putInt(CUR_HEADER_TAG, index);
674 }
675 }
676 }
677
678 // Save the current fragment, if it is the same as originally launched
679 if (mCurrentHeader != null) {
680 outState.putParcelable(SAVE_KEY_CURRENT_HEADER, mCurrentHeader);
681 }
682 }
683
684 @Override
685 public void onResume() {
686 super.onResume();
687
688 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
689 @Override
690 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
691 invalidateHeaders();
692 }
693 };
694 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
695 mDevelopmentPreferencesListener);
696
697 mHeaderAdapter.resume();
698 invalidateHeaders();
699
700 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
701 }
702
703 @Override
704 public void onPause() {
705 super.onPause();
706
707 unregisterReceiver(mBatteryInfoReceiver);
708
709 mHeaderAdapter.pause();
710
711 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
712 mDevelopmentPreferencesListener);
713
714 mDevelopmentPreferencesListener = null;
715 }
716
717 @Override
718 public void onDestroy() {
719 super.onDestroy();
720 if (mListeningToAccountUpdates) {
721 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
722 }
723 }
724
725 /**
726 * @hide
727 */
728 protected boolean isValidFragment(String fragmentName) {
729 // Almost all fragments are wrapped in this,
730 // except for a few that have their own activities.
731 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
732 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
733 }
734 return false;
735 }
736
737 /**
738 * When in two-pane mode, switch to the fragment pane to show the given
739 * preference fragment.
740 *
741 * @param header The new header to display.
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800742 * @param validate true means that the fragment's Header needs to be validated.
743 * @param initial true means that the fragment is the initial one.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800744 */
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800745 private void switchToHeader(Header header, boolean validate, boolean initial) {
746 if (mCurHeader != header) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800747 if (header.fragment == null) {
748 throw new IllegalStateException("can't switch to header that has no fragment");
749 }
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800750 getFragmentManager().popBackStackImmediate(BACK_STACK_PREFS,
751 FragmentManager.POP_BACK_STACK_INCLUSIVE);
752 mTitleStack.clear();
753 switchToHeaderInner(header.fragment, header.fragmentArguments, validate, !initial);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800754 setSelectedHeader(header);
755 final CharSequence title;
756 if (header.fragment.equals("com.android.settings.dashboard.DashboardSummary")) {
757 title = getResources().getString(R.string.settings_label);
758 } else {
759 title = header.getTitle(getResources());
760 }
761 final TitlePair pair = new TitlePair(0, title);
762 mTitleStack.add(pair);
763 setTitle(title);
764 }
765 }
766
767 private void setSelectedHeader(Header header) {
768 mCurHeader = header;
769 int index = mHeaders.indexOf(header);
770 if (mDrawer != null) {
771 if (index >= 0) {
772 mDrawer.setItemChecked(index, true);
773 } else {
774 mDrawer.clearChoices();
775 }
776 }
777 }
778
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800779 public Header onGetInitialHeader(CharSequence title, int titleRes, Bundle args) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800780 String fragmentClass = getStartingFragmentClass(super.getIntent());
781 if (fragmentClass != null) {
782 Header header = new Header();
783 header.fragment = fragmentClass;
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800784 header.title = title;
785 header.titleRes = titleRes;
786 header.fragmentArguments = args;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800787 mCurrentHeader = header;
788 return header;
789 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800790 return mFirstHeader;
791 }
792
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800793 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate,
794 boolean addToBackStack) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800795 if (validate && !isValidFragment(fragmentName)) {
796 throw new IllegalArgumentException("Invalid fragment for this activity: "
797 + fragmentName);
798 }
799 Fragment f = Fragment.instantiate(this, fragmentName, args);
800 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800801 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800802 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
803 if (addToBackStack) {
804 transaction.addToBackStack(BACK_STACK_PREFS);
805 }
806 transaction.commitAllowingStateLoss();
807 }
808
809 /**
810 * Start a new fragment.
811 *
812 * @param fragmentName The name of the fragment to display.
813 * @param args Optional arguments to supply to the fragment.
814 * @param resultTo Option fragment that should receive the result of
815 * the activity launch.
816 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
817 * report the result.
818 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
819 * valid one then it will be used to get the title. Otherwise the titleText
820 * argument will be used as the title.
821 * @param titleText string to display for the title of.
822 */
823 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
824 int resultRequestCode, int titleRes, CharSequence titleText) {
825 Fragment f = Fragment.instantiate(this, fragmentName, args);
826 if (resultTo != null) {
827 f.setTargetFragment(resultTo, resultRequestCode);
828 }
829 FragmentTransaction transaction = getFragmentManager().beginTransaction();
830 transaction.replace(R.id.prefs, f);
831 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
832 transaction.addToBackStack(BACK_STACK_PREFS);
833 transaction.commitAllowingStateLoss();
834
835 final TitlePair pair;
836 final CharSequence cs;
837 if (titleRes != 0) {
838 pair = new TitlePair(titleRes, null);
839 cs = getText(titleRes);
840 } else {
841 pair = new TitlePair(0, titleText);
842 cs = titleText;
843 }
844 setTitle(cs);
845 mTitleStack.add(pair);
846 }
847
848 /**
849 * Start a new fragment containing a preference panel. If the preferences
850 * are being displayed in multi-pane mode, the given fragment class will
851 * be instantiated and placed in the appropriate pane. If running in
852 * single-pane mode, a new activity will be launched in which to show the
853 * fragment.
854 *
855 * @param fragmentClass Full name of the class implementing the fragment.
856 * @param args Any desired arguments to supply to the fragment.
857 * @param titleRes Optional resource identifier of the title of this
858 * fragment.
859 * @param titleText Optional text of the title of this fragment.
860 * @param resultTo Optional fragment that result data should be sent to.
861 * If non-null, resultTo.onActivityResult() will be called when this
862 * preference panel is done. The launched panel must use
863 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
864 * @param resultRequestCode If resultTo is non-null, this is the caller's
865 * request code to be received with the resut.
866 */
867 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
868 CharSequence titleText, Fragment resultTo,
869 int resultRequestCode) {
870 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
871 }
872
873 /**
874 * Start a new fragment.
875 *
876 * @param fragment The fragment to start
877 * @param push If true, the current fragment will be pushed onto the back stack. If false,
878 * the current fragment will be replaced.
879 */
880 public void startPreferenceFragment(Fragment fragment, boolean push) {
881 FragmentTransaction transaction = getFragmentManager().beginTransaction();
882 transaction.replace(R.id.prefs, fragment);
883 if (push) {
884 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
885 transaction.addToBackStack(BACK_STACK_PREFS);
886 } else {
887 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
888 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800889 transaction.commitAllowingStateLoss();
890 }
891
892 @Override
893 public void onNewIntent(Intent intent) {
894 super.onNewIntent(intent);
895
896 // If it is not launched from history, then reset to top-level
897 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
898 if (mDrawer != null) {
899 mDrawer.setSelectionFromTop(0, 0);
900 }
901 }
902 }
903
904 /**
905 * Called to determine whether the header list should be hidden.
906 * The default implementation returns the
907 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
908 * This is set to false, for example, when the activity is being re-launched
909 * to show a particular preference activity.
910 */
911 public boolean onIsHidingHeaders() {
912 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
913 }
914
915 private void highlightHeader(int id) {
916 if (id != 0) {
917 Integer index = mHeaderIndexMap.get(id);
918 if (index != null && mDrawer != null) {
919 mDrawer.setItemChecked(index, true);
920 if (mDrawer.getVisibility() == View.VISIBLE) {
921 mDrawer.smoothScrollToPosition(index);
922 }
923 }
924 }
925 }
926
927 @Override
928 public Intent getIntent() {
929 Intent superIntent = super.getIntent();
930 String startingFragment = getStartingFragmentClass(superIntent);
931 // This is called from super.onCreate, isMultiPane() is not yet reliable
932 // Do not use onIsHidingHeaders either, which relies itself on this method
933 if (startingFragment != null) {
934 Intent modIntent = new Intent(superIntent);
935 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
936 Bundle args = superIntent.getExtras();
937 if (args != null) {
938 args = new Bundle(args);
939 } else {
940 args = new Bundle();
941 }
942 args.putParcelable("intent", superIntent);
943 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
944 return modIntent;
945 }
946 return superIntent;
947 }
948
949 /**
950 * Checks if the component name in the intent is different from the Settings class and
951 * returns the class name to load as a fragment.
952 */
953 private String getStartingFragmentClass(Intent intent) {
954 if (mFragmentClass != null) return mFragmentClass;
955
956 String intentClass = intent.getComponent().getClassName();
957 if (intentClass.equals(getClass().getName())) return null;
958
959 if ("com.android.settings.ManageApplications".equals(intentClass)
960 || "com.android.settings.RunningServices".equals(intentClass)
961 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
962 // Old names of manage apps.
963 intentClass = com.android.settings.applications.ManageApplications.class.getName();
964 }
965
966 return intentClass;
967 }
968
969 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800970 * Called by a preference panel fragment to finish itself.
971 *
972 * @param caller The fragment that is asking to be finished.
973 * @param resultCode Optional result code to send back to the original
974 * launching fragment.
975 * @param resultData Optional result data to send back to the original
976 * launching fragment.
977 */
978 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
979 setResult(resultCode, resultData);
980 }
981
982 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800983 * Called when the activity needs its list of headers build. By
984 * implementing this and adding at least one item to the list, you
985 * will cause the activity to run in its modern fragment mode. Note
986 * that this function may not always be called; for example, if the
987 * activity has been asked to display a particular fragment without
988 * the header list, there is no need to build the headers.
989 *
990 * <p>Typical implementations will use {@link #loadHeadersFromResource}
991 * to fill in the list from a resource.
992 *
993 * @param headers The list in which to place the headers.
994 */
995 private void onBuildHeaders(List<Header> headers) {
996 loadHeadersFromResource(R.xml.settings_headers, headers);
997 updateHeaderList(headers);
998 }
999
1000 /**
1001 * Parse the given XML file as a header description, adding each
1002 * parsed Header into the target list.
1003 *
1004 * @param resid The XML resource to load and parse.
1005 * @param target The list in which the parsed headers should be placed.
1006 */
1007 private void loadHeadersFromResource(int resid, List<Header> target) {
1008 XmlResourceParser parser = null;
1009 try {
1010 parser = getResources().getXml(resid);
1011 AttributeSet attrs = Xml.asAttributeSet(parser);
1012
1013 int type;
1014 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1015 && type != XmlPullParser.START_TAG) {
1016 // Parse next until start tag is found
1017 }
1018
1019 String nodeName = parser.getName();
1020 if (!"preference-headers".equals(nodeName)) {
1021 throw new RuntimeException(
1022 "XML document must start with <preference-headers> tag; found"
1023 + nodeName + " at " + parser.getPositionDescription());
1024 }
1025
1026 Bundle curBundle = null;
1027
1028 final int outerDepth = parser.getDepth();
1029 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1030 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1031 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1032 continue;
1033 }
1034
1035 nodeName = parser.getName();
1036 if ("header".equals(nodeName)) {
1037 Header header = new Header();
1038
1039 TypedArray sa = obtainStyledAttributes(
1040 attrs, com.android.internal.R.styleable.PreferenceHeader);
1041 header.id = sa.getResourceId(
1042 com.android.internal.R.styleable.PreferenceHeader_id,
1043 (int)HEADER_ID_UNDEFINED);
1044 TypedValue tv = sa.peekValue(
1045 com.android.internal.R.styleable.PreferenceHeader_title);
1046 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1047 if (tv.resourceId != 0) {
1048 header.titleRes = tv.resourceId;
1049 } else {
1050 header.title = tv.string;
1051 }
1052 }
1053 tv = sa.peekValue(
1054 com.android.internal.R.styleable.PreferenceHeader_summary);
1055 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1056 if (tv.resourceId != 0) {
1057 header.summaryRes = tv.resourceId;
1058 } else {
1059 header.summary = tv.string;
1060 }
1061 }
1062 header.iconRes = sa.getResourceId(
1063 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1064 header.fragment = sa.getString(
1065 com.android.internal.R.styleable.PreferenceHeader_fragment);
1066 sa.recycle();
1067
1068 if (curBundle == null) {
1069 curBundle = new Bundle();
1070 }
1071
1072 final int innerDepth = parser.getDepth();
1073 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1074 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1075 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1076 continue;
1077 }
1078
1079 String innerNodeName = parser.getName();
1080 if (innerNodeName.equals("extra")) {
1081 getResources().parseBundleExtra("extra", attrs, curBundle);
1082 XmlUtils.skipCurrentTag(parser);
1083
1084 } else if (innerNodeName.equals("intent")) {
1085 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1086
1087 } else {
1088 XmlUtils.skipCurrentTag(parser);
1089 }
1090 }
1091
1092 if (curBundle.size() > 0) {
1093 header.fragmentArguments = curBundle;
1094 curBundle = null;
1095 }
1096
1097 target.add(header);
1098 } else {
1099 XmlUtils.skipCurrentTag(parser);
1100 }
1101 }
1102
1103 } catch (XmlPullParserException e) {
1104 throw new RuntimeException("Error parsing headers", e);
1105 } catch (IOException e) {
1106 throw new RuntimeException("Error parsing headers", e);
1107 } finally {
1108 if (parser != null) parser.close();
1109 }
1110 }
1111
1112 private void updateHeaderList(List<Header> target) {
1113 final boolean showDev = mDevelopmentPreferences.getBoolean(
1114 DevelopmentSettings.PREF_SHOW,
1115 android.os.Build.TYPE.equals("eng"));
1116 int i = 0;
1117
1118 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1119 mHeaderIndexMap.clear();
1120 while (i < target.size()) {
1121 Header header = target.get(i);
1122 // Ids are integers, so downcasting
1123 int id = (int) header.id;
1124 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1125 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1126 } else if (id == R.id.wifi_settings) {
1127 // Remove WiFi Settings if WiFi service is not available.
1128 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1129 target.remove(i);
1130 }
1131 } else if (id == R.id.bluetooth_settings) {
1132 // Remove Bluetooth Settings if Bluetooth service is not available.
1133 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1134 target.remove(i);
1135 }
1136 } else if (id == R.id.data_usage_settings) {
1137 // Remove data usage when kernel module not enabled
1138 final INetworkManagementService netManager = INetworkManagementService.Stub
1139 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1140 try {
1141 if (!netManager.isBandwidthControlEnabled()) {
1142 target.remove(i);
1143 }
1144 } catch (RemoteException e) {
1145 // ignored
1146 }
1147 } else if (id == R.id.battery_settings) {
1148 // Remove battery settings when battery is not available. (e.g. TV)
1149
1150 if (!mBatteryPresent) {
1151 target.remove(i);
1152 }
1153 } else if (id == R.id.account_settings) {
1154 int headerIndex = i + 1;
1155 i = insertAccountsHeaders(target, headerIndex);
1156 } else if (id == R.id.home_settings) {
1157 if (!updateHomeSettingHeaders(header)) {
1158 target.remove(i);
1159 }
1160 } else if (id == R.id.user_settings) {
1161 if (!UserHandle.MU_ENABLED
1162 || !UserManager.supportsMultipleUsers()
1163 || Utils.isMonkeyRunning()) {
1164 target.remove(i);
1165 }
1166 } else if (id == R.id.nfc_payment_settings) {
1167 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1168 target.remove(i);
1169 } else {
1170 // Only show if NFC is on and we have the HCE feature
1171 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1172 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1173 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1174 target.remove(i);
1175 }
1176 }
1177 } else if (id == R.id.development_settings) {
1178 if (!showDev) {
1179 target.remove(i);
1180 }
1181 } else if (id == R.id.account_add) {
1182 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1183 target.remove(i);
1184 }
1185 }
1186
1187 if (i < target.size() && target.get(i) == header
1188 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1189 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1190 target.remove(i);
1191 }
1192
1193 // Increment if the current one wasn't removed by the Utils code.
1194 if (i < target.size() && target.get(i) == header) {
1195 // Hold on to the first header, when we need to reset to the top-level
1196 if (mFirstHeader == null &&
1197 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1198 mFirstHeader = header;
1199 }
1200 mHeaderIndexMap.put(id, i);
1201 i++;
1202 }
1203 }
1204 }
1205
1206 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1207 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1208 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1209 for (String accountType : accountTypes) {
1210 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1211 if (label == null) {
1212 continue;
1213 }
1214
1215 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1216 boolean skipToAccount = accounts.length == 1
1217 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1218 Header accHeader = new Header();
1219 accHeader.title = label;
1220 if (accHeader.extras == null) {
1221 accHeader.extras = new Bundle();
1222 }
1223 if (skipToAccount) {
1224 accHeader.fragment = AccountSyncSettings.class.getName();
1225 accHeader.fragmentArguments = new Bundle();
1226 // Need this for the icon
1227 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1228 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1229 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1230 accounts[0]);
1231 } else {
1232 accHeader.fragment = ManageAccountsSettings.class.getName();
1233 accHeader.fragmentArguments = new Bundle();
1234 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1235 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1236 accountType);
1237 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1238 label.toString());
1239 }
1240 accountHeaders.add(accHeader);
1241 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1242 }
1243
1244 // Sort by label
1245 Collections.sort(accountHeaders, new Comparator<Header>() {
1246 @Override
1247 public int compare(Header h1, Header h2) {
1248 return h1.title.toString().compareTo(h2.title.toString());
1249 }
1250 });
1251
1252 for (Header header : accountHeaders) {
1253 target.add(headerIndex++, header);
1254 }
1255 if (!mListeningToAccountUpdates) {
1256 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1257 mListeningToAccountUpdates = true;
1258 }
1259 return headerIndex;
1260 }
1261
1262 private boolean updateHomeSettingHeaders(Header header) {
1263 // Once we decide to show Home settings, keep showing it forever
1264 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1265 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1266 return true;
1267 }
1268
1269 try {
1270 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1271 getPackageManager().getHomeActivities(homeApps);
1272 if (homeApps.size() < 2) {
1273 // When there's only one available home app, omit this settings
1274 // category entirely at the top level UI. If the user just
1275 // uninstalled the penultimate home app candidiate, we also
1276 // now tell them about why they aren't seeing 'Home' in the list.
1277 if (sShowNoHomeNotice) {
1278 sShowNoHomeNotice = false;
1279 NoHomeDialogFragment.show(this);
1280 }
1281 return false;
1282 } else {
1283 // Okay, we're allowing the Home settings category. Tell it, when
1284 // invoked via this front door, that we'll need to be told about the
1285 // case when the user uninstalls all but one home app.
1286 if (header.fragmentArguments == null) {
1287 header.fragmentArguments = new Bundle();
1288 }
1289 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1290 }
1291 } catch (Exception e) {
1292 // Can't look up the home activity; bail on configuring the icon
1293 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1294 }
1295
1296 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1297 return true;
1298 }
1299
1300 private void getMetaData() {
1301 try {
1302 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1303 PackageManager.GET_META_DATA);
1304 if (ai == null || ai.metaData == null) return;
1305 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1306 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1307 } catch (NameNotFoundException nnfe) {
1308 // No recovery
1309 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1310 }
1311 }
1312
1313 // give subclasses access to the Next button
1314 public boolean hasNextButton() {
1315 return mNextButton != null;
1316 }
1317
1318 public Button getNextButton() {
1319 return mNextButton;
1320 }
1321
1322 public static class NoHomeDialogFragment extends DialogFragment {
1323 public static void show(Activity parent) {
1324 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1325 dialog.show(parent.getFragmentManager(), null);
1326 }
1327
1328 @Override
1329 public Dialog onCreateDialog(Bundle savedInstanceState) {
1330 return new AlertDialog.Builder(getActivity())
1331 .setMessage(R.string.only_one_home_message)
1332 .setPositiveButton(android.R.string.ok, null)
1333 .create();
1334 }
1335 }
1336
1337 private static class HeaderAdapter extends ArrayAdapter<Header> {
1338 static final int HEADER_TYPE_CATEGORY = 0;
1339 static final int HEADER_TYPE_NORMAL = 1;
1340 static final int HEADER_TYPE_SWITCH = 2;
1341 static final int HEADER_TYPE_BUTTON = 3;
1342 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1343
1344 private final WifiEnabler mWifiEnabler;
1345 private final BluetoothEnabler mBluetoothEnabler;
1346 private AuthenticatorHelper mAuthHelper;
1347 private DevicePolicyManager mDevicePolicyManager;
1348
1349 private static class HeaderViewHolder {
1350 ImageView mIcon;
1351 TextView mTitle;
1352 TextView mSummary;
1353 Switch mSwitch;
1354 ImageButton mButton;
1355 View mDivider;
1356 }
1357
1358 private LayoutInflater mInflater;
1359
1360 static int getHeaderType(Header header) {
1361 if (header.fragment == null && header.intent == null) {
1362 return HEADER_TYPE_CATEGORY;
1363 } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
1364 return HEADER_TYPE_SWITCH;
1365 } else if (header.id == R.id.security_settings) {
1366 return HEADER_TYPE_BUTTON;
1367 } else {
1368 return HEADER_TYPE_NORMAL;
1369 }
1370 }
1371
1372 @Override
1373 public int getItemViewType(int position) {
1374 Header header = getItem(position);
1375 return getHeaderType(header);
1376 }
1377
1378 @Override
1379 public boolean areAllItemsEnabled() {
1380 return false; // because of categories
1381 }
1382
1383 @Override
1384 public boolean isEnabled(int position) {
1385 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1386 }
1387
1388 @Override
1389 public int getViewTypeCount() {
1390 return HEADER_TYPE_COUNT;
1391 }
1392
1393 @Override
1394 public boolean hasStableIds() {
1395 return true;
1396 }
1397
1398 public HeaderAdapter(Context context, List<Header> objects,
1399 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1400 super(context, 0, objects);
1401
1402 mAuthHelper = authenticatorHelper;
1403 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1404
1405 // Temp Switches provided as placeholder until the adapter replaces these with actual
1406 // Switches inflated from their layouts. Must be done before adapter is set in super
1407 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1408 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1409 mDevicePolicyManager = dpm;
1410 }
1411
1412 @Override
1413 public View getView(int position, View convertView, ViewGroup parent) {
1414 HeaderViewHolder holder;
1415 Header header = getItem(position);
1416 int headerType = getHeaderType(header);
1417 View view = null;
1418
1419 if (convertView == null) {
1420 holder = new HeaderViewHolder();
1421 switch (headerType) {
1422 case HEADER_TYPE_CATEGORY:
1423 view = new TextView(getContext(), null,
1424 android.R.attr.listSeparatorTextViewStyle);
1425 holder.mTitle = (TextView) view;
1426 break;
1427
1428 case HEADER_TYPE_SWITCH:
1429 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1430 false);
1431 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1432 holder.mTitle = (TextView)
1433 view.findViewById(com.android.internal.R.id.title);
1434 holder.mSummary = (TextView)
1435 view.findViewById(com.android.internal.R.id.summary);
1436 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1437 break;
1438
1439 case HEADER_TYPE_BUTTON:
1440 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1441 false);
1442 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1443 holder.mTitle = (TextView)
1444 view.findViewById(com.android.internal.R.id.title);
1445 holder.mSummary = (TextView)
1446 view.findViewById(com.android.internal.R.id.summary);
1447 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1448 holder.mDivider = view.findViewById(R.id.divider);
1449 break;
1450
1451 case HEADER_TYPE_NORMAL:
1452 view = mInflater.inflate(
1453 R.layout.preference_header_item, parent,
1454 false);
1455 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1456 holder.mTitle = (TextView)
1457 view.findViewById(com.android.internal.R.id.title);
1458 holder.mSummary = (TextView)
1459 view.findViewById(com.android.internal.R.id.summary);
1460 break;
1461 }
1462 view.setTag(holder);
1463 } else {
1464 view = convertView;
1465 holder = (HeaderViewHolder) view.getTag();
1466 }
1467
1468 // All view fields must be updated every time, because the view may be recycled
1469 switch (headerType) {
1470 case HEADER_TYPE_CATEGORY:
1471 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1472 break;
1473
1474 case HEADER_TYPE_SWITCH:
1475 // Would need a different treatment if the main menu had more switches
1476 if (header.id == R.id.wifi_settings) {
1477 mWifiEnabler.setSwitch(holder.mSwitch);
1478 } else {
1479 mBluetoothEnabler.setSwitch(holder.mSwitch);
1480 }
1481 updateCommonHeaderView(header, holder);
1482 break;
1483
1484 case HEADER_TYPE_BUTTON:
1485 if (header.id == R.id.security_settings) {
1486 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1487 if (hasCert) {
1488 holder.mButton.setVisibility(View.VISIBLE);
1489 holder.mDivider.setVisibility(View.VISIBLE);
1490 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1491 if (isManaged) {
1492 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1493 } else {
1494 holder.mButton.setImageResource(
1495 android.R.drawable.stat_notify_error);
1496 }
1497 holder.mButton.setOnClickListener(new OnClickListener() {
1498 @Override
1499 public void onClick(View v) {
1500 Intent intent = new Intent(
1501 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1502 getContext().startActivity(intent);
1503 }
1504 });
1505 } else {
1506 holder.mButton.setVisibility(View.GONE);
1507 holder.mDivider.setVisibility(View.GONE);
1508 }
1509 }
1510 updateCommonHeaderView(header, holder);
1511 break;
1512
1513 case HEADER_TYPE_NORMAL:
1514 updateCommonHeaderView(header, holder);
1515 break;
1516 }
1517
1518 return view;
1519 }
1520
1521 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1522 if (header.extras != null
1523 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1524 String accType = header.extras.getString(
1525 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1526 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1527 setHeaderIcon(holder, icon);
1528 } else {
1529 holder.mIcon.setImageResource(header.iconRes);
1530 }
1531 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1532 CharSequence summary = header.getSummary(getContext().getResources());
1533 if (!TextUtils.isEmpty(summary)) {
1534 holder.mSummary.setVisibility(View.VISIBLE);
1535 holder.mSummary.setText(summary);
1536 } else {
1537 holder.mSummary.setVisibility(View.GONE);
1538 }
1539 }
1540
1541 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1542 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1543 lp.width = getContext().getResources().getDimensionPixelSize(
1544 R.dimen.header_icon_width);
1545 lp.height = lp.width;
1546 holder.mIcon.setLayoutParams(lp);
1547 holder.mIcon.setImageDrawable(icon);
1548 }
1549
1550 public void resume() {
1551 mWifiEnabler.resume();
1552 mBluetoothEnabler.resume();
1553 }
1554
1555 public void pause() {
1556 mWifiEnabler.pause();
1557 mBluetoothEnabler.pause();
1558 }
1559 }
1560
1561 private void onListItemClick(ListView l, View v, int position, long id) {
1562 if (!isResumed()) {
1563 return;
1564 }
1565 Object item = mHeaderAdapter.getItem(position);
1566 if (item instanceof Header) {
1567 mCurrentHeader = (Header) item;
1568 }
1569 }
1570
1571 /**
1572 * Called when the user selects an item in the header list. The default
1573 * implementation will call either
Fabrice Di Megliodc77b732014-02-04 12:41:30 -08001574 * {@link #startWithFragment(String, android.os.Bundle, android.app.Fragment, int, int, CharSequence)}
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -08001575 * or {@link #switchToHeader(com.android.settings.SettingsActivity.Header, boolean, boolean)}
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001576 * as appropriate.
1577 *
1578 * @param header The header that was selected.
1579 */
1580 private void onHeaderClick(Header header) {
1581 if (header == null) return;
1582 if (header.fragment != null) {
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -08001583 switchToHeader(header, false, false);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001584 } else if (header.intent != null) {
1585 startActivity(header.intent);
1586 }
1587 }
1588
1589 @Override
1590 public boolean shouldUpRecreateTask(Intent targetIntent) {
1591 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1592 }
1593
1594 @Override
1595 public void onAccountsUpdated(Account[] accounts) {
1596 // TODO: watch for package upgrades to invalidate cache; see 7206643
1597 mAuthenticatorHelper.updateAuthDescriptions(this);
1598 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1599 invalidateHeaders();
1600 }
1601
1602 public static void requestHomeNotice() {
1603 sShowNoHomeNotice = true;
1604 }
1605
1606 /**
1607 * Default value for {@link Header#id Header.id} indicating that no
1608 * identifier value is set. All other values (including those below -1)
1609 * are valid.
1610 */
1611 private static final long HEADER_ID_UNDEFINED = -1;
1612
1613 /**
1614 * Description of a single Header item that the user can select.
1615 */
1616 static final class Header implements Parcelable {
1617 /**
1618 * Identifier for this header, to correlate with a new list when
1619 * it is updated. The default value is
1620 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1621 * @attr ref android.R.styleable#PreferenceHeader_id
1622 */
1623 public long id = HEADER_ID_UNDEFINED;
1624
1625 /**
1626 * Resource ID of title of the header that is shown to the user.
1627 * @attr ref android.R.styleable#PreferenceHeader_title
1628 */
1629 public int titleRes;
1630
1631 /**
1632 * Title of the header that is shown to the user.
1633 * @attr ref android.R.styleable#PreferenceHeader_title
1634 */
1635 public CharSequence title;
1636
1637 /**
1638 * Resource ID of optional summary describing what this header controls.
1639 * @attr ref android.R.styleable#PreferenceHeader_summary
1640 */
1641 public int summaryRes;
1642
1643 /**
1644 * Optional summary describing what this header controls.
1645 * @attr ref android.R.styleable#PreferenceHeader_summary
1646 */
1647 public CharSequence summary;
1648
1649 /**
1650 * Optional icon resource to show for this header.
1651 * @attr ref android.R.styleable#PreferenceHeader_icon
1652 */
1653 public int iconRes;
1654
1655 /**
1656 * Full class name of the fragment to display when this header is
1657 * selected.
1658 * @attr ref android.R.styleable#PreferenceHeader_fragment
1659 */
1660 public String fragment;
1661
1662 /**
1663 * Optional arguments to supply to the fragment when it is
1664 * instantiated.
1665 */
1666 public Bundle fragmentArguments;
1667
1668 /**
1669 * Intent to launch when the preference is selected.
1670 */
1671 public Intent intent;
1672
1673 /**
1674 * Optional additional data for use by subclasses of the activity
1675 */
1676 public Bundle extras;
1677
1678 public Header() {
1679 // Empty
1680 }
1681
1682 /**
1683 * Return the currently set title. If {@link #titleRes} is set,
1684 * this resource is loaded from <var>res</var> and returned. Otherwise
1685 * {@link #title} is returned.
1686 */
1687 public CharSequence getTitle(Resources res) {
1688 if (titleRes != 0) {
1689 return res.getText(titleRes);
1690 }
1691 return title;
1692 }
1693
1694 /**
1695 * Return the currently set summary. If {@link #summaryRes} is set,
1696 * this resource is loaded from <var>res</var> and returned. Otherwise
1697 * {@link #summary} is returned.
1698 */
1699 public CharSequence getSummary(Resources res) {
1700 if (summaryRes != 0) {
1701 return res.getText(summaryRes);
1702 }
1703 return summary;
1704 }
1705
1706 @Override
1707 public int describeContents() {
1708 return 0;
1709 }
1710
1711 @Override
1712 public void writeToParcel(Parcel dest, int flags) {
1713 dest.writeLong(id);
1714 dest.writeInt(titleRes);
1715 TextUtils.writeToParcel(title, dest, flags);
1716 dest.writeInt(summaryRes);
1717 TextUtils.writeToParcel(summary, dest, flags);
1718 dest.writeInt(iconRes);
1719 dest.writeString(fragment);
1720 dest.writeBundle(fragmentArguments);
1721 if (intent != null) {
1722 dest.writeInt(1);
1723 intent.writeToParcel(dest, flags);
1724 } else {
1725 dest.writeInt(0);
1726 }
1727 dest.writeBundle(extras);
1728 }
1729
1730 public void readFromParcel(Parcel in) {
1731 id = in.readLong();
1732 titleRes = in.readInt();
1733 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1734 summaryRes = in.readInt();
1735 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1736 iconRes = in.readInt();
1737 fragment = in.readString();
1738 fragmentArguments = in.readBundle();
1739 if (in.readInt() != 0) {
1740 intent = Intent.CREATOR.createFromParcel(in);
1741 }
1742 extras = in.readBundle();
1743 }
1744
1745 Header(Parcel in) {
1746 readFromParcel(in);
1747 }
1748
1749 public static final Creator<Header> CREATOR = new Creator<Header>() {
1750 public Header createFromParcel(Parcel source) {
1751 return new Header(source);
1752 }
1753 public Header[] newArray(int size) {
1754 return new Header[size];
1755 }
1756 };
1757 }
1758}