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