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