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 17 package com.android.settings; 18 19 import com.android.settings.accounts.AccountSyncSettings; 20 import com.android.settings.bluetooth.BluetoothEnabler; 21 import com.android.settings.fuelgauge.PowerUsageSummary; 22 import com.android.settings.wifi.WifiEnabler; 23 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.ActivityInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.os.Bundle; 31 import android.preference.Preference; 32 import android.preference.PreferenceActivity; 33 import android.preference.PreferenceFragment; 34 import android.text.TextUtils; 35 import android.util.Log; 36 import android.view.LayoutInflater; 37 import android.view.View; 38 import android.view.View.OnClickListener; 39 import android.view.ViewGroup; 40 import android.widget.ArrayAdapter; 41 import android.widget.Button; 42 import android.widget.ImageView; 43 import android.widget.ListAdapter; 44 import android.widget.Switch; 45 import android.widget.TextView; 46 47 import java.util.ArrayList; 48 import java.util.HashMap; 49 import java.util.List; 50 51 /** 52 * Top-level settings activity to handle single pane and double pane UI layout. 53 */ 54 public 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_CLEAR_UI_OPTIONS = "settings:remove_ui_options"; 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 if (getIntent().getBooleanExtra(EXTRA_CLEAR_UI_OPTIONS, false)) { 86 getWindow().setUiOptions(0); 87 } 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 highlightHeader(); 194 195 mParentHeader = new Header(); 196 mParentHeader.fragment 197 = parentInfo.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS); 198 mParentHeader.title = parentInfo.metaData.getString(META_DATA_KEY_PARENT_TITLE); 199 } 200 } catch (NameNotFoundException nnfe) { 201 Log.w(LOG_TAG, "Could not find parent activity : " + className); 202 } 203 } 204 205 @Override 206 public void onNewIntent(Intent intent) { 207 super.onNewIntent(intent); 208 209 // If it is not launched from history, then reset to top-level 210 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0 211 && mFirstHeader != null && !onIsHidingHeaders() && onIsMultiPane()) { 212 switchToHeaderLocal(mFirstHeader); 213 } 214 } 215 216 private void highlightHeader() { 217 if (mTopLevelHeaderId != 0) { 218 Integer index = mHeaderIndexMap.get(mTopLevelHeaderId); 219 if (index != null) { 220 getListView().setItemChecked(index, true); 221 getListView().smoothScrollToPosition(index); 222 } 223 } 224 } 225 226 @Override 227 public Intent getIntent() { 228 Intent superIntent = super.getIntent(); 229 String startingFragment = getStartingFragmentClass(superIntent); 230 // This is called from super.onCreate, isMultiPane() is not yet reliable 231 // Do not use onIsHidingHeaders either, which relies itself on this method 232 if (startingFragment != null && !onIsMultiPane()) { 233 Intent modIntent = new Intent(superIntent); 234 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment); 235 Bundle args = superIntent.getExtras(); 236 if (args != null) { 237 args = new Bundle(args); 238 } else { 239 args = new Bundle(); 240 } 241 args.putParcelable("intent", superIntent); 242 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras()); 243 return modIntent; 244 } 245 return superIntent; 246 } 247 248 /** 249 * Checks if the component name in the intent is different from the Settings class and 250 * returns the class name to load as a fragment. 251 */ 252 protected String getStartingFragmentClass(Intent intent) { 253 if (mFragmentClass != null) return mFragmentClass; 254 255 String intentClass = intent.getComponent().getClassName(); 256 if (intentClass.equals(getClass().getName())) return null; 257 258 if ("com.android.settings.ManageApplications".equals(intentClass) 259 || "com.android.settings.RunningServices".equals(intentClass) 260 || "com.android.settings.applications.StorageUse".equals(intentClass)) { 261 // Old names of manage apps. 262 intentClass = com.android.settings.applications.ManageApplications.class.getName(); 263 } 264 265 return intentClass; 266 } 267 268 /** 269 * Override initial header when an activity-alias is causing Settings to be launched 270 * for a specific fragment encoded in the android:name parameter. 271 */ 272 @Override 273 public Header onGetInitialHeader() { 274 String fragmentClass = getStartingFragmentClass(super.getIntent()); 275 if (fragmentClass != null) { 276 Header header = new Header(); 277 header.fragment = fragmentClass; 278 header.title = getTitle(); 279 header.fragmentArguments = getIntent().getExtras(); 280 mCurrentHeader = header; 281 return header; 282 } 283 284 return mFirstHeader; 285 } 286 287 @Override 288 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, 289 int titleRes, int shortTitleRes) { 290 Intent intent = super.onBuildStartFragmentIntent(fragmentName, args, 291 titleRes, shortTitleRes); 292 293 // some fragments want to avoid split actionbar 294 if (DataUsageSummary.class.getName().equals(fragmentName) || 295 PowerUsageSummary.class.getName().equals(fragmentName) || 296 AccountSyncSettings.class.getName().equals(fragmentName) || 297 UserDictionarySettings.class.getName().equals(fragmentName)) { 298 intent.putExtra(EXTRA_CLEAR_UI_OPTIONS, true); 299 } 300 301 intent.setClass(this, SubSettings.class); 302 return intent; 303 } 304 305 /** 306 * Populate the activity with the top-level headers. 307 */ 308 @Override 309 public void onBuildHeaders(List<Header> headers) { 310 loadHeadersFromResource(R.xml.settings_headers, headers); 311 312 updateHeaderList(headers); 313 314 mHeaders = headers; 315 } 316 317 private void updateHeaderList(List<Header> target) { 318 int i = 0; 319 while (i < target.size()) { 320 Header header = target.get(i); 321 // Ids are integers, so downcasting 322 int id = (int) header.id; 323 if (id == R.id.dock_settings) { 324 if (!needsDockSettings()) 325 target.remove(header); 326 } else if (id == R.id.operator_settings || id == R.id.manufacturer_settings) { 327 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header); 328 } else if (id == R.id.wifi_settings) { 329 // Remove WiFi Settings if WiFi service is not available. 330 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) { 331 target.remove(header); 332 } 333 } else if (id == R.id.bluetooth_settings) { 334 // Remove Bluetooth Settings if Bluetooth service is not available. 335 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) { 336 target.remove(header); 337 } 338 } 339 340 // Increment if the current one wasn't removed by the Utils code. 341 if (target.get(i) == header) { 342 // Hold on to the first header, when we need to reset to the top-level 343 if (mFirstHeader == null && 344 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) { 345 mFirstHeader = header; 346 } 347 mHeaderIndexMap.put(id, i); 348 i++; 349 } 350 } 351 } 352 353 private boolean needsDockSettings() { 354 return getResources().getBoolean(R.bool.has_dock_settings); 355 } 356 357 private void getMetaData() { 358 try { 359 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(), 360 PackageManager.GET_META_DATA); 361 if (ai == null || ai.metaData == null) return; 362 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID); 363 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS); 364 365 // Check if it has a parent specified and create a Header object 366 final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE); 367 String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS); 368 if (parentFragmentClass != null) { 369 mParentHeader = new Header(); 370 mParentHeader.fragment = parentFragmentClass; 371 if (parentHeaderTitleRes != 0) { 372 mParentHeader.title = getResources().getString(parentHeaderTitleRes); 373 } 374 } 375 } catch (NameNotFoundException nnfe) { 376 // No recovery 377 } 378 } 379 380 @Override 381 public boolean hasNextButton() { 382 return super.hasNextButton(); 383 } 384 385 @Override 386 public Button getNextButton() { 387 return super.getNextButton(); 388 } 389 390 private static class HeaderAdapter extends ArrayAdapter<Header> { 391 static final int HEADER_TYPE_CATEGORY = 0; 392 static final int HEADER_TYPE_NORMAL = 1; 393 static final int HEADER_TYPE_SWITCH = 2; 394 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_SWITCH + 1; 395 396 private final WifiEnabler mWifiEnabler; 397 private final BluetoothEnabler mBluetoothEnabler; 398 399 private static class HeaderViewHolder { 400 ImageView icon; 401 TextView title; 402 TextView summary; 403 Switch switch_; 404 } 405 406 private LayoutInflater mInflater; 407 408 static int getHeaderType(Header header) { 409 if (header.fragment == null && header.intent == null) { 410 return HEADER_TYPE_CATEGORY; 411 } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) { 412 return HEADER_TYPE_SWITCH; 413 } else { 414 return HEADER_TYPE_NORMAL; 415 } 416 } 417 418 @Override 419 public int getItemViewType(int position) { 420 Header header = getItem(position); 421 return getHeaderType(header); 422 } 423 424 @Override 425 public boolean areAllItemsEnabled() { 426 return false; // because of categories 427 } 428 429 @Override 430 public boolean isEnabled(int position) { 431 return getItemViewType(position) != HEADER_TYPE_CATEGORY; 432 } 433 434 @Override 435 public int getViewTypeCount() { 436 return HEADER_TYPE_COUNT; 437 } 438 439 @Override 440 public boolean hasStableIds() { 441 return true; 442 } 443 444 public HeaderAdapter(Context context, List<Header> objects) { 445 super(context, 0, objects); 446 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 447 448 // Temp Switches provided as placeholder until the adapter replaces these with actual 449 // Switches inflated from their layouts. Must be done before adapter is set in super 450 mWifiEnabler = new WifiEnabler(context, new Switch(context)); 451 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context)); 452 } 453 454 @Override 455 public View getView(int position, View convertView, ViewGroup parent) { 456 HeaderViewHolder holder; 457 Header header = getItem(position); 458 int headerType = getHeaderType(header); 459 View view = null; 460 461 if (convertView == null) { 462 holder = new HeaderViewHolder(); 463 switch (headerType) { 464 case HEADER_TYPE_CATEGORY: 465 view = new TextView(getContext(), null, 466 android.R.attr.listSeparatorTextViewStyle); 467 holder.title = (TextView) view; 468 break; 469 470 case HEADER_TYPE_SWITCH: 471 view = mInflater.inflate(R.layout.preference_header_switch_item, parent, 472 false); 473 holder.icon = (ImageView) view.findViewById(R.id.icon); 474 holder.title = (TextView) 475 view.findViewById(com.android.internal.R.id.title); 476 holder.summary = (TextView) 477 view.findViewById(com.android.internal.R.id.summary); 478 holder.switch_ = (Switch) view.findViewById(R.id.switchWidget); 479 break; 480 481 case HEADER_TYPE_NORMAL: 482 view = mInflater.inflate( 483 com.android.internal.R.layout.preference_header_item, parent, 484 false); 485 holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon); 486 holder.title = (TextView) 487 view.findViewById(com.android.internal.R.id.title); 488 holder.summary = (TextView) 489 view.findViewById(com.android.internal.R.id.summary); 490 break; 491 } 492 view.setTag(holder); 493 } else { 494 view = convertView; 495 holder = (HeaderViewHolder) view.getTag(); 496 } 497 498 // All view fields must be updated every time, because the view may be recycled 499 switch (headerType) { 500 case HEADER_TYPE_CATEGORY: 501 holder.title.setText(header.getTitle(getContext().getResources())); 502 break; 503 504 case HEADER_TYPE_SWITCH: 505 // Would need a different treatment if the main menu had more switches 506 if (header.id == R.id.wifi_settings) { 507 mWifiEnabler.setSwitch(holder.switch_); 508 } else { 509 mBluetoothEnabler.setSwitch(holder.switch_); 510 } 511 // No break, fall through on purpose to update common fields 512 513 //$FALL-THROUGH$ 514 case HEADER_TYPE_NORMAL: 515 holder.icon.setImageResource(header.iconRes); 516 holder.title.setText(header.getTitle(getContext().getResources())); 517 CharSequence summary = header.getSummary(getContext().getResources()); 518 if (!TextUtils.isEmpty(summary)) { 519 holder.summary.setVisibility(View.VISIBLE); 520 holder.summary.setText(summary); 521 } else { 522 holder.summary.setVisibility(View.GONE); 523 } 524 break; 525 } 526 527 return view; 528 } 529 530 public void resume() { 531 mWifiEnabler.resume(); 532 mBluetoothEnabler.resume(); 533 } 534 535 public void pause() { 536 mWifiEnabler.pause(); 537 mBluetoothEnabler.pause(); 538 } 539 } 540 541 @Override 542 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) { 543 // Override the fragment title for Wallpaper settings 544 int titleRes = pref.getTitleRes(); 545 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) { 546 titleRes = R.string.wallpaper_settings_fragment_title; 547 } 548 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, null, null, 0); 549 return true; 550 } 551 552 @Override 553 public void setListAdapter(ListAdapter adapter) { 554 if (mHeaders == null) { 555 mHeaders = new ArrayList<Header>(); 556 // When the saved state provides the list of headers, onBuildHeaders is not called 557 // Copy the list of Headers from the adapter, preserving their order 558 for (int i = 0; i < adapter.getCount(); i++) { 559 mHeaders.add((Header) adapter.getItem(i)); 560 } 561 } 562 563 // Ignore the adapter provided by PreferenceActivity and substitute ours instead 564 super.setListAdapter(new HeaderAdapter(this, mHeaders)); 565 } 566 567 /* 568 * Settings subclasses for launching independently. 569 */ 570 public static class BluetoothSettingsActivity extends Settings { /* empty */ } 571 public static class WirelessSettingsActivity extends Settings { /* empty */ } 572 public static class TetherSettingsActivity extends Settings { /* empty */ } 573 public static class VpnSettingsActivity extends Settings { /* empty */ } 574 public static class DateTimeSettingsActivity extends Settings { /* empty */ } 575 public static class StorageSettingsActivity extends Settings { /* empty */ } 576 public static class WifiSettingsActivity extends Settings { /* empty */ } 577 public static class WifiP2pSettingsActivity extends Settings { /* empty */ } 578 public static class InputMethodAndLanguageSettingsActivity extends Settings { /* empty */ } 579 public static class InputMethodAndSubtypeEnablerActivity extends Settings { /* empty */ } 580 public static class SpellCheckersSettingsActivity extends Settings { /* empty */ } 581 public static class LocalePickerActivity extends Settings { /* empty */ } 582 public static class UserDictionarySettingsActivity extends Settings { /* empty */ } 583 public static class SoundSettingsActivity extends Settings { /* empty */ } 584 public static class DisplaySettingsActivity extends Settings { /* empty */ } 585 public static class DeviceInfoSettingsActivity extends Settings { /* empty */ } 586 public static class ApplicationSettingsActivity extends Settings { /* empty */ } 587 public static class ManageApplicationsActivity extends Settings { /* empty */ } 588 public static class StorageUseActivity extends Settings { /* empty */ } 589 public static class DevelopmentSettingsActivity extends Settings { /* empty */ } 590 public static class AccessibilitySettingsActivity extends Settings { /* empty */ } 591 public static class SecuritySettingsActivity extends Settings { /* empty */ } 592 public static class LocationSettingsActivity extends Settings { /* empty */ } 593 public static class PrivacySettingsActivity extends Settings { /* empty */ } 594 public static class DockSettingsActivity extends Settings { /* empty */ } 595 public static class RunningServicesActivity extends Settings { /* empty */ } 596 public static class ManageAccountsSettingsActivity extends Settings { /* empty */ } 597 public static class PowerUsageSummaryActivity extends Settings { /* empty */ } 598 public static class AccountSyncSettingsActivity extends Settings { /* empty */ } 599 public static class AccountSyncSettingsInAddAccountActivity extends Settings { /* empty */ } 600 public static class CryptKeeperSettingsActivity extends Settings { /* empty */ } 601 public static class DeviceAdminSettingsActivity extends Settings { /* empty */ } 602 public static class DataUsageSummaryActivity extends Settings { /* empty */ } 603 public static class AdvancedWifiSettingsActivity extends Settings { /* empty */ } 604 public static class TextToSpeechSettingsActivity extends Settings { /* empty */ } 605 public static class AndroidBeamSettingsActivity extends Settings { /* empty */ } 606 } 607