1 /* 2 * Copyright (C) 2014 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 static com.android.settings.dashboard.DashboardTile.TILE_ID_UNDEFINED; 20 21 import android.app.ActionBar; 22 import android.app.Activity; 23 import android.app.Fragment; 24 import android.app.FragmentManager; 25 import android.app.FragmentTransaction; 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.SharedPreferences; 32 import android.content.pm.ActivityInfo; 33 import android.content.pm.PackageManager; 34 import android.content.pm.PackageManager.NameNotFoundException; 35 import android.content.pm.ResolveInfo; 36 import android.content.res.Configuration; 37 import android.content.res.TypedArray; 38 import android.content.res.XmlResourceParser; 39 import android.nfc.NfcAdapter; 40 import android.os.Bundle; 41 import android.os.Handler; 42 import android.os.Message; 43 import android.os.UserHandle; 44 import android.os.UserManager; 45 import android.preference.Preference; 46 import android.preference.PreferenceFragment; 47 import android.preference.PreferenceManager; 48 import android.preference.PreferenceScreen; 49 import android.text.TextUtils; 50 import android.transition.TransitionManager; 51 import android.util.ArrayMap; 52 import android.util.AttributeSet; 53 import android.util.Log; 54 import android.util.Pair; 55 import android.util.TypedValue; 56 import android.util.Xml; 57 import android.view.Menu; 58 import android.view.MenuInflater; 59 import android.view.MenuItem; 60 import android.view.View; 61 import android.view.View.OnClickListener; 62 import android.view.ViewGroup; 63 import android.widget.Button; 64 import android.widget.SearchView; 65 66 import com.android.internal.logging.MetricsLogger; 67 import com.android.internal.util.ArrayUtils; 68 import com.android.internal.util.XmlUtils; 69 import com.android.settings.accessibility.AccessibilitySettings; 70 import com.android.settings.accessibility.CaptionPropertiesFragment; 71 import com.android.settings.accounts.AccountSettings; 72 import com.android.settings.accounts.AccountSyncSettings; 73 import com.android.settings.applications.DrawOverlayDetails; 74 import com.android.settings.applications.InstalledAppDetails; 75 import com.android.settings.applications.ManageApplications; 76 import com.android.settings.applications.ManageAssist; 77 import com.android.settings.applications.ProcessStatsSummary; 78 import com.android.settings.applications.ProcessStatsUi; 79 import com.android.settings.applications.UsageAccessDetails; 80 import com.android.settings.applications.WriteSettingsDetails; 81 import com.android.settings.bluetooth.BluetoothSettings; 82 import com.android.settings.dashboard.DashboardCategory; 83 import com.android.settings.dashboard.DashboardSummary; 84 import com.android.settings.dashboard.DashboardTile; 85 import com.android.settings.dashboard.NoHomeDialogFragment; 86 import com.android.settings.dashboard.SearchResultsSummary; 87 import com.android.settings.deviceinfo.PrivateVolumeForget; 88 import com.android.settings.deviceinfo.PrivateVolumeSettings; 89 import com.android.settings.deviceinfo.PublicVolumeSettings; 90 import com.android.settings.deviceinfo.StorageSettings; 91 import com.android.settings.fuelgauge.BatterySaverSettings; 92 import com.android.settings.fuelgauge.PowerUsageDetail; 93 import com.android.settings.fuelgauge.PowerUsageSummary; 94 import com.android.settings.inputmethod.InputMethodAndLanguageSettings; 95 import com.android.settings.inputmethod.KeyboardLayoutPickerFragment; 96 import com.android.settings.inputmethod.SpellCheckersSettings; 97 import com.android.settings.inputmethod.UserDictionaryList; 98 import com.android.settings.location.LocationSettings; 99 import com.android.settings.nfc.AndroidBeam; 100 import com.android.settings.nfc.PaymentSettings; 101 import com.android.settings.notification.AppNotificationSettings; 102 import com.android.settings.notification.NotificationAccessSettings; 103 import com.android.settings.notification.NotificationSettings; 104 import com.android.settings.notification.NotificationStation; 105 import com.android.settings.notification.OtherSoundSettings; 106 import com.android.settings.notification.ZenAccessSettings; 107 import com.android.settings.notification.ZenModeAutomationSettings; 108 import com.android.settings.notification.ZenModeEventRuleSettings; 109 import com.android.settings.notification.ZenModeExternalRuleSettings; 110 import com.android.settings.notification.ZenModePrioritySettings; 111 import com.android.settings.notification.ZenModeSettings; 112 import com.android.settings.notification.ZenModeScheduleRuleSettings; 113 import com.android.settings.print.PrintJobSettingsFragment; 114 import com.android.settings.print.PrintSettingsFragment; 115 import com.android.settings.search.DynamicIndexableContentMonitor; 116 import com.android.settings.search.Index; 117 import com.android.settings.sim.SimSettings; 118 import com.android.settings.tts.TextToSpeechSettings; 119 import com.android.settings.users.UserSettings; 120 import com.android.settings.vpn2.VpnSettings; 121 import com.android.settings.wfd.WifiDisplaySettings; 122 import com.android.settings.widget.SwitchBar; 123 import com.android.settings.wifi.AdvancedWifiSettings; 124 import com.android.settings.wifi.SavedAccessPointsWifiSettings; 125 import com.android.settings.wifi.WifiSettings; 126 import com.android.settings.wifi.p2p.WifiP2pSettings; 127 128 import org.xmlpull.v1.XmlPullParser; 129 import org.xmlpull.v1.XmlPullParserException; 130 131 import java.io.IOException; 132 import java.util.ArrayList; 133 import java.util.List; 134 import java.util.Map; 135 import java.util.Set; 136 137 public class SettingsActivity extends Activity 138 implements PreferenceManager.OnPreferenceTreeClickListener, 139 PreferenceFragment.OnPreferenceStartFragmentCallback, 140 ButtonBarHandler, FragmentManager.OnBackStackChangedListener, 141 SearchView.OnQueryTextListener, SearchView.OnCloseListener, 142 MenuItem.OnActionExpandListener { 143 144 private static final String LOG_TAG = "Settings"; 145 146 // Constants for state save/restore 147 private static final String SAVE_KEY_CATEGORIES = ":settings:categories"; 148 private static final String SAVE_KEY_SEARCH_MENU_EXPANDED = ":settings:search_menu_expanded"; 149 private static final String SAVE_KEY_SEARCH_QUERY = ":settings:search_query"; 150 private static final String SAVE_KEY_SHOW_HOME_AS_UP = ":settings:show_home_as_up"; 151 private static final String SAVE_KEY_SHOW_SEARCH = ":settings:show_search"; 152 private static final String SAVE_KEY_HOME_ACTIVITIES_COUNT = ":settings:home_activities_count"; 153 154 /** 155 * When starting this activity, the invoking Intent can contain this extra 156 * string to specify which fragment should be initially displayed. 157 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity 158 * will call isValidFragment() to confirm that the fragment class name is valid for this 159 * activity. 160 */ 161 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment"; 162 163 /** 164 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 165 * this extra can also be specified to supply a Bundle of arguments to pass 166 * to that fragment when it is instantiated during the initial creation 167 * of the activity. 168 */ 169 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"; 170 171 /** 172 * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS} 173 */ 174 public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 175 176 public static final String BACK_STACK_PREFS = ":settings:prefs"; 177 178 // extras that allow any preference activity to be launched as part of a wizard 179 180 // show Back and Next buttons? takes boolean parameter 181 // Back will then return RESULT_CANCELED and Next RESULT_OK 182 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar"; 183 184 // add a Skip button? 185 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip"; 186 187 // specify custom text for the Back or Next buttons, or cause a button to not appear 188 // at all by setting it to null 189 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text"; 190 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text"; 191 192 /** 193 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 194 * those extra can also be specify to supply the title or title res id to be shown for 195 * that fragment. 196 */ 197 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title"; 198 /** 199 * The package name used to resolve the title resource id. 200 */ 201 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME = 202 ":settings:show_fragment_title_res_package_name"; 203 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RESID = 204 ":settings:show_fragment_title_resid"; 205 public static final String EXTRA_SHOW_FRAGMENT_AS_SHORTCUT = 206 ":settings:show_fragment_as_shortcut"; 207 208 public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING = 209 ":settings:show_fragment_as_subsetting"; 210 211 private static final String META_DATA_KEY_FRAGMENT_CLASS = 212 "com.android.settings.FRAGMENT_CLASS"; 213 214 private static final String EXTRA_UI_OPTIONS = "settings:ui_options"; 215 216 private static final String EMPTY_QUERY = ""; 217 218 /** 219 * Settings will search for system activities of this action and add them as a top level 220 * settings tile using the following parameters. 221 * 222 * <p>A category must be specified in the meta-data for the activity named 223 * {@link #EXTRA_CATEGORY_KEY} 224 * 225 * <p>The title may be defined by meta-data named {@link Utils#META_DATA_PREFERENCE_TITLE} 226 * otherwise the label for the activity will be used. 227 * 228 * <p>The icon may be defined by meta-data named {@link Utils#META_DATA_PREFERENCE_ICON} 229 * otherwise the icon for the activity will be used. 230 * 231 * <p>A summary my be defined by meta-data named {@link Utils#META_DATA_PREFERENCE_SUMMARY} 232 */ 233 private static final String EXTRA_SETTINGS_ACTION = 234 "com.android.settings.action.EXTRA_SETTINGS"; 235 236 /** 237 * The key used to get the category from metadata of activities of action 238 * {@link #EXTRA_SETTINGS_ACTION} 239 * The value must be one of: 240 * <li>com.android.settings.category.wireless</li> 241 * <li>com.android.settings.category.device</li> 242 * <li>com.android.settings.category.personal</li> 243 * <li>com.android.settings.category.system</li> 244 */ 245 private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category"; 246 247 private static boolean sShowNoHomeNotice = false; 248 249 private String mFragmentClass; 250 251 private CharSequence mInitialTitle; 252 private int mInitialTitleResId; 253 254 // Show only these settings for restricted users 255 private int[] SETTINGS_FOR_RESTRICTED = { 256 R.id.wireless_section, 257 R.id.wifi_settings, 258 R.id.bluetooth_settings, 259 R.id.data_usage_settings, 260 R.id.sim_settings, 261 R.id.wireless_settings, 262 R.id.device_section, 263 R.id.notification_settings, 264 R.id.display_settings, 265 R.id.storage_settings, 266 R.id.application_settings, 267 R.id.battery_settings, 268 R.id.personal_section, 269 R.id.location_settings, 270 R.id.security_settings, 271 R.id.language_settings, 272 R.id.user_settings, 273 R.id.account_settings, 274 R.id.system_section, 275 R.id.date_time_settings, 276 R.id.about_settings, 277 R.id.accessibility_settings, 278 R.id.print_settings, 279 R.id.nfc_payment_settings, 280 R.id.home_settings, 281 R.id.dashboard 282 }; 283 284 private static final String[] ENTRY_FRAGMENTS = { 285 WirelessSettings.class.getName(), 286 WifiSettings.class.getName(), 287 AdvancedWifiSettings.class.getName(), 288 SavedAccessPointsWifiSettings.class.getName(), 289 BluetoothSettings.class.getName(), 290 SimSettings.class.getName(), 291 TetherSettings.class.getName(), 292 WifiP2pSettings.class.getName(), 293 VpnSettings.class.getName(), 294 DateTimeSettings.class.getName(), 295 LocalePicker.class.getName(), 296 InputMethodAndLanguageSettings.class.getName(), 297 SpellCheckersSettings.class.getName(), 298 UserDictionaryList.class.getName(), 299 UserDictionarySettings.class.getName(), 300 HomeSettings.class.getName(), 301 DisplaySettings.class.getName(), 302 DeviceInfoSettings.class.getName(), 303 ManageApplications.class.getName(), 304 ManageAssist.class.getName(), 305 ProcessStatsUi.class.getName(), 306 NotificationStation.class.getName(), 307 LocationSettings.class.getName(), 308 SecuritySettings.class.getName(), 309 UsageAccessDetails.class.getName(), 310 PrivacySettings.class.getName(), 311 DeviceAdminSettings.class.getName(), 312 AccessibilitySettings.class.getName(), 313 CaptionPropertiesFragment.class.getName(), 314 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(), 315 TextToSpeechSettings.class.getName(), 316 StorageSettings.class.getName(), 317 PrivateVolumeForget.class.getName(), 318 PrivateVolumeSettings.class.getName(), 319 PublicVolumeSettings.class.getName(), 320 DevelopmentSettings.class.getName(), 321 AndroidBeam.class.getName(), 322 WifiDisplaySettings.class.getName(), 323 PowerUsageSummary.class.getName(), 324 AccountSyncSettings.class.getName(), 325 AccountSettings.class.getName(), 326 CryptKeeperSettings.class.getName(), 327 DataUsageSummary.class.getName(), 328 DreamSettings.class.getName(), 329 UserSettings.class.getName(), 330 NotificationAccessSettings.class.getName(), 331 ZenAccessSettings.class.getName(), 332 PrintSettingsFragment.class.getName(), 333 PrintJobSettingsFragment.class.getName(), 334 TrustedCredentialsSettings.class.getName(), 335 PaymentSettings.class.getName(), 336 KeyboardLayoutPickerFragment.class.getName(), 337 ZenModeSettings.class.getName(), 338 NotificationSettings.class.getName(), 339 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(), 340 ChooseLockPattern.ChooseLockPatternFragment.class.getName(), 341 InstalledAppDetails.class.getName(), 342 BatterySaverSettings.class.getName(), 343 AppNotificationSettings.class.getName(), 344 OtherSoundSettings.class.getName(), 345 ApnSettings.class.getName(), 346 WifiCallingSettings.class.getName(), 347 ZenModePrioritySettings.class.getName(), 348 ZenModeAutomationSettings.class.getName(), 349 ZenModeScheduleRuleSettings.class.getName(), 350 ZenModeEventRuleSettings.class.getName(), 351 ZenModeExternalRuleSettings.class.getName(), 352 ProcessStatsUi.class.getName(), 353 PowerUsageDetail.class.getName(), 354 ProcessStatsSummary.class.getName(), 355 DrawOverlayDetails.class.getName(), 356 WriteSettingsDetails.class.getName(), 357 }; 358 359 360 private static final String[] LIKE_SHORTCUT_INTENT_ACTION_ARRAY = { 361 "android.settings.APPLICATION_DETAILS_SETTINGS" 362 }; 363 364 private SharedPreferences mDevelopmentPreferences; 365 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener; 366 367 private boolean mBatteryPresent = true; 368 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() { 369 @Override 370 public void onReceive(Context context, Intent intent) { 371 String action = intent.getAction(); 372 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { 373 boolean batteryPresent = Utils.isBatteryPresent(intent); 374 375 if (mBatteryPresent != batteryPresent) { 376 mBatteryPresent = batteryPresent; 377 invalidateCategories(true); 378 } 379 } 380 } 381 }; 382 383 private final DynamicIndexableContentMonitor mDynamicIndexableContentMonitor = 384 new DynamicIndexableContentMonitor(); 385 386 private ActionBar mActionBar; 387 private SwitchBar mSwitchBar; 388 389 private Button mNextButton; 390 391 private boolean mDisplayHomeAsUpEnabled; 392 private boolean mDisplaySearch; 393 394 private boolean mIsShowingDashboard; 395 private boolean mIsShortcut; 396 397 private ViewGroup mContent; 398 399 private SearchView mSearchView; 400 private MenuItem mSearchMenuItem; 401 private boolean mSearchMenuItemExpanded = false; 402 private SearchResultsSummary mSearchResultsFragment; 403 private String mSearchQuery; 404 405 // Categories 406 private ArrayList<DashboardCategory> mCategories = new ArrayList<DashboardCategory>(); 407 408 private static final String MSG_DATA_FORCE_REFRESH = "msg_data_force_refresh"; 409 private static final int MSG_BUILD_CATEGORIES = 1; 410 private Handler mHandler = new Handler() { 411 @Override 412 public void handleMessage(Message msg) { 413 switch (msg.what) { 414 case MSG_BUILD_CATEGORIES: { 415 final boolean forceRefresh = msg.getData().getBoolean(MSG_DATA_FORCE_REFRESH); 416 if (forceRefresh) { 417 buildDashboardCategories(mCategories); 418 } 419 } break; 420 } 421 } 422 }; 423 424 private boolean mNeedToRevertToInitialFragment = false; 425 private int mHomeActivitiesCount = 1; 426 427 private Intent mResultIntentData; 428 429 public SwitchBar getSwitchBar() { 430 return mSwitchBar; 431 } 432 433 public List<DashboardCategory> getDashboardCategories(boolean forceRefresh) { 434 if (forceRefresh || mCategories.size() == 0) { 435 buildDashboardCategories(mCategories); 436 } 437 return mCategories; 438 } 439 440 @Override 441 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) { 442 // Override the fragment title for Wallpaper settings 443 int titleRes = pref.getTitleRes(); 444 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) { 445 titleRes = R.string.wallpaper_settings_fragment_title; 446 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName()) 447 && UserHandle.myUserId() != UserHandle.USER_OWNER) { 448 if (UserManager.get(this).isLinkedUser()) { 449 titleRes = R.string.profile_info_settings_title; 450 } else { 451 titleRes = R.string.user_info_settings_title; 452 } 453 } 454 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(), 455 null, 0); 456 return true; 457 } 458 459 @Override 460 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 461 return false; 462 } 463 464 private void invalidateCategories(boolean forceRefresh) { 465 if (!mHandler.hasMessages(MSG_BUILD_CATEGORIES)) { 466 Message msg = new Message(); 467 msg.what = MSG_BUILD_CATEGORIES; 468 msg.getData().putBoolean(MSG_DATA_FORCE_REFRESH, forceRefresh); 469 } 470 } 471 472 @Override 473 public void onConfigurationChanged(Configuration newConfig) { 474 super.onConfigurationChanged(newConfig); 475 Index.getInstance(this).update(); 476 } 477 478 @Override 479 protected void onStart() { 480 super.onStart(); 481 482 if (mNeedToRevertToInitialFragment) { 483 revertToInitialFragment(); 484 } 485 } 486 487 @Override 488 public boolean onCreateOptionsMenu(Menu menu) { 489 if (!mDisplaySearch) { 490 return false; 491 } 492 493 MenuInflater inflater = getMenuInflater(); 494 inflater.inflate(R.menu.options_menu, menu); 495 496 // Cache the search query (can be overriden by the OnQueryTextListener) 497 final String query = mSearchQuery; 498 499 mSearchMenuItem = menu.findItem(R.id.search); 500 mSearchView = (SearchView) mSearchMenuItem.getActionView(); 501 502 if (mSearchMenuItem == null || mSearchView == null) { 503 return false; 504 } 505 506 if (mSearchResultsFragment != null) { 507 mSearchResultsFragment.setSearchView(mSearchView); 508 } 509 510 mSearchMenuItem.setOnActionExpandListener(this); 511 mSearchView.setOnQueryTextListener(this); 512 mSearchView.setOnCloseListener(this); 513 514 if (mSearchMenuItemExpanded) { 515 mSearchMenuItem.expandActionView(); 516 } 517 mSearchView.setQuery(query, true /* submit */); 518 519 return true; 520 } 521 522 private static boolean isShortCutIntent(final Intent intent) { 523 Set<String> categories = intent.getCategories(); 524 return (categories != null) && categories.contains("com.android.settings.SHORTCUT"); 525 } 526 527 private static boolean isLikeShortCutIntent(final Intent intent) { 528 String action = intent.getAction(); 529 if (action == null) { 530 return false; 531 } 532 for (int i = 0; i < LIKE_SHORTCUT_INTENT_ACTION_ARRAY.length; i++) { 533 if (LIKE_SHORTCUT_INTENT_ACTION_ARRAY[i].equals(action)) return true; 534 } 535 return false; 536 } 537 538 @Override 539 protected void onCreate(Bundle savedState) { 540 super.onCreate(savedState); 541 542 // Should happen before any call to getIntent() 543 getMetaData(); 544 545 final Intent intent = getIntent(); 546 if (intent.hasExtra(EXTRA_UI_OPTIONS)) { 547 getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0)); 548 } 549 550 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE, 551 Context.MODE_PRIVATE); 552 553 // Getting Intent properties can only be done after the super.onCreate(...) 554 final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); 555 556 mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) || 557 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false); 558 559 final ComponentName cn = intent.getComponent(); 560 final String className = cn.getClassName(); 561 562 mIsShowingDashboard = className.equals(Settings.class.getName()); 563 564 // This is a "Sub Settings" when: 565 // - this is a real SubSettings 566 // - or :settings:show_fragment_as_subsetting is passed to the Intent 567 final boolean isSubSettings = this instanceof SubSettings || 568 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false); 569 570 // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content insets 571 if (isSubSettings) { 572 // Check also that we are not a Theme Dialog as we don't want to override them 573 final int themeResId = getThemeResId(); 574 if (themeResId != R.style.Theme_DialogWhenLarge && 575 themeResId != R.style.Theme_SubSettingsDialogWhenLarge) { 576 setTheme(R.style.Theme_SubSettings); 577 } 578 } 579 580 setContentView(mIsShowingDashboard ? 581 R.layout.settings_main_dashboard : R.layout.settings_main_prefs); 582 583 mContent = (ViewGroup) findViewById(R.id.main_content); 584 585 getFragmentManager().addOnBackStackChangedListener(this); 586 587 if (mIsShowingDashboard) { 588 // Run the Index update only if we have some space 589 if (!Utils.isLowStorage(this)) { 590 Index.getInstance(getApplicationContext()).update(); 591 } else { 592 Log.w(LOG_TAG, "Cannot update the Indexer as we are running low on storage space!"); 593 } 594 } 595 596 if (savedState != null) { 597 // We are restarting from a previous saved state; used that to initialize, instead 598 // of starting fresh. 599 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED); 600 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY); 601 602 setTitleFromIntent(intent); 603 604 ArrayList<DashboardCategory> categories = 605 savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES); 606 if (categories != null) { 607 mCategories.clear(); 608 mCategories.addAll(categories); 609 setTitleFromBackStack(); 610 } 611 612 mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP); 613 mDisplaySearch = savedState.getBoolean(SAVE_KEY_SHOW_SEARCH); 614 mHomeActivitiesCount = savedState.getInt(SAVE_KEY_HOME_ACTIVITIES_COUNT, 615 1 /* one home activity by default */); 616 } else { 617 if (!mIsShowingDashboard) { 618 mDisplaySearch = false; 619 // UP will be shown only if it is a sub settings 620 if (mIsShortcut) { 621 mDisplayHomeAsUpEnabled = isSubSettings; 622 } else if (isSubSettings) { 623 mDisplayHomeAsUpEnabled = true; 624 } else { 625 mDisplayHomeAsUpEnabled = false; 626 } 627 setTitleFromIntent(intent); 628 629 Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 630 switchToFragment(initialFragmentName, initialArguments, true, false, 631 mInitialTitleResId, mInitialTitle, false); 632 } else { 633 // No UP affordance if we are displaying the main Dashboard 634 mDisplayHomeAsUpEnabled = false; 635 // Show Search affordance 636 mDisplaySearch = true; 637 mInitialTitleResId = R.string.dashboard_title; 638 switchToFragment(DashboardSummary.class.getName(), null, false, false, 639 mInitialTitleResId, mInitialTitle, false); 640 } 641 } 642 643 mActionBar = getActionBar(); 644 if (mActionBar != null) { 645 mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled); 646 mActionBar.setHomeButtonEnabled(mDisplayHomeAsUpEnabled); 647 } 648 mSwitchBar = (SwitchBar) findViewById(R.id.switch_bar); 649 650 // see if we should show Back/Next buttons 651 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) { 652 653 View buttonBar = findViewById(R.id.button_bar); 654 if (buttonBar != null) { 655 buttonBar.setVisibility(View.VISIBLE); 656 657 Button backButton = (Button)findViewById(R.id.back_button); 658 backButton.setOnClickListener(new OnClickListener() { 659 public void onClick(View v) { 660 setResult(RESULT_CANCELED, getResultIntentData()); 661 finish(); 662 } 663 }); 664 Button skipButton = (Button)findViewById(R.id.skip_button); 665 skipButton.setOnClickListener(new OnClickListener() { 666 public void onClick(View v) { 667 setResult(RESULT_OK, getResultIntentData()); 668 finish(); 669 } 670 }); 671 mNextButton = (Button)findViewById(R.id.next_button); 672 mNextButton.setOnClickListener(new OnClickListener() { 673 public void onClick(View v) { 674 setResult(RESULT_OK, getResultIntentData()); 675 finish(); 676 } 677 }); 678 679 // set our various button parameters 680 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) { 681 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT); 682 if (TextUtils.isEmpty(buttonText)) { 683 mNextButton.setVisibility(View.GONE); 684 } 685 else { 686 mNextButton.setText(buttonText); 687 } 688 } 689 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) { 690 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT); 691 if (TextUtils.isEmpty(buttonText)) { 692 backButton.setVisibility(View.GONE); 693 } 694 else { 695 backButton.setText(buttonText); 696 } 697 } 698 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) { 699 skipButton.setVisibility(View.VISIBLE); 700 } 701 } 702 } 703 704 mHomeActivitiesCount = getHomeActivitiesCount(); 705 } 706 707 private int getHomeActivitiesCount() { 708 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>(); 709 getPackageManager().getHomeActivities(homeApps); 710 return homeApps.size(); 711 } 712 713 private void setTitleFromIntent(Intent intent) { 714 final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1); 715 if (initialTitleResId > 0) { 716 mInitialTitle = null; 717 mInitialTitleResId = initialTitleResId; 718 719 final String initialTitleResPackageName = intent.getStringExtra( 720 EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME); 721 if (initialTitleResPackageName != null) { 722 try { 723 Context authContext = createPackageContextAsUser(initialTitleResPackageName, 724 0 /* flags */, new UserHandle(UserHandle.myUserId())); 725 mInitialTitle = authContext.getResources().getText(mInitialTitleResId); 726 setTitle(mInitialTitle); 727 mInitialTitleResId = -1; 728 return; 729 } catch (NameNotFoundException e) { 730 Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName); 731 } 732 } else { 733 setTitle(mInitialTitleResId); 734 } 735 } else { 736 mInitialTitleResId = -1; 737 final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE); 738 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle(); 739 setTitle(mInitialTitle); 740 } 741 } 742 743 @Override 744 public void onBackStackChanged() { 745 setTitleFromBackStack(); 746 } 747 748 private int setTitleFromBackStack() { 749 final int count = getFragmentManager().getBackStackEntryCount(); 750 751 if (count == 0) { 752 if (mInitialTitleResId > 0) { 753 setTitle(mInitialTitleResId); 754 } else { 755 setTitle(mInitialTitle); 756 } 757 return 0; 758 } 759 760 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1); 761 setTitleFromBackStackEntry(bse); 762 763 return count; 764 } 765 766 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) { 767 final CharSequence title; 768 final int titleRes = bse.getBreadCrumbTitleRes(); 769 if (titleRes > 0) { 770 title = getText(titleRes); 771 } else { 772 title = bse.getBreadCrumbTitle(); 773 } 774 if (title != null) { 775 setTitle(title); 776 } 777 } 778 779 @Override 780 protected void onSaveInstanceState(Bundle outState) { 781 super.onSaveInstanceState(outState); 782 783 if (mCategories.size() > 0) { 784 outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories); 785 } 786 787 outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled); 788 outState.putBoolean(SAVE_KEY_SHOW_SEARCH, mDisplaySearch); 789 790 if (mDisplaySearch) { 791 // The option menus are created if the ActionBar is visible and they are also created 792 // asynchronously. If you launch Settings with an Intent action like 793 // android.intent.action.POWER_USAGE_SUMMARY and at the same time your device is locked 794 // thru a LockScreen, onCreateOptionsMenu() is not yet called and references to the search 795 // menu item and search view are null. 796 boolean isExpanded = (mSearchMenuItem != null) && mSearchMenuItem.isActionViewExpanded(); 797 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, isExpanded); 798 799 String query = (mSearchView != null) ? mSearchView.getQuery().toString() : EMPTY_QUERY; 800 outState.putString(SAVE_KEY_SEARCH_QUERY, query); 801 } 802 803 outState.putInt(SAVE_KEY_HOME_ACTIVITIES_COUNT, mHomeActivitiesCount); 804 } 805 806 @Override 807 public void onResume() { 808 super.onResume(); 809 if (mIsShowingDashboard) { 810 MetricsLogger.visible(this, MetricsLogger.MAIN_SETTINGS); 811 } 812 813 final int newHomeActivityCount = getHomeActivitiesCount(); 814 if (newHomeActivityCount != mHomeActivitiesCount) { 815 mHomeActivitiesCount = newHomeActivityCount; 816 invalidateCategories(true); 817 } 818 819 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() { 820 @Override 821 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 822 invalidateCategories(true); 823 } 824 }; 825 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener( 826 mDevelopmentPreferencesListener); 827 828 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 829 830 mDynamicIndexableContentMonitor.register(this); 831 832 if(mDisplaySearch && !TextUtils.isEmpty(mSearchQuery)) { 833 onQueryTextSubmit(mSearchQuery); 834 } 835 } 836 837 @Override 838 public void onPause() { 839 super.onPause(); 840 if (mIsShowingDashboard) { 841 MetricsLogger.hidden(this, MetricsLogger.MAIN_SETTINGS); 842 } 843 unregisterReceiver(mBatteryInfoReceiver); 844 mDynamicIndexableContentMonitor.unregister(); 845 } 846 847 @Override 848 public void onDestroy() { 849 super.onDestroy(); 850 851 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener( 852 mDevelopmentPreferencesListener); 853 mDevelopmentPreferencesListener = null; 854 } 855 856 protected boolean isValidFragment(String fragmentName) { 857 // Almost all fragments are wrapped in this, 858 // except for a few that have their own activities. 859 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) { 860 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true; 861 } 862 return false; 863 } 864 865 @Override 866 public Intent getIntent() { 867 Intent superIntent = super.getIntent(); 868 String startingFragment = getStartingFragmentClass(superIntent); 869 // This is called from super.onCreate, isMultiPane() is not yet reliable 870 // Do not use onIsHidingHeaders either, which relies itself on this method 871 if (startingFragment != null) { 872 Intent modIntent = new Intent(superIntent); 873 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment); 874 Bundle args = superIntent.getExtras(); 875 if (args != null) { 876 args = new Bundle(args); 877 } else { 878 args = new Bundle(); 879 } 880 args.putParcelable("intent", superIntent); 881 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 882 return modIntent; 883 } 884 return superIntent; 885 } 886 887 /** 888 * Checks if the component name in the intent is different from the Settings class and 889 * returns the class name to load as a fragment. 890 */ 891 private String getStartingFragmentClass(Intent intent) { 892 if (mFragmentClass != null) return mFragmentClass; 893 894 String intentClass = intent.getComponent().getClassName(); 895 if (intentClass.equals(getClass().getName())) return null; 896 897 if ("com.android.settings.ManageApplications".equals(intentClass) 898 || "com.android.settings.RunningServices".equals(intentClass) 899 || "com.android.settings.applications.StorageUse".equals(intentClass)) { 900 // Old names of manage apps. 901 intentClass = com.android.settings.applications.ManageApplications.class.getName(); 902 } 903 904 return intentClass; 905 } 906 907 /** 908 * Start a new fragment containing a preference panel. If the preferences 909 * are being displayed in multi-pane mode, the given fragment class will 910 * be instantiated and placed in the appropriate pane. If running in 911 * single-pane mode, a new activity will be launched in which to show the 912 * fragment. 913 * 914 * @param fragmentClass Full name of the class implementing the fragment. 915 * @param args Any desired arguments to supply to the fragment. 916 * @param titleRes Optional resource identifier of the title of this 917 * fragment. 918 * @param titleText Optional text of the title of this fragment. 919 * @param resultTo Optional fragment that result data should be sent to. 920 * If non-null, resultTo.onActivityResult() will be called when this 921 * preference panel is done. The launched panel must use 922 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done. 923 * @param resultRequestCode If resultTo is non-null, this is the caller's 924 * request code to be received with the result. 925 */ 926 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes, 927 CharSequence titleText, Fragment resultTo, int resultRequestCode) { 928 String title = null; 929 if (titleRes < 0) { 930 if (titleText != null) { 931 title = titleText.toString(); 932 } else { 933 // There not much we can do in that case 934 title = ""; 935 } 936 } 937 Utils.startWithFragment(this, fragmentClass, args, resultTo, resultRequestCode, 938 titleRes, title, mIsShortcut); 939 } 940 941 /** 942 * Start a new fragment in a new activity containing a preference panel for a given user. If the 943 * preferences are being displayed in multi-pane mode, the given fragment class will be 944 * instantiated and placed in the appropriate pane. If running in single-pane mode, a new 945 * activity will be launched in which to show the fragment. 946 * 947 * @param fragmentClass Full name of the class implementing the fragment. 948 * @param args Any desired arguments to supply to the fragment. 949 * @param titleRes Optional resource identifier of the title of this fragment. 950 * @param titleText Optional text of the title of this fragment. 951 * @param userHandle The user for which the panel has to be started. 952 */ 953 public void startPreferencePanelAsUser(String fragmentClass, Bundle args, int titleRes, 954 CharSequence titleText, UserHandle userHandle) { 955 // This is a workaround. 956 // 957 // Calling startWithFragmentAsUser() without specifying FLAG_ACTIVITY_NEW_TASK to the intent 958 // starting the fragment could cause a native stack corruption. See b/17523189. However, 959 // adding that flag and start the preference panel with the same UserHandler will make it 960 // impossible to use back button to return to the previous screen. See b/20042570. 961 // 962 // We work around this issue by adding FLAG_ACTIVITY_NEW_TASK to the intent, while doing 963 // another check here to call startPreferencePanel() instead of startWithFragmentAsUser() 964 // when we're calling it as the same user. 965 if (userHandle.getIdentifier() == UserHandle.myUserId()) { 966 startPreferencePanel(fragmentClass, args, titleRes, titleText, null, 0); 967 } else { 968 String title = null; 969 if (titleRes < 0) { 970 if (titleText != null) { 971 title = titleText.toString(); 972 } else { 973 // There not much we can do in that case 974 title = ""; 975 } 976 } 977 Utils.startWithFragmentAsUser(this, fragmentClass, args, 978 titleRes, title, mIsShortcut, userHandle); 979 } 980 } 981 982 /** 983 * Called by a preference panel fragment to finish itself. 984 * 985 * @param caller The fragment that is asking to be finished. 986 * @param resultCode Optional result code to send back to the original 987 * launching fragment. 988 * @param resultData Optional result data to send back to the original 989 * launching fragment. 990 */ 991 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) { 992 setResult(resultCode, resultData); 993 finish(); 994 } 995 996 /** 997 * Start a new fragment. 998 * 999 * @param fragment The fragment to start 1000 * @param push If true, the current fragment will be pushed onto the back stack. If false, 1001 * the current fragment will be replaced. 1002 */ 1003 public void startPreferenceFragment(Fragment fragment, boolean push) { 1004 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1005 transaction.replace(R.id.main_content, fragment); 1006 if (push) { 1007 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 1008 transaction.addToBackStack(BACK_STACK_PREFS); 1009 } else { 1010 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 1011 } 1012 transaction.commitAllowingStateLoss(); 1013 } 1014 1015 /** 1016 * Switch to a specific Fragment with taking care of validation, Title and BackStack 1017 */ 1018 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate, 1019 boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) { 1020 if (validate && !isValidFragment(fragmentName)) { 1021 throw new IllegalArgumentException("Invalid fragment for this activity: " 1022 + fragmentName); 1023 } 1024 Fragment f = Fragment.instantiate(this, fragmentName, args); 1025 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1026 transaction.replace(R.id.main_content, f); 1027 if (withTransition) { 1028 TransitionManager.beginDelayedTransition(mContent); 1029 } 1030 if (addToBackStack) { 1031 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS); 1032 } 1033 if (titleResId > 0) { 1034 transaction.setBreadCrumbTitle(titleResId); 1035 } else if (title != null) { 1036 transaction.setBreadCrumbTitle(title); 1037 } 1038 transaction.commitAllowingStateLoss(); 1039 getFragmentManager().executePendingTransactions(); 1040 return f; 1041 } 1042 1043 /** 1044 * Called when the activity needs its list of categories/tiles built. 1045 * 1046 * @param categories The list in which to place the tiles categories. 1047 */ 1048 private void buildDashboardCategories(List<DashboardCategory> categories) { 1049 categories.clear(); 1050 loadCategoriesFromResource(R.xml.dashboard_categories, categories, this); 1051 updateTilesList(categories); 1052 } 1053 1054 /** 1055 * Parse the given XML file as a categories description, adding each 1056 * parsed categories and tiles into the target list. 1057 * 1058 * @param resid The XML resource to load and parse. 1059 * @param target The list in which the parsed categories and tiles should be placed. 1060 */ 1061 public static void loadCategoriesFromResource(int resid, List<DashboardCategory> target, 1062 Context context) { 1063 XmlResourceParser parser = null; 1064 try { 1065 parser = context.getResources().getXml(resid); 1066 AttributeSet attrs = Xml.asAttributeSet(parser); 1067 1068 int type; 1069 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 1070 && type != XmlPullParser.START_TAG) { 1071 // Parse next until start tag is found 1072 } 1073 1074 String nodeName = parser.getName(); 1075 if (!"dashboard-categories".equals(nodeName)) { 1076 throw new RuntimeException( 1077 "XML document must start with <preference-categories> tag; found" 1078 + nodeName + " at " + parser.getPositionDescription()); 1079 } 1080 1081 Bundle curBundle = null; 1082 1083 final int outerDepth = parser.getDepth(); 1084 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 1085 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 1086 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 1087 continue; 1088 } 1089 1090 nodeName = parser.getName(); 1091 if ("dashboard-category".equals(nodeName)) { 1092 DashboardCategory category = new DashboardCategory(); 1093 1094 TypedArray sa = context.obtainStyledAttributes( 1095 attrs, com.android.internal.R.styleable.PreferenceHeader); 1096 category.id = sa.getResourceId( 1097 com.android.internal.R.styleable.PreferenceHeader_id, 1098 (int)DashboardCategory.CAT_ID_UNDEFINED); 1099 1100 TypedValue tv = sa.peekValue( 1101 com.android.internal.R.styleable.PreferenceHeader_title); 1102 if (tv != null && tv.type == TypedValue.TYPE_STRING) { 1103 if (tv.resourceId != 0) { 1104 category.titleRes = tv.resourceId; 1105 } else { 1106 category.title = tv.string; 1107 } 1108 } 1109 sa.recycle(); 1110 sa = context.obtainStyledAttributes(attrs, 1111 com.android.internal.R.styleable.Preference); 1112 tv = sa.peekValue( 1113 com.android.internal.R.styleable.Preference_key); 1114 if (tv != null && tv.type == TypedValue.TYPE_STRING) { 1115 if (tv.resourceId != 0) { 1116 category.key = context.getString(tv.resourceId); 1117 } else { 1118 category.key = tv.string.toString(); 1119 } 1120 } 1121 sa.recycle(); 1122 1123 final int innerDepth = parser.getDepth(); 1124 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 1125 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { 1126 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 1127 continue; 1128 } 1129 1130 String innerNodeName = parser.getName(); 1131 if (innerNodeName.equals("dashboard-tile")) { 1132 DashboardTile tile = new DashboardTile(); 1133 1134 sa = context.obtainStyledAttributes( 1135 attrs, com.android.internal.R.styleable.PreferenceHeader); 1136 tile.id = sa.getResourceId( 1137 com.android.internal.R.styleable.PreferenceHeader_id, 1138 (int)TILE_ID_UNDEFINED); 1139 tv = sa.peekValue( 1140 com.android.internal.R.styleable.PreferenceHeader_title); 1141 if (tv != null && tv.type == TypedValue.TYPE_STRING) { 1142 if (tv.resourceId != 0) { 1143 tile.titleRes = tv.resourceId; 1144 } else { 1145 tile.title = tv.string; 1146 } 1147 } 1148 tv = sa.peekValue( 1149 com.android.internal.R.styleable.PreferenceHeader_summary); 1150 if (tv != null && tv.type == TypedValue.TYPE_STRING) { 1151 if (tv.resourceId != 0) { 1152 tile.summaryRes = tv.resourceId; 1153 } else { 1154 tile.summary = tv.string; 1155 } 1156 } 1157 tile.iconRes = sa.getResourceId( 1158 com.android.internal.R.styleable.PreferenceHeader_icon, 0); 1159 tile.fragment = sa.getString( 1160 com.android.internal.R.styleable.PreferenceHeader_fragment); 1161 sa.recycle(); 1162 1163 if (curBundle == null) { 1164 curBundle = new Bundle(); 1165 } 1166 1167 final int innerDepth2 = parser.getDepth(); 1168 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 1169 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth2)) { 1170 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 1171 continue; 1172 } 1173 1174 String innerNodeName2 = parser.getName(); 1175 if (innerNodeName2.equals("extra")) { 1176 context.getResources().parseBundleExtra("extra", attrs, 1177 curBundle); 1178 XmlUtils.skipCurrentTag(parser); 1179 1180 } else if (innerNodeName2.equals("intent")) { 1181 tile.intent = Intent.parseIntent(context.getResources(), parser, 1182 attrs); 1183 1184 } else { 1185 XmlUtils.skipCurrentTag(parser); 1186 } 1187 } 1188 1189 if (curBundle.size() > 0) { 1190 tile.fragmentArguments = curBundle; 1191 curBundle = null; 1192 } 1193 1194 // Show the SIM Cards setting if there are more than 2 SIMs installed. 1195 if(tile.id != R.id.sim_settings || Utils.showSimCardTile(context)){ 1196 category.addTile(tile); 1197 } 1198 1199 } else if (innerNodeName.equals("external-tiles")) { 1200 category.externalIndex = category.getTilesCount(); 1201 } else { 1202 XmlUtils.skipCurrentTag(parser); 1203 } 1204 } 1205 1206 target.add(category); 1207 } else { 1208 XmlUtils.skipCurrentTag(parser); 1209 } 1210 } 1211 1212 } catch (XmlPullParserException e) { 1213 throw new RuntimeException("Error parsing categories", e); 1214 } catch (IOException e) { 1215 throw new RuntimeException("Error parsing categories", e); 1216 } finally { 1217 if (parser != null) parser.close(); 1218 } 1219 } 1220 1221 private void updateTilesList(List<DashboardCategory> target) { 1222 final boolean showDev = mDevelopmentPreferences.getBoolean( 1223 DevelopmentSettings.PREF_SHOW, 1224 android.os.Build.TYPE.equals("eng")); 1225 1226 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE); 1227 1228 final int size = target.size(); 1229 for (int i = 0; i < size; i++) { 1230 1231 DashboardCategory category = target.get(i); 1232 1233 // Ids are integers, so downcasting is ok 1234 int id = (int) category.id; 1235 int n = category.getTilesCount() - 1; 1236 while (n >= 0) { 1237 1238 DashboardTile tile = category.getTile(n); 1239 boolean removeTile = false; 1240 id = (int) tile.id; 1241 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) { 1242 if (!Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, tile)) { 1243 removeTile = true; 1244 } 1245 } else if (id == R.id.wifi_settings) { 1246 // Remove WiFi Settings if WiFi service is not available. 1247 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) { 1248 removeTile = true; 1249 } 1250 } else if (id == R.id.bluetooth_settings) { 1251 // Remove Bluetooth Settings if Bluetooth service is not available. 1252 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) { 1253 removeTile = true; 1254 } 1255 } else if (id == R.id.data_usage_settings) { 1256 // Remove data usage when kernel module not enabled 1257 if (!Utils.isBandwidthControlEnabled()) { 1258 removeTile = true; 1259 } 1260 } else if (id == R.id.battery_settings) { 1261 // Remove battery settings when battery is not available. (e.g. TV) 1262 1263 if (!mBatteryPresent) { 1264 removeTile = true; 1265 } 1266 } else if (id == R.id.home_settings) { 1267 if (!updateHomeSettingTiles(tile)) { 1268 removeTile = true; 1269 } 1270 } else if (id == R.id.user_settings) { 1271 boolean hasMultipleUsers = 1272 ((UserManager) getSystemService(Context.USER_SERVICE)) 1273 .getUserCount() > 1; 1274 if (!UserHandle.MU_ENABLED 1275 || !UserManager.supportsMultipleUsers() 1276 || Utils.isMonkeyRunning()) { 1277 removeTile = true; 1278 } 1279 } else if (id == R.id.nfc_payment_settings) { 1280 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) { 1281 removeTile = true; 1282 } else { 1283 // Only show if NFC is on and we have the HCE feature 1284 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); 1285 if (adapter == null || !adapter.isEnabled() || 1286 !getPackageManager().hasSystemFeature( 1287 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) { 1288 removeTile = true; 1289 } 1290 } 1291 } else if (id == R.id.print_settings) { 1292 boolean hasPrintingSupport = getPackageManager().hasSystemFeature( 1293 PackageManager.FEATURE_PRINTING); 1294 if (!hasPrintingSupport) { 1295 removeTile = true; 1296 } 1297 } else if (id == R.id.development_settings) { 1298 if (!showDev || um.hasUserRestriction( 1299 UserManager.DISALLOW_DEBUGGING_FEATURES)) { 1300 removeTile = true; 1301 } 1302 } 1303 1304 if (UserHandle.MU_ENABLED && UserHandle.myUserId() != 0 1305 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) { 1306 removeTile = true; 1307 } 1308 1309 if (removeTile && n < category.getTilesCount()) { 1310 category.removeTile(n); 1311 } 1312 n--; 1313 } 1314 } 1315 addExternalTiles(target); 1316 } 1317 1318 private void addExternalTiles(List<DashboardCategory> target) { 1319 Map<Pair<String, String>, DashboardTile> addedCache = 1320 new ArrayMap<Pair<String, String>, DashboardTile>(); 1321 UserManager userManager = UserManager.get(this); 1322 for (UserHandle user : userManager.getUserProfiles()) { 1323 addExternalTiles(target, user, addedCache); 1324 } 1325 } 1326 1327 private void addExternalTiles(List<DashboardCategory> target, UserHandle user, 1328 Map<Pair<String, String>, DashboardTile> addedCache) { 1329 PackageManager pm = getPackageManager(); 1330 Intent intent = new Intent(EXTRA_SETTINGS_ACTION); 1331 List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent, 1332 PackageManager.GET_META_DATA, user.getIdentifier()); 1333 for (ResolveInfo resolved : results) { 1334 if (!resolved.system) { 1335 // Do not allow any app to add to settings, only system ones. 1336 continue; 1337 } 1338 ActivityInfo activityInfo = resolved.activityInfo; 1339 Bundle metaData = activityInfo.metaData; 1340 if ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY)) { 1341 Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for action " 1342 + EXTRA_SETTINGS_ACTION + " missing metadata " + 1343 (metaData == null ? "" : EXTRA_CATEGORY_KEY)); 1344 continue; 1345 } 1346 String categoryKey = metaData.getString(EXTRA_CATEGORY_KEY); 1347 DashboardCategory category = getCategory(target, categoryKey); 1348 if (category == null) { 1349 Log.w(LOG_TAG, "Activity " + resolved.activityInfo.name + " has unknown " 1350 + "category key " + categoryKey); 1351 continue; 1352 } 1353 Pair<String, String> key = new Pair<String, String>(activityInfo.packageName, 1354 activityInfo.name); 1355 DashboardTile tile = addedCache.get(key); 1356 if (tile == null) { 1357 tile = new DashboardTile(); 1358 tile.intent = new Intent().setClassName( 1359 activityInfo.packageName, activityInfo.name); 1360 Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, tile); 1361 1362 if (category.externalIndex == -1) { 1363 // If no location for external tiles has been specified for this category, 1364 // then just put them at the end. 1365 category.addTile(tile); 1366 } else { 1367 category.addTile(category.externalIndex, tile); 1368 } 1369 addedCache.put(key, tile); 1370 } 1371 tile.userHandle.add(user); 1372 } 1373 } 1374 1375 private DashboardCategory getCategory(List<DashboardCategory> target, String categoryKey) { 1376 for (DashboardCategory category : target) { 1377 if (categoryKey.equals(category.key)) { 1378 return category; 1379 } 1380 } 1381 return null; 1382 } 1383 1384 private boolean updateHomeSettingTiles(DashboardTile tile) { 1385 // Once we decide to show Home settings, keep showing it forever 1386 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE); 1387 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) { 1388 return true; 1389 } 1390 1391 try { 1392 mHomeActivitiesCount = getHomeActivitiesCount(); 1393 if (mHomeActivitiesCount < 2) { 1394 // When there's only one available home app, omit this settings 1395 // category entirely at the top level UI. If the user just 1396 // uninstalled the penultimate home app candidiate, we also 1397 // now tell them about why they aren't seeing 'Home' in the list. 1398 if (sShowNoHomeNotice) { 1399 sShowNoHomeNotice = false; 1400 NoHomeDialogFragment.show(this); 1401 } 1402 return false; 1403 } else { 1404 // Okay, we're allowing the Home settings category. Tell it, when 1405 // invoked via this front door, that we'll need to be told about the 1406 // case when the user uninstalls all but one home app. 1407 if (tile.fragmentArguments == null) { 1408 tile.fragmentArguments = new Bundle(); 1409 } 1410 tile.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true); 1411 } 1412 } catch (Exception e) { 1413 // Can't look up the home activity; bail on configuring the icon 1414 Log.w(LOG_TAG, "Problem looking up home activity!", e); 1415 } 1416 1417 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply(); 1418 return true; 1419 } 1420 1421 private void getMetaData() { 1422 try { 1423 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(), 1424 PackageManager.GET_META_DATA); 1425 if (ai == null || ai.metaData == null) return; 1426 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS); 1427 } catch (NameNotFoundException nnfe) { 1428 // No recovery 1429 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString()); 1430 } 1431 } 1432 1433 // give subclasses access to the Next button 1434 public boolean hasNextButton() { 1435 return mNextButton != null; 1436 } 1437 1438 public Button getNextButton() { 1439 return mNextButton; 1440 } 1441 1442 @Override 1443 public boolean shouldUpRecreateTask(Intent targetIntent) { 1444 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class)); 1445 } 1446 1447 public static void requestHomeNotice() { 1448 sShowNoHomeNotice = true; 1449 } 1450 1451 @Override 1452 public boolean onQueryTextSubmit(String query) { 1453 switchToSearchResultsFragmentIfNeeded(); 1454 mSearchQuery = query; 1455 return mSearchResultsFragment.onQueryTextSubmit(query); 1456 } 1457 1458 @Override 1459 public boolean onQueryTextChange(String newText) { 1460 mSearchQuery = newText; 1461 if (mSearchResultsFragment == null) { 1462 return false; 1463 } 1464 return mSearchResultsFragment.onQueryTextChange(newText); 1465 } 1466 1467 @Override 1468 public boolean onClose() { 1469 return false; 1470 } 1471 1472 @Override 1473 public boolean onMenuItemActionExpand(MenuItem item) { 1474 if (item.getItemId() == mSearchMenuItem.getItemId()) { 1475 switchToSearchResultsFragmentIfNeeded(); 1476 } 1477 return true; 1478 } 1479 1480 @Override 1481 public boolean onMenuItemActionCollapse(MenuItem item) { 1482 if (item.getItemId() == mSearchMenuItem.getItemId()) { 1483 if (mSearchMenuItemExpanded) { 1484 revertToInitialFragment(); 1485 } 1486 } 1487 return true; 1488 } 1489 1490 private void switchToSearchResultsFragmentIfNeeded() { 1491 if (mSearchResultsFragment != null) { 1492 return; 1493 } 1494 Fragment current = getFragmentManager().findFragmentById(R.id.main_content); 1495 if (current != null && current instanceof SearchResultsSummary) { 1496 mSearchResultsFragment = (SearchResultsSummary) current; 1497 } else { 1498 mSearchResultsFragment = (SearchResultsSummary) switchToFragment( 1499 SearchResultsSummary.class.getName(), null, false, true, 1500 R.string.search_results_title, null, true); 1501 } 1502 mSearchResultsFragment.setSearchView(mSearchView); 1503 mSearchMenuItemExpanded = true; 1504 } 1505 1506 public void needToRevertToInitialFragment() { 1507 mNeedToRevertToInitialFragment = true; 1508 } 1509 1510 private void revertToInitialFragment() { 1511 mNeedToRevertToInitialFragment = false; 1512 mSearchResultsFragment = null; 1513 mSearchMenuItemExpanded = false; 1514 getFragmentManager().popBackStackImmediate(SettingsActivity.BACK_STACK_PREFS, 1515 FragmentManager.POP_BACK_STACK_INCLUSIVE); 1516 if (mSearchMenuItem != null) { 1517 mSearchMenuItem.collapseActionView(); 1518 } 1519 } 1520 1521 public Intent getResultIntentData() { 1522 return mResultIntentData; 1523 } 1524 1525 public void setResultIntentData(Intent resultIntentData) { 1526 mResultIntentData = resultIntentData; 1527 } 1528 } 1529