1/*
2 * Copyright (C) 2008 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.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.RestrictionEntry;
26import android.content.SharedPreferences;
27import android.content.pm.ActivityInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.PackageManager.NameNotFoundException;
30import android.graphics.drawable.Drawable;
31import android.os.Bundle;
32import android.os.INetworkManagementService;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.os.UserHandle;
36import android.os.UserManager;
37import android.preference.Preference;
38import android.preference.PreferenceActivity;
39import android.preference.PreferenceFragment;
40import android.text.TextUtils;
41import android.util.Log;
42import android.view.LayoutInflater;
43import android.view.View;
44import android.view.View.OnClickListener;
45import android.view.ViewGroup;
46import android.widget.ArrayAdapter;
47import android.widget.Button;
48import android.widget.ImageView;
49import android.widget.ListAdapter;
50import android.widget.Switch;
51import android.widget.TextView;
52
53import com.android.internal.util.ArrayUtils;
54import com.android.settings.AccessibilitySettings.ToggleAccessibilityServicePreferenceFragment;
55import com.android.settings.accounts.AccountSyncSettings;
56import com.android.settings.accounts.AuthenticatorHelper;
57import com.android.settings.accounts.ManageAccountsSettings;
58import com.android.settings.bluetooth.BluetoothEnabler;
59import com.android.settings.bluetooth.BluetoothSettings;
60import com.android.settings.wfd.WifiDisplaySettings;
61import com.android.settings.wifi.WifiEnabler;
62import com.android.settings.wifi.WifiSettings;
63import com.android.settings.wifi.p2p.WifiP2pSettings;
64
65import java.util.ArrayList;
66import java.util.Collections;
67import java.util.Comparator;
68import java.util.HashMap;
69import java.util.List;
70
71/**
72 * Top-level settings activity to handle single pane and double pane UI layout.
73 */
74public class Settings extends PreferenceActivity
75        implements ButtonBarHandler, OnAccountsUpdateListener {
76
77    private static final String LOG_TAG = "Settings";
78
79    private static final String META_DATA_KEY_HEADER_ID =
80        "com.android.settings.TOP_LEVEL_HEADER_ID";
81    private static final String META_DATA_KEY_FRAGMENT_CLASS =
82        "com.android.settings.FRAGMENT_CLASS";
83    private static final String META_DATA_KEY_PARENT_TITLE =
84        "com.android.settings.PARENT_FRAGMENT_TITLE";
85    private static final String META_DATA_KEY_PARENT_FRAGMENT_CLASS =
86        "com.android.settings.PARENT_FRAGMENT_CLASS";
87
88    private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
89
90    private static final String SAVE_KEY_CURRENT_HEADER = "com.android.settings.CURRENT_HEADER";
91    private static final String SAVE_KEY_PARENT_HEADER = "com.android.settings.PARENT_HEADER";
92
93    private String mFragmentClass;
94    private int mTopLevelHeaderId;
95    private Header mFirstHeader;
96    private Header mCurrentHeader;
97    private Header mParentHeader;
98    private boolean mInLocalHeaderSwitch;
99
100    // Show only these settings for restricted users
101    private int[] SETTINGS_FOR_RESTRICTED = {
102            R.id.wireless_section,
103            R.id.wifi_settings,
104            R.id.bluetooth_settings,
105            R.id.data_usage_settings,
106            R.id.wireless_settings,
107            R.id.device_section,
108            R.id.sound_settings,
109            R.id.display_settings,
110            R.id.storage_settings,
111            R.id.application_settings,
112            R.id.battery_settings,
113            R.id.personal_section,
114            R.id.location_settings,
115            R.id.security_settings,
116            R.id.language_settings,
117            R.id.user_settings,
118            R.id.account_settings,
119            R.id.account_add,
120            R.id.system_section,
121            R.id.date_time_settings,
122            R.id.about_settings,
123            R.id.accessibility_settings
124    };
125
126    private SharedPreferences mDevelopmentPreferences;
127    private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
128
129    // TODO: Update Call Settings based on airplane mode state.
130
131    protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
132
133    private AuthenticatorHelper mAuthenticatorHelper;
134    private Header mLastHeader;
135    private boolean mListeningToAccountUpdates;
136
137    @Override
138    protected void onCreate(Bundle savedInstanceState) {
139        if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
140            getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
141        }
142
143        mAuthenticatorHelper = new AuthenticatorHelper();
144        mAuthenticatorHelper.updateAuthDescriptions(this);
145        mAuthenticatorHelper.onAccountsUpdated(this, null);
146
147        mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
148                Context.MODE_PRIVATE);
149
150        getMetaData();
151        mInLocalHeaderSwitch = true;
152        super.onCreate(savedInstanceState);
153        mInLocalHeaderSwitch = false;
154
155        if (!onIsHidingHeaders() && onIsMultiPane()) {
156            highlightHeader(mTopLevelHeaderId);
157            // Force the title so that it doesn't get overridden by a direct launch of
158            // a specific settings screen.
159            setTitle(R.string.settings_label);
160        }
161
162        // Retrieve any saved state
163        if (savedInstanceState != null) {
164            mCurrentHeader = savedInstanceState.getParcelable(SAVE_KEY_CURRENT_HEADER);
165            mParentHeader = savedInstanceState.getParcelable(SAVE_KEY_PARENT_HEADER);
166        }
167
168        // If the current header was saved, switch to it
169        if (savedInstanceState != null && mCurrentHeader != null) {
170            //switchToHeaderLocal(mCurrentHeader);
171            showBreadCrumbs(mCurrentHeader.title, null);
172        }
173
174        if (mParentHeader != null) {
175            setParentTitle(mParentHeader.title, null, new OnClickListener() {
176                @Override
177                public void onClick(View v) {
178                    switchToParent(mParentHeader.fragment);
179                }
180            });
181        }
182
183        // Override up navigation for multi-pane, since we handle it in the fragment breadcrumbs
184        if (onIsMultiPane()) {
185            getActionBar().setDisplayHomeAsUpEnabled(false);
186            getActionBar().setHomeButtonEnabled(false);
187        }
188    }
189
190    @Override
191    protected void onSaveInstanceState(Bundle outState) {
192        super.onSaveInstanceState(outState);
193
194        // Save the current fragment, if it is the same as originally launched
195        if (mCurrentHeader != null) {
196            outState.putParcelable(SAVE_KEY_CURRENT_HEADER, mCurrentHeader);
197        }
198        if (mParentHeader != null) {
199            outState.putParcelable(SAVE_KEY_PARENT_HEADER, mParentHeader);
200        }
201    }
202
203    @Override
204    public void onResume() {
205        super.onResume();
206
207        mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
208            @Override
209            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
210                invalidateHeaders();
211            }
212        };
213        mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
214                mDevelopmentPreferencesListener);
215
216        ListAdapter listAdapter = getListAdapter();
217        if (listAdapter instanceof HeaderAdapter) {
218            ((HeaderAdapter) listAdapter).resume();
219        }
220        invalidateHeaders();
221    }
222
223    @Override
224    public void onPause() {
225        super.onPause();
226
227        ListAdapter listAdapter = getListAdapter();
228        if (listAdapter instanceof HeaderAdapter) {
229            ((HeaderAdapter) listAdapter).pause();
230        }
231
232        mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
233                mDevelopmentPreferencesListener);
234        mDevelopmentPreferencesListener = null;
235    }
236
237    @Override
238    public void onDestroy() {
239        super.onDestroy();
240        if (mListeningToAccountUpdates) {
241            AccountManager.get(this).removeOnAccountsUpdatedListener(this);
242        }
243    }
244
245    private void switchToHeaderLocal(Header header) {
246        mInLocalHeaderSwitch = true;
247        switchToHeader(header);
248        mInLocalHeaderSwitch = false;
249    }
250
251    @Override
252    public void switchToHeader(Header header) {
253        if (!mInLocalHeaderSwitch) {
254            mCurrentHeader = null;
255            mParentHeader = null;
256        }
257        super.switchToHeader(header);
258    }
259
260    /**
261     * Switch to parent fragment and store the grand parent's info
262     * @param className name of the activity wrapper for the parent fragment.
263     */
264    private void switchToParent(String className) {
265        final ComponentName cn = new ComponentName(this, className);
266        try {
267            final PackageManager pm = getPackageManager();
268            final ActivityInfo parentInfo = pm.getActivityInfo(cn, PackageManager.GET_META_DATA);
269
270            if (parentInfo != null && parentInfo.metaData != null) {
271                String fragmentClass = parentInfo.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
272                CharSequence fragmentTitle = parentInfo.loadLabel(pm);
273                Header parentHeader = new Header();
274                parentHeader.fragment = fragmentClass;
275                parentHeader.title = fragmentTitle;
276                mCurrentHeader = parentHeader;
277
278                switchToHeaderLocal(parentHeader);
279                highlightHeader(mTopLevelHeaderId);
280
281                mParentHeader = new Header();
282                mParentHeader.fragment
283                        = parentInfo.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);
284                mParentHeader.title = parentInfo.metaData.getString(META_DATA_KEY_PARENT_TITLE);
285            }
286        } catch (NameNotFoundException nnfe) {
287            Log.w(LOG_TAG, "Could not find parent activity : " + className);
288        }
289    }
290
291    @Override
292    public void onNewIntent(Intent intent) {
293        super.onNewIntent(intent);
294
295        // If it is not launched from history, then reset to top-level
296        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
297            if (mFirstHeader != null && !onIsHidingHeaders() && onIsMultiPane()) {
298                switchToHeaderLocal(mFirstHeader);
299            }
300            getListView().setSelectionFromTop(0, 0);
301        }
302    }
303
304    private void highlightHeader(int id) {
305        if (id != 0) {
306            Integer index = mHeaderIndexMap.get(id);
307            if (index != null) {
308                getListView().setItemChecked(index, true);
309                if (isMultiPane()) {
310                    getListView().smoothScrollToPosition(index);
311                }
312            }
313        }
314    }
315
316    @Override
317    public Intent getIntent() {
318        Intent superIntent = super.getIntent();
319        String startingFragment = getStartingFragmentClass(superIntent);
320        // This is called from super.onCreate, isMultiPane() is not yet reliable
321        // Do not use onIsHidingHeaders either, which relies itself on this method
322        if (startingFragment != null && !onIsMultiPane()) {
323            Intent modIntent = new Intent(superIntent);
324            modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
325            Bundle args = superIntent.getExtras();
326            if (args != null) {
327                args = new Bundle(args);
328            } else {
329                args = new Bundle();
330            }
331            args.putParcelable("intent", superIntent);
332            modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
333            return modIntent;
334        }
335        return superIntent;
336    }
337
338    /**
339     * Checks if the component name in the intent is different from the Settings class and
340     * returns the class name to load as a fragment.
341     */
342    protected String getStartingFragmentClass(Intent intent) {
343        if (mFragmentClass != null) return mFragmentClass;
344
345        String intentClass = intent.getComponent().getClassName();
346        if (intentClass.equals(getClass().getName())) return null;
347
348        if ("com.android.settings.ManageApplications".equals(intentClass)
349                || "com.android.settings.RunningServices".equals(intentClass)
350                || "com.android.settings.applications.StorageUse".equals(intentClass)) {
351            // Old names of manage apps.
352            intentClass = com.android.settings.applications.ManageApplications.class.getName();
353        }
354
355        return intentClass;
356    }
357
358    /**
359     * Override initial header when an activity-alias is causing Settings to be launched
360     * for a specific fragment encoded in the android:name parameter.
361     */
362    @Override
363    public Header onGetInitialHeader() {
364        String fragmentClass = getStartingFragmentClass(super.getIntent());
365        if (fragmentClass != null) {
366            Header header = new Header();
367            header.fragment = fragmentClass;
368            header.title = getTitle();
369            header.fragmentArguments = getIntent().getExtras();
370            mCurrentHeader = header;
371            return header;
372        }
373
374        return mFirstHeader;
375    }
376
377    @Override
378    public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
379            int titleRes, int shortTitleRes) {
380        Intent intent = super.onBuildStartFragmentIntent(fragmentName, args,
381                titleRes, shortTitleRes);
382
383        // Some fragments want split ActionBar; these should stay in sync with
384        // uiOptions for fragments also defined as activities in manifest.
385        if (WifiSettings.class.getName().equals(fragmentName) ||
386                WifiP2pSettings.class.getName().equals(fragmentName) ||
387                WifiDisplaySettings.class.getName().equals(fragmentName) ||
388                BluetoothSettings.class.getName().equals(fragmentName) ||
389                DreamSettings.class.getName().equals(fragmentName) ||
390                ToggleAccessibilityServicePreferenceFragment.class.getName().equals(fragmentName)) {
391            intent.putExtra(EXTRA_UI_OPTIONS, ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW);
392        }
393
394        intent.setClass(this, SubSettings.class);
395        return intent;
396    }
397
398    /**
399     * Populate the activity with the top-level headers.
400     */
401    @Override
402    public void onBuildHeaders(List<Header> headers) {
403        loadHeadersFromResource(R.xml.settings_headers, headers);
404        updateHeaderList(headers);
405    }
406
407    private void updateHeaderList(List<Header> target) {
408        final boolean showDev = mDevelopmentPreferences.getBoolean(
409                DevelopmentSettings.PREF_SHOW,
410                android.os.Build.TYPE.equals("eng"));
411        int i = 0;
412
413        final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
414        mHeaderIndexMap.clear();
415        while (i < target.size()) {
416            Header header = target.get(i);
417            // Ids are integers, so downcasting
418            int id = (int) header.id;
419            if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
420                Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
421            } else if (id == R.id.wifi_settings) {
422                // Remove WiFi Settings if WiFi service is not available.
423                if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
424                    target.remove(i);
425                }
426            } else if (id == R.id.bluetooth_settings) {
427                // Remove Bluetooth Settings if Bluetooth service is not available.
428                if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
429                    target.remove(i);
430                }
431            } else if (id == R.id.data_usage_settings) {
432                // Remove data usage when kernel module not enabled
433                final INetworkManagementService netManager = INetworkManagementService.Stub
434                        .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
435                try {
436                    if (!netManager.isBandwidthControlEnabled()) {
437                        target.remove(i);
438                    }
439                } catch (RemoteException e) {
440                    // ignored
441                }
442            } else if (id == R.id.account_settings) {
443                int headerIndex = i + 1;
444                i = insertAccountsHeaders(target, headerIndex);
445            } else if (id == R.id.user_settings) {
446                if (!UserHandle.MU_ENABLED
447                        || !UserManager.supportsMultipleUsers()
448                        || Utils.isMonkeyRunning()) {
449                    target.remove(i);
450                }
451            } else if (id == R.id.development_settings) {
452                if (!showDev) {
453                    target.remove(i);
454                }
455            } else if (id == R.id.account_add) {
456                if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
457                    target.remove(i);
458                }
459            }
460
461            if (i < target.size() && target.get(i) == header
462                    && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
463                    && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
464                target.remove(i);
465            }
466
467            // Increment if the current one wasn't removed by the Utils code.
468            if (i < target.size() && target.get(i) == header) {
469                // Hold on to the first header, when we need to reset to the top-level
470                if (mFirstHeader == null &&
471                        HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
472                    mFirstHeader = header;
473                }
474                mHeaderIndexMap.put(id, i);
475                i++;
476            }
477        }
478    }
479
480    private int insertAccountsHeaders(List<Header> target, int headerIndex) {
481        String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
482        List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
483        for (String accountType : accountTypes) {
484            CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
485            if (label == null) {
486                continue;
487            }
488
489            Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
490            boolean skipToAccount = accounts.length == 1
491                    && !mAuthenticatorHelper.hasAccountPreferences(accountType);
492            Header accHeader = new Header();
493            accHeader.title = label;
494            if (accHeader.extras == null) {
495                accHeader.extras = new Bundle();
496            }
497            if (skipToAccount) {
498                accHeader.breadCrumbTitleRes = R.string.account_sync_settings_title;
499                accHeader.breadCrumbShortTitleRes = R.string.account_sync_settings_title;
500                accHeader.fragment = AccountSyncSettings.class.getName();
501                accHeader.fragmentArguments = new Bundle();
502                // Need this for the icon
503                accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
504                accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
505                accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
506                        accounts[0]);
507            } else {
508                accHeader.breadCrumbTitle = label;
509                accHeader.breadCrumbShortTitle = label;
510                accHeader.fragment = ManageAccountsSettings.class.getName();
511                accHeader.fragmentArguments = new Bundle();
512                accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
513                accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
514                        accountType);
515                if (!isMultiPane()) {
516                    accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
517                            label.toString());
518                }
519            }
520            accountHeaders.add(accHeader);
521        }
522
523        // Sort by label
524        Collections.sort(accountHeaders, new Comparator<Header>() {
525            @Override
526            public int compare(Header h1, Header h2) {
527                return h1.title.toString().compareTo(h2.title.toString());
528            }
529        });
530
531        for (Header header : accountHeaders) {
532            target.add(headerIndex++, header);
533        }
534        if (!mListeningToAccountUpdates) {
535            AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
536            mListeningToAccountUpdates = true;
537        }
538        return headerIndex;
539    }
540
541    private void getMetaData() {
542        try {
543            ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
544                    PackageManager.GET_META_DATA);
545            if (ai == null || ai.metaData == null) return;
546            mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
547            mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
548
549            // Check if it has a parent specified and create a Header object
550            final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE);
551            String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);
552            if (parentFragmentClass != null) {
553                mParentHeader = new Header();
554                mParentHeader.fragment = parentFragmentClass;
555                if (parentHeaderTitleRes != 0) {
556                    mParentHeader.title = getResources().getString(parentHeaderTitleRes);
557                }
558            }
559        } catch (NameNotFoundException nnfe) {
560            // No recovery
561        }
562    }
563
564    @Override
565    public boolean hasNextButton() {
566        return super.hasNextButton();
567    }
568
569    @Override
570    public Button getNextButton() {
571        return super.getNextButton();
572    }
573
574    private static class HeaderAdapter extends ArrayAdapter<Header> {
575        static final int HEADER_TYPE_CATEGORY = 0;
576        static final int HEADER_TYPE_NORMAL = 1;
577        static final int HEADER_TYPE_SWITCH = 2;
578        private static final int HEADER_TYPE_COUNT = HEADER_TYPE_SWITCH + 1;
579
580        private final WifiEnabler mWifiEnabler;
581        private final BluetoothEnabler mBluetoothEnabler;
582        private AuthenticatorHelper mAuthHelper;
583
584        private static class HeaderViewHolder {
585            ImageView icon;
586            TextView title;
587            TextView summary;
588            Switch switch_;
589        }
590
591        private LayoutInflater mInflater;
592
593        static int getHeaderType(Header header) {
594            if (header.fragment == null && header.intent == null) {
595                return HEADER_TYPE_CATEGORY;
596            } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
597                return HEADER_TYPE_SWITCH;
598            } else {
599                return HEADER_TYPE_NORMAL;
600            }
601        }
602
603        @Override
604        public int getItemViewType(int position) {
605            Header header = getItem(position);
606            return getHeaderType(header);
607        }
608
609        @Override
610        public boolean areAllItemsEnabled() {
611            return false; // because of categories
612        }
613
614        @Override
615        public boolean isEnabled(int position) {
616            return getItemViewType(position) != HEADER_TYPE_CATEGORY;
617        }
618
619        @Override
620        public int getViewTypeCount() {
621            return HEADER_TYPE_COUNT;
622        }
623
624        @Override
625        public boolean hasStableIds() {
626            return true;
627        }
628
629        public HeaderAdapter(Context context, List<Header> objects,
630                AuthenticatorHelper authenticatorHelper) {
631            super(context, 0, objects);
632
633            mAuthHelper = authenticatorHelper;
634            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
635
636            // Temp Switches provided as placeholder until the adapter replaces these with actual
637            // Switches inflated from their layouts. Must be done before adapter is set in super
638            mWifiEnabler = new WifiEnabler(context, new Switch(context));
639            mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
640        }
641
642        @Override
643        public View getView(int position, View convertView, ViewGroup parent) {
644            HeaderViewHolder holder;
645            Header header = getItem(position);
646            int headerType = getHeaderType(header);
647            View view = null;
648
649            if (convertView == null) {
650                holder = new HeaderViewHolder();
651                switch (headerType) {
652                    case HEADER_TYPE_CATEGORY:
653                        view = new TextView(getContext(), null,
654                                android.R.attr.listSeparatorTextViewStyle);
655                        holder.title = (TextView) view;
656                        break;
657
658                    case HEADER_TYPE_SWITCH:
659                        view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
660                                false);
661                        holder.icon = (ImageView) view.findViewById(R.id.icon);
662                        holder.title = (TextView)
663                                view.findViewById(com.android.internal.R.id.title);
664                        holder.summary = (TextView)
665                                view.findViewById(com.android.internal.R.id.summary);
666                        holder.switch_ = (Switch) view.findViewById(R.id.switchWidget);
667                        break;
668
669                    case HEADER_TYPE_NORMAL:
670                        view = mInflater.inflate(
671                                R.layout.preference_header_item, parent,
672                                false);
673                        holder.icon = (ImageView) view.findViewById(R.id.icon);
674                        holder.title = (TextView)
675                                view.findViewById(com.android.internal.R.id.title);
676                        holder.summary = (TextView)
677                                view.findViewById(com.android.internal.R.id.summary);
678                        break;
679                }
680                view.setTag(holder);
681            } else {
682                view = convertView;
683                holder = (HeaderViewHolder) view.getTag();
684            }
685
686            // All view fields must be updated every time, because the view may be recycled
687            switch (headerType) {
688                case HEADER_TYPE_CATEGORY:
689                    holder.title.setText(header.getTitle(getContext().getResources()));
690                    break;
691
692                case HEADER_TYPE_SWITCH:
693                    // Would need a different treatment if the main menu had more switches
694                    if (header.id == R.id.wifi_settings) {
695                        mWifiEnabler.setSwitch(holder.switch_);
696                    } else {
697                        mBluetoothEnabler.setSwitch(holder.switch_);
698                    }
699                    // No break, fall through on purpose to update common fields
700
701                    //$FALL-THROUGH$
702                case HEADER_TYPE_NORMAL:
703                    if (header.extras != null
704                            && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
705                        String accType = header.extras.getString(
706                                ManageAccountsSettings.KEY_ACCOUNT_TYPE);
707                        ViewGroup.LayoutParams lp = holder.icon.getLayoutParams();
708                        lp.width = getContext().getResources().getDimensionPixelSize(
709                                R.dimen.header_icon_width);
710                        lp.height = lp.width;
711                        holder.icon.setLayoutParams(lp);
712                        Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
713                        holder.icon.setImageDrawable(icon);
714                    } else {
715                        holder.icon.setImageResource(header.iconRes);
716                    }
717                    holder.title.setText(header.getTitle(getContext().getResources()));
718                    CharSequence summary = header.getSummary(getContext().getResources());
719                    if (!TextUtils.isEmpty(summary)) {
720                        holder.summary.setVisibility(View.VISIBLE);
721                        holder.summary.setText(summary);
722                    } else {
723                        holder.summary.setVisibility(View.GONE);
724                    }
725                    break;
726            }
727
728            return view;
729        }
730
731        public void resume() {
732            mWifiEnabler.resume();
733            mBluetoothEnabler.resume();
734        }
735
736        public void pause() {
737            mWifiEnabler.pause();
738            mBluetoothEnabler.pause();
739        }
740    }
741
742    @Override
743    public void onHeaderClick(Header header, int position) {
744        boolean revert = false;
745        if (header.id == R.id.account_add) {
746            revert = true;
747        }
748
749        super.onHeaderClick(header, position);
750
751        if (revert && mLastHeader != null) {
752            highlightHeader((int) mLastHeader.id);
753        } else {
754            mLastHeader = header;
755        }
756    }
757
758    @Override
759    public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
760        // Override the fragment title for Wallpaper settings
761        int titleRes = pref.getTitleRes();
762        if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
763            titleRes = R.string.wallpaper_settings_fragment_title;
764        } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
765                && UserHandle.myUserId() != UserHandle.USER_OWNER) {
766            if (UserManager.get(this).isLinkedUser()) {
767                titleRes = R.string.profile_info_settings_title;
768            } else {
769                titleRes = R.string.user_info_settings_title;
770            }
771        }
772        startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
773                null, 0);
774        return true;
775    }
776
777    @Override
778    public boolean shouldUpRecreateTask(Intent targetIntent) {
779        return super.shouldUpRecreateTask(new Intent(this, Settings.class));
780    }
781
782    @Override
783    public void setListAdapter(ListAdapter adapter) {
784        if (adapter == null) {
785            super.setListAdapter(null);
786        } else {
787            super.setListAdapter(new HeaderAdapter(this, getHeaders(), mAuthenticatorHelper));
788        }
789    }
790
791    @Override
792    public void onAccountsUpdated(Account[] accounts) {
793        // TODO: watch for package upgrades to invalidate cache; see 7206643
794        mAuthenticatorHelper.updateAuthDescriptions(this);
795        mAuthenticatorHelper.onAccountsUpdated(this, accounts);
796        invalidateHeaders();
797    }
798
799    /*
800     * Settings subclasses for launching independently.
801     */
802    public static class BluetoothSettingsActivity extends Settings { /* empty */ }
803    public static class WirelessSettingsActivity extends Settings { /* empty */ }
804    public static class TetherSettingsActivity extends Settings { /* empty */ }
805    public static class VpnSettingsActivity extends Settings { /* empty */ }
806    public static class DateTimeSettingsActivity extends Settings { /* empty */ }
807    public static class StorageSettingsActivity extends Settings { /* empty */ }
808    public static class WifiSettingsActivity extends Settings { /* empty */ }
809    public static class WifiP2pSettingsActivity extends Settings { /* empty */ }
810    public static class InputMethodAndLanguageSettingsActivity extends Settings { /* empty */ }
811    public static class KeyboardLayoutPickerActivity extends Settings { /* empty */ }
812    public static class InputMethodAndSubtypeEnablerActivity extends Settings { /* empty */ }
813    public static class SpellCheckersSettingsActivity extends Settings { /* empty */ }
814    public static class LocalePickerActivity extends Settings { /* empty */ }
815    public static class UserDictionarySettingsActivity extends Settings { /* empty */ }
816    public static class SoundSettingsActivity extends Settings { /* empty */ }
817    public static class DisplaySettingsActivity extends Settings { /* empty */ }
818    public static class DeviceInfoSettingsActivity extends Settings { /* empty */ }
819    public static class ApplicationSettingsActivity extends Settings { /* empty */ }
820    public static class ManageApplicationsActivity extends Settings { /* empty */ }
821    public static class AppOpsSummaryActivity extends Settings { /* empty */ }
822    public static class StorageUseActivity extends Settings { /* empty */ }
823    public static class DevelopmentSettingsActivity extends Settings { /* empty */ }
824    public static class AccessibilitySettingsActivity extends Settings { /* empty */ }
825    public static class SecuritySettingsActivity extends Settings { /* empty */ }
826    public static class LocationSettingsActivity extends Settings { /* empty */ }
827    public static class PrivacySettingsActivity extends Settings { /* empty */ }
828    public static class RunningServicesActivity extends Settings { /* empty */ }
829    public static class ManageAccountsSettingsActivity extends Settings { /* empty */ }
830    public static class PowerUsageSummaryActivity extends Settings { /* empty */ }
831    public static class AccountSyncSettingsActivity extends Settings { /* empty */ }
832    public static class AccountSyncSettingsInAddAccountActivity extends Settings { /* empty */ }
833    public static class CryptKeeperSettingsActivity extends Settings { /* empty */ }
834    public static class DeviceAdminSettingsActivity extends Settings { /* empty */ }
835    public static class DataUsageSummaryActivity extends Settings { /* empty */ }
836    public static class AdvancedWifiSettingsActivity extends Settings { /* empty */ }
837    public static class TextToSpeechSettingsActivity extends Settings { /* empty */ }
838    public static class AndroidBeamSettingsActivity extends Settings { /* empty */ }
839    public static class WifiDisplaySettingsActivity extends Settings { /* empty */ }
840    public static class DreamSettingsActivity extends Settings { /* empty */ }
841    public static class NotificationStationActivity extends Settings { /* empty */ }
842    public static class UserSettingsActivity extends Settings { /* empty */ }
843    public static class NotificationAccessSettingsActivity extends Settings { /* empty */ }
844}
845