Settings.java revision faba7e4d9f132b399e49f2bb20679b1568eb617b
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 com.android.internal.util.ArrayUtils;
20import com.android.settings.accounts.AccountSyncSettings;
21import com.android.settings.applications.ManageApplications;
22import com.android.settings.bluetooth.BluetoothEnabler;
23import com.android.settings.deviceinfo.Memory;
24import com.android.settings.fuelgauge.PowerUsageSummary;
25import com.android.settings.wifi.WifiEnabler;
26
27import android.content.ComponentName;
28import android.content.Context;
29import android.content.Intent;
30import android.content.pm.ActivityInfo;
31import android.content.pm.PackageManager;
32import android.content.pm.PackageManager.NameNotFoundException;
33import android.os.Bundle;
34import android.os.UserId;
35import android.preference.Preference;
36import android.preference.PreferenceActivity;
37import android.preference.PreferenceFragment;
38import android.text.TextUtils;
39import android.util.Log;
40import android.view.LayoutInflater;
41import android.view.View;
42import android.view.View.OnClickListener;
43import android.view.ViewGroup;
44import android.widget.ArrayAdapter;
45import android.widget.Button;
46import android.widget.ImageView;
47import android.widget.ListAdapter;
48import android.widget.Switch;
49import android.widget.TextView;
50
51import java.util.ArrayList;
52import java.util.HashMap;
53import java.util.List;
54
55/**
56 * Top-level settings activity to handle single pane and double pane UI layout.
57 */
58public class Settings extends PreferenceActivity implements ButtonBarHandler {
59
60    private static final String LOG_TAG = "Settings";
61    private static final String META_DATA_KEY_HEADER_ID =
62        "com.android.settings.TOP_LEVEL_HEADER_ID";
63    private static final String META_DATA_KEY_FRAGMENT_CLASS =
64        "com.android.settings.FRAGMENT_CLASS";
65    private static final String META_DATA_KEY_PARENT_TITLE =
66        "com.android.settings.PARENT_FRAGMENT_TITLE";
67    private static final String META_DATA_KEY_PARENT_FRAGMENT_CLASS =
68        "com.android.settings.PARENT_FRAGMENT_CLASS";
69
70    private static final String EXTRA_CLEAR_UI_OPTIONS = "settings:remove_ui_options";
71
72    private static final String SAVE_KEY_CURRENT_HEADER = "com.android.settings.CURRENT_HEADER";
73    private static final String SAVE_KEY_PARENT_HEADER = "com.android.settings.PARENT_HEADER";
74
75    private String mFragmentClass;
76    private int mTopLevelHeaderId;
77    private Header mFirstHeader;
78    private Header mCurrentHeader;
79    private Header mParentHeader;
80    private boolean mInLocalHeaderSwitch;
81
82    // Show only these settings for restricted users
83    private int[] SETTINGS_FOR_RESTRICTED = {
84            R.id.wifi_settings,
85            R.id.bluetooth_settings,
86            R.id.sound_settings,
87            R.id.display_settings,
88            //R.id.security_settings,
89            R.id.sync_settings,
90            R.id.about_settings
91    };
92
93    private boolean mEnableUserManagement = false;
94
95    // TODO: Update Call Settings based on airplane mode state.
96
97    protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
98    private List<Header> mHeaders;
99
100    @Override
101    protected void onCreate(Bundle savedInstanceState) {
102        if (getIntent().getBooleanExtra(EXTRA_CLEAR_UI_OPTIONS, false)) {
103            getWindow().setUiOptions(0);
104        }
105
106        if (android.provider.Settings.Secure.getInt(getContentResolver(), "multiuser_enabled", -1)
107                > 0) {
108            mEnableUserManagement = true;
109        }
110
111        getMetaData();
112        mInLocalHeaderSwitch = true;
113        super.onCreate(savedInstanceState);
114        mInLocalHeaderSwitch = false;
115
116        if (!onIsHidingHeaders() && onIsMultiPane()) {
117            highlightHeader();
118            // Force the title so that it doesn't get overridden by a direct launch of
119            // a specific settings screen.
120            setTitle(R.string.settings_label);
121        }
122
123        // Retrieve any saved state
124        if (savedInstanceState != null) {
125            mCurrentHeader = savedInstanceState.getParcelable(SAVE_KEY_CURRENT_HEADER);
126            mParentHeader = savedInstanceState.getParcelable(SAVE_KEY_PARENT_HEADER);
127        }
128
129        // If the current header was saved, switch to it
130        if (savedInstanceState != null && mCurrentHeader != null) {
131            //switchToHeaderLocal(mCurrentHeader);
132            showBreadCrumbs(mCurrentHeader.title, null);
133        }
134
135        if (mParentHeader != null) {
136            setParentTitle(mParentHeader.title, null, new OnClickListener() {
137                public void onClick(View v) {
138                    switchToParent(mParentHeader.fragment);
139                }
140            });
141        }
142
143        // TODO Add support for android.R.id.home in all Setting's onOptionsItemSelected
144        // getActionBar().setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP,
145        // ActionBar.DISPLAY_HOME_AS_UP);
146    }
147
148    @Override
149    protected void onSaveInstanceState(Bundle outState) {
150        super.onSaveInstanceState(outState);
151
152        // Save the current fragment, if it is the same as originally launched
153        if (mCurrentHeader != null) {
154            outState.putParcelable(SAVE_KEY_CURRENT_HEADER, mCurrentHeader);
155        }
156        if (mParentHeader != null) {
157            outState.putParcelable(SAVE_KEY_PARENT_HEADER, mParentHeader);
158        }
159    }
160
161    @Override
162    public void onResume() {
163        super.onResume();
164
165        ListAdapter listAdapter = getListAdapter();
166        if (listAdapter instanceof HeaderAdapter) {
167            ((HeaderAdapter) listAdapter).resume();
168        }
169    }
170
171    @Override
172    public void onPause() {
173        super.onPause();
174
175        ListAdapter listAdapter = getListAdapter();
176        if (listAdapter instanceof HeaderAdapter) {
177            ((HeaderAdapter) listAdapter).pause();
178        }
179    }
180
181    private void switchToHeaderLocal(Header header) {
182        mInLocalHeaderSwitch = true;
183        switchToHeader(header);
184        mInLocalHeaderSwitch = false;
185    }
186
187    @Override
188    public void switchToHeader(Header header) {
189        if (!mInLocalHeaderSwitch) {
190            mCurrentHeader = null;
191            mParentHeader = null;
192        }
193        super.switchToHeader(header);
194    }
195
196    /**
197     * Switch to parent fragment and store the grand parent's info
198     * @param className name of the activity wrapper for the parent fragment.
199     */
200    private void switchToParent(String className) {
201        final ComponentName cn = new ComponentName(this, className);
202        try {
203            final PackageManager pm = getPackageManager();
204            final ActivityInfo parentInfo = pm.getActivityInfo(cn, PackageManager.GET_META_DATA);
205
206            if (parentInfo != null && parentInfo.metaData != null) {
207                String fragmentClass = parentInfo.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
208                CharSequence fragmentTitle = parentInfo.loadLabel(pm);
209                Header parentHeader = new Header();
210                parentHeader.fragment = fragmentClass;
211                parentHeader.title = fragmentTitle;
212                mCurrentHeader = parentHeader;
213
214                switchToHeaderLocal(parentHeader);
215                highlightHeader();
216
217                mParentHeader = new Header();
218                mParentHeader.fragment
219                        = parentInfo.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);
220                mParentHeader.title = parentInfo.metaData.getString(META_DATA_KEY_PARENT_TITLE);
221            }
222        } catch (NameNotFoundException nnfe) {
223            Log.w(LOG_TAG, "Could not find parent activity : " + className);
224        }
225    }
226
227    @Override
228    public void onNewIntent(Intent intent) {
229        super.onNewIntent(intent);
230
231        // If it is not launched from history, then reset to top-level
232        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0
233                && mFirstHeader != null && !onIsHidingHeaders() && onIsMultiPane()) {
234            switchToHeaderLocal(mFirstHeader);
235        }
236    }
237
238    private void highlightHeader() {
239        if (mTopLevelHeaderId != 0) {
240            Integer index = mHeaderIndexMap.get(mTopLevelHeaderId);
241            if (index != null) {
242                getListView().setItemChecked(index, true);
243                getListView().smoothScrollToPosition(index);
244            }
245        }
246    }
247
248    @Override
249    public Intent getIntent() {
250        Intent superIntent = super.getIntent();
251        String startingFragment = getStartingFragmentClass(superIntent);
252        // This is called from super.onCreate, isMultiPane() is not yet reliable
253        // Do not use onIsHidingHeaders either, which relies itself on this method
254        if (startingFragment != null && !onIsMultiPane()) {
255            Intent modIntent = new Intent(superIntent);
256            modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
257            Bundle args = superIntent.getExtras();
258            if (args != null) {
259                args = new Bundle(args);
260            } else {
261                args = new Bundle();
262            }
263            args.putParcelable("intent", superIntent);
264            modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
265            return modIntent;
266        }
267        return superIntent;
268    }
269
270    /**
271     * Checks if the component name in the intent is different from the Settings class and
272     * returns the class name to load as a fragment.
273     */
274    protected String getStartingFragmentClass(Intent intent) {
275        if (mFragmentClass != null) return mFragmentClass;
276
277        String intentClass = intent.getComponent().getClassName();
278        if (intentClass.equals(getClass().getName())) return null;
279
280        if ("com.android.settings.ManageApplications".equals(intentClass)
281                || "com.android.settings.RunningServices".equals(intentClass)
282                || "com.android.settings.applications.StorageUse".equals(intentClass)) {
283            // Old names of manage apps.
284            intentClass = com.android.settings.applications.ManageApplications.class.getName();
285        }
286
287        return intentClass;
288    }
289
290    /**
291     * Override initial header when an activity-alias is causing Settings to be launched
292     * for a specific fragment encoded in the android:name parameter.
293     */
294    @Override
295    public Header onGetInitialHeader() {
296        String fragmentClass = getStartingFragmentClass(super.getIntent());
297        if (fragmentClass != null) {
298            Header header = new Header();
299            header.fragment = fragmentClass;
300            header.title = getTitle();
301            header.fragmentArguments = getIntent().getExtras();
302            mCurrentHeader = header;
303            return header;
304        }
305
306        return mFirstHeader;
307    }
308
309    @Override
310    public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
311            int titleRes, int shortTitleRes) {
312        Intent intent = super.onBuildStartFragmentIntent(fragmentName, args,
313                titleRes, shortTitleRes);
314
315        // some fragments want to avoid split actionbar
316        if (DataUsageSummary.class.getName().equals(fragmentName) ||
317                PowerUsageSummary.class.getName().equals(fragmentName) ||
318                AccountSyncSettings.class.getName().equals(fragmentName) ||
319                UserDictionarySettings.class.getName().equals(fragmentName) ||
320                Memory.class.getName().equals(fragmentName) ||
321                ManageApplications.class.getName().equals(fragmentName)) {
322            intent.putExtra(EXTRA_CLEAR_UI_OPTIONS, true);
323        }
324
325        intent.setClass(this, SubSettings.class);
326        return intent;
327    }
328
329    /**
330     * Populate the activity with the top-level headers.
331     */
332    @Override
333    public void onBuildHeaders(List<Header> headers) {
334        loadHeadersFromResource(R.xml.settings_headers, headers);
335
336        updateHeaderList(headers);
337
338        mHeaders = headers;
339    }
340
341    private void updateHeaderList(List<Header> target) {
342        int i = 0;
343        while (i < target.size()) {
344            Header header = target.get(i);
345            // Ids are integers, so downcasting
346            int id = (int) header.id;
347            if (id == R.id.dock_settings) {
348                if (!needsDockSettings())
349                    target.remove(header);
350            } else if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
351                Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
352            } else if (id == R.id.wifi_settings) {
353                // Remove WiFi Settings if WiFi service is not available.
354                if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
355                    target.remove(header);
356                }
357            } else if (id == R.id.bluetooth_settings) {
358                // Remove Bluetooth Settings if Bluetooth service is not available.
359                if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
360                    target.remove(header);
361                }
362            } else if (id == R.id.user_settings) {
363                if (!mEnableUserManagement
364                        || !UserId.MU_ENABLED || UserId.myUserId() != 0
365                        || !getResources().getBoolean(R.bool.enable_user_management)
366                        || Utils.isMonkeyRunning()) {
367                    target.remove(header);
368                }
369            }
370            if (UserId.MU_ENABLED && UserId.myUserId() != 0
371                    && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
372                target.remove(header);
373            }
374
375            // Increment if the current one wasn't removed by the Utils code.
376            if (target.get(i) == header) {
377                // Hold on to the first header, when we need to reset to the top-level
378                if (mFirstHeader == null &&
379                        HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
380                    mFirstHeader = header;
381                }
382                mHeaderIndexMap.put(id, i);
383                i++;
384            }
385        }
386    }
387
388    private boolean needsDockSettings() {
389        return getResources().getBoolean(R.bool.has_dock_settings);
390    }
391
392    private void getMetaData() {
393        try {
394            ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
395                    PackageManager.GET_META_DATA);
396            if (ai == null || ai.metaData == null) return;
397            mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
398            mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
399
400            // Check if it has a parent specified and create a Header object
401            final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE);
402            String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);
403            if (parentFragmentClass != null) {
404                mParentHeader = new Header();
405                mParentHeader.fragment = parentFragmentClass;
406                if (parentHeaderTitleRes != 0) {
407                    mParentHeader.title = getResources().getString(parentHeaderTitleRes);
408                }
409            }
410        } catch (NameNotFoundException nnfe) {
411            // No recovery
412        }
413    }
414
415    @Override
416    public boolean hasNextButton() {
417        return super.hasNextButton();
418    }
419
420    @Override
421    public Button getNextButton() {
422        return super.getNextButton();
423    }
424
425    private static class HeaderAdapter extends ArrayAdapter<Header> {
426        static final int HEADER_TYPE_CATEGORY = 0;
427        static final int HEADER_TYPE_NORMAL = 1;
428        static final int HEADER_TYPE_SWITCH = 2;
429        private static final int HEADER_TYPE_COUNT = HEADER_TYPE_SWITCH + 1;
430
431        private final WifiEnabler mWifiEnabler;
432        private final BluetoothEnabler mBluetoothEnabler;
433
434        private static class HeaderViewHolder {
435            ImageView icon;
436            TextView title;
437            TextView summary;
438            Switch switch_;
439        }
440
441        private LayoutInflater mInflater;
442
443        static int getHeaderType(Header header) {
444            if (header.fragment == null && header.intent == null) {
445                return HEADER_TYPE_CATEGORY;
446            } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
447                return HEADER_TYPE_SWITCH;
448            } else {
449                return HEADER_TYPE_NORMAL;
450            }
451        }
452
453        @Override
454        public int getItemViewType(int position) {
455            Header header = getItem(position);
456            return getHeaderType(header);
457        }
458
459        @Override
460        public boolean areAllItemsEnabled() {
461            return false; // because of categories
462        }
463
464        @Override
465        public boolean isEnabled(int position) {
466            return getItemViewType(position) != HEADER_TYPE_CATEGORY;
467        }
468
469        @Override
470        public int getViewTypeCount() {
471            return HEADER_TYPE_COUNT;
472        }
473
474        @Override
475        public boolean hasStableIds() {
476            return true;
477        }
478
479        public HeaderAdapter(Context context, List<Header> objects) {
480            super(context, 0, objects);
481            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
482
483            // Temp Switches provided as placeholder until the adapter replaces these with actual
484            // Switches inflated from their layouts. Must be done before adapter is set in super
485            mWifiEnabler = new WifiEnabler(context, new Switch(context));
486            mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
487        }
488
489        @Override
490        public View getView(int position, View convertView, ViewGroup parent) {
491            HeaderViewHolder holder;
492            Header header = getItem(position);
493            int headerType = getHeaderType(header);
494            View view = null;
495
496            if (convertView == null) {
497                holder = new HeaderViewHolder();
498                switch (headerType) {
499                    case HEADER_TYPE_CATEGORY:
500                        view = new TextView(getContext(), null,
501                                android.R.attr.listSeparatorTextViewStyle);
502                        holder.title = (TextView) view;
503                        break;
504
505                    case HEADER_TYPE_SWITCH:
506                        view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
507                                false);
508                        holder.icon = (ImageView) view.findViewById(R.id.icon);
509                        holder.title = (TextView)
510                                view.findViewById(com.android.internal.R.id.title);
511                        holder.summary = (TextView)
512                                view.findViewById(com.android.internal.R.id.summary);
513                        holder.switch_ = (Switch) view.findViewById(R.id.switchWidget);
514                        break;
515
516                    case HEADER_TYPE_NORMAL:
517                        view = mInflater.inflate(
518                                com.android.internal.R.layout.preference_header_item, parent,
519                                false);
520                        holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
521                        holder.title = (TextView)
522                                view.findViewById(com.android.internal.R.id.title);
523                        holder.summary = (TextView)
524                                view.findViewById(com.android.internal.R.id.summary);
525                        break;
526                }
527                view.setTag(holder);
528            } else {
529                view = convertView;
530                holder = (HeaderViewHolder) view.getTag();
531            }
532
533            // All view fields must be updated every time, because the view may be recycled
534            switch (headerType) {
535                case HEADER_TYPE_CATEGORY:
536                    holder.title.setText(header.getTitle(getContext().getResources()));
537                    break;
538
539                case HEADER_TYPE_SWITCH:
540                    // Would need a different treatment if the main menu had more switches
541                    if (header.id == R.id.wifi_settings) {
542                        mWifiEnabler.setSwitch(holder.switch_);
543                    } else {
544                        mBluetoothEnabler.setSwitch(holder.switch_);
545                    }
546                    // No break, fall through on purpose to update common fields
547
548                    //$FALL-THROUGH$
549                case HEADER_TYPE_NORMAL:
550                    holder.icon.setImageResource(header.iconRes);
551                    holder.title.setText(header.getTitle(getContext().getResources()));
552                    CharSequence summary = header.getSummary(getContext().getResources());
553                    if (!TextUtils.isEmpty(summary)) {
554                        holder.summary.setVisibility(View.VISIBLE);
555                        holder.summary.setText(summary);
556                    } else {
557                        holder.summary.setVisibility(View.GONE);
558                    }
559                    break;
560            }
561
562            return view;
563        }
564
565        public void resume() {
566            mWifiEnabler.resume();
567            mBluetoothEnabler.resume();
568        }
569
570        public void pause() {
571            mWifiEnabler.pause();
572            mBluetoothEnabler.pause();
573        }
574    }
575
576    @Override
577    public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
578        // Override the fragment title for Wallpaper settings
579        int titleRes = pref.getTitleRes();
580        if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
581            titleRes = R.string.wallpaper_settings_fragment_title;
582        }
583        startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, null, null, 0);
584        return true;
585    }
586
587    @Override
588    public void setListAdapter(ListAdapter adapter) {
589        if (mHeaders == null) {
590            mHeaders = new ArrayList<Header>();
591            // When the saved state provides the list of headers, onBuildHeaders is not called
592            // Copy the list of Headers from the adapter, preserving their order
593            for (int i = 0; i < adapter.getCount(); i++) {
594                mHeaders.add((Header) adapter.getItem(i));
595            }
596        }
597
598        // Ignore the adapter provided by PreferenceActivity and substitute ours instead
599        super.setListAdapter(new HeaderAdapter(this, mHeaders));
600    }
601
602    /*
603     * Settings subclasses for launching independently.
604     */
605    public static class BluetoothSettingsActivity extends Settings { /* empty */ }
606    public static class WirelessSettingsActivity extends Settings { /* empty */ }
607    public static class TetherSettingsActivity extends Settings { /* empty */ }
608    public static class VpnSettingsActivity extends Settings { /* empty */ }
609    public static class DateTimeSettingsActivity extends Settings { /* empty */ }
610    public static class StorageSettingsActivity extends Settings { /* empty */ }
611    public static class WifiSettingsActivity extends Settings { /* empty */ }
612    public static class WifiP2pSettingsActivity extends Settings { /* empty */ }
613    public static class InputMethodAndLanguageSettingsActivity extends Settings { /* empty */ }
614    public static class InputMethodAndSubtypeEnablerActivity extends Settings { /* empty */ }
615    public static class SpellCheckersSettingsActivity extends Settings { /* empty */ }
616    public static class LocalePickerActivity extends Settings { /* empty */ }
617    public static class UserDictionarySettingsActivity extends Settings { /* empty */ }
618    public static class SoundSettingsActivity extends Settings { /* empty */ }
619    public static class DisplaySettingsActivity extends Settings { /* empty */ }
620    public static class DeviceInfoSettingsActivity extends Settings { /* empty */ }
621    public static class ApplicationSettingsActivity extends Settings { /* empty */ }
622    public static class ManageApplicationsActivity extends Settings { /* empty */ }
623    public static class StorageUseActivity extends Settings { /* empty */ }
624    public static class DevelopmentSettingsActivity extends Settings { /* empty */ }
625    public static class AccessibilitySettingsActivity extends Settings { /* empty */ }
626    public static class SecuritySettingsActivity extends Settings { /* empty */ }
627    public static class LocationSettingsActivity extends Settings { /* empty */ }
628    public static class PrivacySettingsActivity extends Settings { /* empty */ }
629    public static class DockSettingsActivity extends Settings { /* empty */ }
630    public static class RunningServicesActivity extends Settings { /* empty */ }
631    public static class ManageAccountsSettingsActivity extends Settings { /* empty */ }
632    public static class PowerUsageSummaryActivity extends Settings { /* empty */ }
633    public static class AccountSyncSettingsActivity extends Settings { /* empty */ }
634    public static class AccountSyncSettingsInAddAccountActivity extends Settings { /* empty */ }
635    public static class CryptKeeperSettingsActivity extends Settings { /* empty */ }
636    public static class DeviceAdminSettingsActivity extends Settings { /* empty */ }
637    public static class DataUsageSummaryActivity extends Settings { /* empty */ }
638    public static class AdvancedWifiSettingsActivity extends Settings { /* empty */ }
639    public static class TextToSpeechSettingsActivity extends Settings { /* empty */ }
640    public static class AndroidBeamSettingsActivity extends Settings { /* empty */ }
641}
642