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 android.app.ActionBar; 20 import android.app.ActivityManager; 21 import android.app.Fragment; 22 import android.app.FragmentManager; 23 import android.app.FragmentTransaction; 24 import android.content.BroadcastReceiver; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.content.SharedPreferences; 30 import android.content.pm.ActivityInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.PackageManager.NameNotFoundException; 33 import android.graphics.Bitmap; 34 import android.graphics.Canvas; 35 import android.graphics.drawable.Drawable; 36 import android.nfc.NfcAdapter; 37 import android.os.AsyncTask; 38 import android.os.Bundle; 39 import android.os.UserHandle; 40 import android.os.UserManager; 41 import android.support.annotation.VisibleForTesting; 42 import android.support.v14.preference.PreferenceFragment; 43 import android.support.v7.preference.Preference; 44 import android.support.v7.preference.PreferenceManager; 45 import android.text.TextUtils; 46 import android.transition.TransitionManager; 47 import android.util.Log; 48 import android.view.Menu; 49 import android.view.View; 50 import android.view.View.OnClickListener; 51 import android.view.ViewGroup; 52 import android.widget.Button; 53 54 import com.android.internal.util.ArrayUtils; 55 import com.android.settings.Settings.WifiSettingsActivity; 56 import com.android.settings.backup.BackupSettingsActivity; 57 import com.android.settings.core.gateway.SettingsGateway; 58 import com.android.settings.core.instrumentation.MetricsFeatureProvider; 59 import com.android.settings.core.instrumentation.SharedPreferencesLogger; 60 import com.android.settings.dashboard.DashboardFeatureProvider; 61 import com.android.settings.dashboard.DashboardSummary; 62 import com.android.settings.development.DevelopmentSettings; 63 import com.android.settings.overlay.FeatureFactory; 64 import com.android.settings.search.DynamicIndexableContentMonitor; 65 import com.android.settings.search2.SearchFeatureProvider; 66 import com.android.settings.wfd.WifiDisplaySettings; 67 import com.android.settings.widget.SwitchBar; 68 import com.android.settingslib.drawer.DashboardCategory; 69 import com.android.settingslib.drawer.SettingsDrawerActivity; 70 71 import java.util.ArrayList; 72 import java.util.List; 73 import java.util.Set; 74 75 public class SettingsActivity extends SettingsDrawerActivity 76 implements PreferenceManager.OnPreferenceTreeClickListener, 77 PreferenceFragment.OnPreferenceStartFragmentCallback, 78 ButtonBarHandler, FragmentManager.OnBackStackChangedListener { 79 80 private static final String LOG_TAG = "Settings"; 81 82 public static final int LOADER_ID_INDEXABLE_CONTENT_MONITOR = 1; 83 84 // Constants for state save/restore 85 private static final String SAVE_KEY_CATEGORIES = ":settings:categories"; 86 @VisibleForTesting 87 static final String SAVE_KEY_SHOW_HOME_AS_UP = ":settings:show_home_as_up"; 88 @VisibleForTesting 89 static final String SAVE_KEY_SHOW_SEARCH = ":settings:show_search"; 90 91 /** 92 * When starting this activity, the invoking Intent can contain this extra 93 * string to specify which fragment should be initially displayed. 94 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity 95 * will call isValidFragment() to confirm that the fragment class name is valid for this 96 * activity. 97 */ 98 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment"; 99 100 /** 101 * The metrics category constant for logging source when a setting fragment is opened. 102 */ 103 public static final String EXTRA_SOURCE_METRICS_CATEGORY = ":settings:source_metrics"; 104 105 /** 106 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 107 * this extra can also be specified to supply a Bundle of arguments to pass 108 * to that fragment when it is instantiated during the initial creation 109 * of the activity. 110 */ 111 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"; 112 113 /** 114 * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS} 115 */ 116 public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 117 118 public static final String BACK_STACK_PREFS = ":settings:prefs"; 119 120 // extras that allow any preference activity to be launched as part of a wizard 121 122 // show Back and Next buttons? takes boolean parameter 123 // Back will then return RESULT_CANCELED and Next RESULT_OK 124 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar"; 125 126 // add a Skip button? 127 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip"; 128 129 // specify custom text for the Back or Next buttons, or cause a button to not appear 130 // at all by setting it to null 131 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text"; 132 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text"; 133 134 /** 135 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 136 * those extra can also be specify to supply the title or title res id to be shown for 137 * that fragment. 138 */ 139 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title"; 140 /** 141 * The package name used to resolve the title resource id. 142 */ 143 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME = 144 ":settings:show_fragment_title_res_package_name"; 145 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RESID = 146 ":settings:show_fragment_title_resid"; 147 public static final String EXTRA_SHOW_FRAGMENT_AS_SHORTCUT = 148 ":settings:show_fragment_as_shortcut"; 149 150 public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING = 151 ":settings:show_fragment_as_subsetting"; 152 153 @Deprecated 154 public static final String EXTRA_HIDE_DRAWER = ":settings:hide_drawer"; 155 156 public static final String META_DATA_KEY_FRAGMENT_CLASS = 157 "com.android.settings.FRAGMENT_CLASS"; 158 159 private static final String EXTRA_UI_OPTIONS = "settings:ui_options"; 160 161 private static final int REQUEST_SUGGESTION = 42; 162 163 private String mFragmentClass; 164 165 private CharSequence mInitialTitle; 166 private int mInitialTitleResId; 167 168 private static final String[] LIKE_SHORTCUT_INTENT_ACTION_ARRAY = { 169 "android.settings.APPLICATION_DETAILS_SETTINGS" 170 }; 171 172 private SharedPreferences mDevelopmentPreferences; 173 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener; 174 175 private boolean mBatteryPresent = true; 176 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() { 177 @Override 178 public void onReceive(Context context, Intent intent) { 179 String action = intent.getAction(); 180 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { 181 boolean batteryPresent = Utils.isBatteryPresent(intent); 182 183 if (mBatteryPresent != batteryPresent) { 184 mBatteryPresent = batteryPresent; 185 updateTilesList(); 186 } 187 } 188 } 189 }; 190 191 private DynamicIndexableContentMonitor mDynamicIndexableContentMonitor; 192 193 private ActionBar mActionBar; 194 private SwitchBar mSwitchBar; 195 196 private Button mNextButton; 197 198 @VisibleForTesting 199 boolean mDisplayHomeAsUpEnabled; 200 @VisibleForTesting 201 boolean mDisplaySearch; 202 203 private boolean mIsShowingDashboard; 204 private boolean mIsShortcut; 205 206 private ViewGroup mContent; 207 208 private SearchFeatureProvider mSearchFeatureProvider; 209 private MetricsFeatureProvider mMetricsFeatureProvider; 210 211 // Categories 212 private ArrayList<DashboardCategory> mCategories = new ArrayList<>(); 213 214 private DashboardFeatureProvider mDashboardFeatureProvider; 215 private ComponentName mCurrentSuggestion; 216 217 public SwitchBar getSwitchBar() { 218 return mSwitchBar; 219 } 220 221 @Override 222 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) { 223 startPreferencePanel(caller, pref.getFragment(), pref.getExtras(), -1, pref.getTitle(), 224 null, 0); 225 return true; 226 } 227 228 @Override 229 public boolean onPreferenceTreeClick(Preference preference) { 230 return false; 231 } 232 233 @Override 234 public boolean onCreateOptionsMenu(Menu menu) { 235 if (!mDisplaySearch) { 236 return false; 237 } 238 mSearchFeatureProvider.setUpSearchMenu(menu, this); 239 return true; 240 } 241 242 @Override 243 public SharedPreferences getSharedPreferences(String name, int mode) { 244 if (name.equals(getPackageName() + "_preferences")) { 245 return new SharedPreferencesLogger(this, getMetricsTag()); 246 } 247 return super.getSharedPreferences(name, mode); 248 } 249 250 private String getMetricsTag() { 251 String tag = getClass().getName(); 252 if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) { 253 tag = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT); 254 } 255 if (tag.startsWith("com.android.settings.")) { 256 tag = tag.replace("com.android.settings.", ""); 257 } 258 return tag; 259 } 260 261 private static boolean isShortCutIntent(final Intent intent) { 262 Set<String> categories = intent.getCategories(); 263 return (categories != null) && categories.contains("com.android.settings.SHORTCUT"); 264 } 265 266 private static boolean isLikeShortCutIntent(final Intent intent) { 267 String action = intent.getAction(); 268 if (action == null) { 269 return false; 270 } 271 for (int i = 0; i < LIKE_SHORTCUT_INTENT_ACTION_ARRAY.length; i++) { 272 if (LIKE_SHORTCUT_INTENT_ACTION_ARRAY[i].equals(action)) return true; 273 } 274 return false; 275 } 276 277 @Override 278 protected void onCreate(Bundle savedState) { 279 super.onCreate(savedState); 280 long startTime = System.currentTimeMillis(); 281 282 final FeatureFactory factory = FeatureFactory.getFactory(this); 283 284 mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this); 285 mSearchFeatureProvider = factory.getSearchFeatureProvider(); 286 mMetricsFeatureProvider = factory.getMetricsFeatureProvider(); 287 288 // Should happen before any call to getIntent() 289 getMetaData(); 290 291 final Intent intent = getIntent(); 292 if (intent.hasExtra(EXTRA_UI_OPTIONS)) { 293 getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0)); 294 } 295 296 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE, 297 Context.MODE_PRIVATE); 298 299 // Getting Intent properties can only be done after the super.onCreate(...) 300 final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); 301 302 mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) || 303 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false); 304 305 final ComponentName cn = intent.getComponent(); 306 final String className = cn.getClassName(); 307 308 mIsShowingDashboard = className.equals(Settings.class.getName()); 309 310 // This is a "Sub Settings" when: 311 // - this is a real SubSettings 312 // - or :settings:show_fragment_as_subsetting is passed to the Intent 313 final boolean isSubSettings = this instanceof SubSettings || 314 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false); 315 316 // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content 317 // insets 318 if (isSubSettings) { 319 setTheme(R.style.Theme_SubSettings); 320 } 321 322 setContentView(mIsShowingDashboard ? 323 R.layout.settings_main_dashboard : R.layout.settings_main_prefs); 324 325 mContent = (ViewGroup) findViewById(R.id.main_content); 326 327 getFragmentManager().addOnBackStackChangedListener(this); 328 329 if (savedState != null) { 330 // We are restarting from a previous saved state; used that to initialize, instead 331 // of starting fresh. 332 setTitleFromIntent(intent); 333 334 ArrayList<DashboardCategory> categories = 335 savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES); 336 if (categories != null) { 337 mCategories.clear(); 338 mCategories.addAll(categories); 339 setTitleFromBackStack(); 340 } 341 342 mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP); 343 344 } else { 345 launchSettingFragment(initialFragmentName, isSubSettings, intent); 346 } 347 348 mActionBar = getActionBar(); 349 if (mActionBar != null) { 350 mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled); 351 mActionBar.setHomeButtonEnabled(mDisplayHomeAsUpEnabled); 352 } 353 mSwitchBar = (SwitchBar) findViewById(R.id.switch_bar); 354 if (mSwitchBar != null) { 355 mSwitchBar.setMetricsTag(getMetricsTag()); 356 } 357 358 // see if we should show Back/Next buttons 359 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) { 360 361 View buttonBar = findViewById(R.id.button_bar); 362 if (buttonBar != null) { 363 buttonBar.setVisibility(View.VISIBLE); 364 365 Button backButton = (Button)findViewById(R.id.back_button); 366 backButton.setOnClickListener(new OnClickListener() { 367 public void onClick(View v) { 368 setResult(RESULT_CANCELED, null); 369 finish(); 370 } 371 }); 372 Button skipButton = (Button)findViewById(R.id.skip_button); 373 skipButton.setOnClickListener(new OnClickListener() { 374 public void onClick(View v) { 375 setResult(RESULT_OK, null); 376 finish(); 377 } 378 }); 379 mNextButton = (Button)findViewById(R.id.next_button); 380 mNextButton.setOnClickListener(new OnClickListener() { 381 public void onClick(View v) { 382 setResult(RESULT_OK, null); 383 finish(); 384 } 385 }); 386 387 // set our various button parameters 388 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) { 389 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT); 390 if (TextUtils.isEmpty(buttonText)) { 391 mNextButton.setVisibility(View.GONE); 392 } 393 else { 394 mNextButton.setText(buttonText); 395 } 396 } 397 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) { 398 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT); 399 if (TextUtils.isEmpty(buttonText)) { 400 backButton.setVisibility(View.GONE); 401 } 402 else { 403 backButton.setText(buttonText); 404 } 405 } 406 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) { 407 skipButton.setVisibility(View.VISIBLE); 408 } 409 } 410 } 411 412 if (DEBUG_TIMING) { 413 Log.d(LOG_TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms"); 414 } 415 } 416 417 @VisibleForTesting 418 void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) { 419 if (!mIsShowingDashboard && initialFragmentName != null) { 420 mDisplaySearch = false; 421 // UP will be shown only if it is a sub settings 422 if (mIsShortcut) { 423 mDisplayHomeAsUpEnabled = isSubSettings; 424 } else if (isSubSettings) { 425 mDisplayHomeAsUpEnabled = true; 426 } else { 427 mDisplayHomeAsUpEnabled = false; 428 } 429 setTitleFromIntent(intent); 430 431 Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 432 switchToFragment(initialFragmentName, initialArguments, true, false, 433 mInitialTitleResId, mInitialTitle, false); 434 } else { 435 // No UP affordance if we are displaying the main Dashboard 436 mDisplayHomeAsUpEnabled = false; 437 // Show Search affordance 438 mDisplaySearch = true; 439 mInitialTitleResId = R.string.dashboard_title; 440 441 switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false, 442 mInitialTitleResId, mInitialTitle, false); 443 } 444 } 445 446 public void setDisplaySearchMenu(boolean displaySearch) { 447 if (displaySearch != mDisplaySearch) { 448 mDisplaySearch = displaySearch; 449 invalidateOptionsMenu(); 450 } 451 } 452 453 private void setTitleFromIntent(Intent intent) { 454 final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1); 455 if (initialTitleResId > 0) { 456 mInitialTitle = null; 457 mInitialTitleResId = initialTitleResId; 458 459 final String initialTitleResPackageName = intent.getStringExtra( 460 EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME); 461 if (initialTitleResPackageName != null) { 462 try { 463 Context authContext = createPackageContextAsUser(initialTitleResPackageName, 464 0 /* flags */, new UserHandle(UserHandle.myUserId())); 465 mInitialTitle = authContext.getResources().getText(mInitialTitleResId); 466 setTitle(mInitialTitle); 467 mInitialTitleResId = -1; 468 return; 469 } catch (NameNotFoundException e) { 470 Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName); 471 } 472 } else { 473 setTitle(mInitialTitleResId); 474 } 475 } else { 476 mInitialTitleResId = -1; 477 final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE); 478 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle(); 479 setTitle(mInitialTitle); 480 } 481 } 482 483 @Override 484 public void onBackStackChanged() { 485 setTitleFromBackStack(); 486 } 487 488 private void setTitleFromBackStack() { 489 final int count = getFragmentManager().getBackStackEntryCount(); 490 491 if (count == 0) { 492 if (mInitialTitleResId > 0) { 493 setTitle(mInitialTitleResId); 494 } else { 495 setTitle(mInitialTitle); 496 } 497 return; 498 } 499 500 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1); 501 setTitleFromBackStackEntry(bse); 502 } 503 504 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) { 505 final CharSequence title; 506 final int titleRes = bse.getBreadCrumbTitleRes(); 507 if (titleRes > 0) { 508 title = getText(titleRes); 509 } else { 510 title = bse.getBreadCrumbTitle(); 511 } 512 if (title != null) { 513 setTitle(title); 514 } 515 } 516 517 @Override 518 protected void onSaveInstanceState(Bundle outState) { 519 super.onSaveInstanceState(outState); 520 saveState(outState); 521 } 522 523 /** 524 * For testing purposes to avoid crashes from final variables in Activity's onSaveInstantState. 525 */ 526 @VisibleForTesting 527 void saveState(Bundle outState) { 528 if (mCategories.size() > 0) { 529 outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories); 530 } 531 532 outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled); 533 outState.putBoolean(SAVE_KEY_SHOW_SEARCH, mDisplaySearch); 534 } 535 536 @Override 537 protected void onRestoreInstanceState(Bundle savedInstanceState) { 538 super.onRestoreInstanceState(savedInstanceState); 539 540 mDisplayHomeAsUpEnabled = savedInstanceState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP); 541 mDisplaySearch = savedInstanceState.getBoolean(SAVE_KEY_SHOW_SEARCH); 542 } 543 544 @Override 545 protected void onResume() { 546 super.onResume(); 547 548 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() { 549 @Override 550 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 551 updateTilesList(); 552 } 553 }; 554 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener( 555 mDevelopmentPreferencesListener); 556 557 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 558 if (mDynamicIndexableContentMonitor == null) { 559 mDynamicIndexableContentMonitor = new DynamicIndexableContentMonitor(); 560 } 561 mDynamicIndexableContentMonitor.register(this, LOADER_ID_INDEXABLE_CONTENT_MONITOR); 562 563 updateTilesList(); 564 } 565 566 @Override 567 protected void onPause() { 568 super.onPause(); 569 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener( 570 mDevelopmentPreferencesListener); 571 mDevelopmentPreferencesListener = null; 572 unregisterReceiver(mBatteryInfoReceiver); 573 if (mDynamicIndexableContentMonitor != null) { 574 mDynamicIndexableContentMonitor.unregister(this, LOADER_ID_INDEXABLE_CONTENT_MONITOR); 575 } 576 } 577 578 @Override 579 public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { 580 final Bitmap icon = getBitmapFromXmlResource(R.drawable.ic_launcher_settings); 581 taskDescription.setIcon(icon); 582 super.setTaskDescription(taskDescription); 583 } 584 585 protected boolean isValidFragment(String fragmentName) { 586 // Almost all fragments are wrapped in this, 587 // except for a few that have their own activities. 588 for (int i = 0; i < SettingsGateway.ENTRY_FRAGMENTS.length; i++) { 589 if (SettingsGateway.ENTRY_FRAGMENTS[i].equals(fragmentName)) return true; 590 } 591 return false; 592 } 593 594 @Override 595 public Intent getIntent() { 596 Intent superIntent = super.getIntent(); 597 String startingFragment = getStartingFragmentClass(superIntent); 598 // This is called from super.onCreate, isMultiPane() is not yet reliable 599 // Do not use onIsHidingHeaders either, which relies itself on this method 600 if (startingFragment != null) { 601 Intent modIntent = new Intent(superIntent); 602 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment); 603 Bundle args = superIntent.getExtras(); 604 if (args != null) { 605 args = new Bundle(args); 606 } else { 607 args = new Bundle(); 608 } 609 args.putParcelable("intent", superIntent); 610 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 611 return modIntent; 612 } 613 return superIntent; 614 } 615 616 /** 617 * Checks if the component name in the intent is different from the Settings class and 618 * returns the class name to load as a fragment. 619 */ 620 private String getStartingFragmentClass(Intent intent) { 621 if (mFragmentClass != null) return mFragmentClass; 622 623 String intentClass = intent.getComponent().getClassName(); 624 if (intentClass.equals(getClass().getName())) return null; 625 626 if ("com.android.settings.ManageApplications".equals(intentClass) 627 || "com.android.settings.RunningServices".equals(intentClass) 628 || "com.android.settings.applications.StorageUse".equals(intentClass)) { 629 // Old names of manage apps. 630 intentClass = com.android.settings.applications.ManageApplications.class.getName(); 631 } 632 633 return intentClass; 634 } 635 636 /** 637 * Start a new fragment containing a preference panel. If the preferences 638 * are being displayed in multi-pane mode, the given fragment class will 639 * be instantiated and placed in the appropriate pane. If running in 640 * single-pane mode, a new activity will be launched in which to show the 641 * fragment. 642 * 643 * @param fragmentClass Full name of the class implementing the fragment. 644 * @param args Any desired arguments to supply to the fragment. 645 * @param titleRes Optional resource identifier of the title of this 646 * fragment. 647 * @param titleText Optional text of the title of this fragment. 648 * @param resultTo Optional fragment that result data should be sent to. 649 * If non-null, resultTo.onActivityResult() will be called when this 650 * preference panel is done. The launched panel must use 651 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done. 652 * @param resultRequestCode If resultTo is non-null, this is the caller's 653 * request code to be received with the result. 654 */ 655 public void startPreferencePanel(Fragment caller, String fragmentClass, Bundle args, 656 int titleRes, CharSequence titleText, Fragment resultTo, int resultRequestCode) { 657 String title = null; 658 if (titleRes < 0) { 659 if (titleText != null) { 660 title = titleText.toString(); 661 } else { 662 // There not much we can do in that case 663 title = ""; 664 } 665 } 666 Utils.startWithFragment(this, fragmentClass, args, resultTo, resultRequestCode, 667 titleRes, title, mIsShortcut, mMetricsFeatureProvider.getMetricsCategory(caller)); 668 } 669 670 /** 671 * Start a new fragment in a new activity containing a preference panel for a given user. If the 672 * preferences are being displayed in multi-pane mode, the given fragment class will be 673 * instantiated and placed in the appropriate pane. If running in single-pane mode, a new 674 * activity will be launched in which to show the fragment. 675 * 676 * @param fragmentClass Full name of the class implementing the fragment. 677 * @param args Any desired arguments to supply to the fragment. 678 * @param titleRes Optional resource identifier of the title of this fragment. 679 * @param titleText Optional text of the title of this fragment. 680 * @param userHandle The user for which the panel has to be started. 681 */ 682 public void startPreferencePanelAsUser(Fragment caller, String fragmentClass, 683 Bundle args, int titleRes, CharSequence titleText, UserHandle userHandle) { 684 // This is a workaround. 685 // 686 // Calling startWithFragmentAsUser() without specifying FLAG_ACTIVITY_NEW_TASK to the intent 687 // starting the fragment could cause a native stack corruption. See b/17523189. However, 688 // adding that flag and start the preference panel with the same UserHandler will make it 689 // impossible to use back button to return to the previous screen. See b/20042570. 690 // 691 // We work around this issue by adding FLAG_ACTIVITY_NEW_TASK to the intent, while doing 692 // another check here to call startPreferencePanel() instead of startWithFragmentAsUser() 693 // when we're calling it as the same user. 694 if (userHandle.getIdentifier() == UserHandle.myUserId()) { 695 startPreferencePanel(caller, fragmentClass, args, titleRes, titleText, null, 0); 696 } else { 697 String title = null; 698 if (titleRes < 0) { 699 if (titleText != null) { 700 title = titleText.toString(); 701 } else { 702 // There not much we can do in that case 703 title = ""; 704 } 705 } 706 Utils.startWithFragmentAsUser(this, fragmentClass, args, titleRes, title, 707 mIsShortcut, mMetricsFeatureProvider.getMetricsCategory(caller), userHandle); 708 } 709 } 710 711 /** 712 * Called by a preference panel fragment to finish itself. 713 * 714 * @param caller The fragment that is asking to be finished. 715 * @param resultCode Optional result code to send back to the original 716 * launching fragment. 717 * @param resultData Optional result data to send back to the original 718 * launching fragment. 719 */ 720 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) { 721 setResult(resultCode, resultData); 722 finish(); 723 } 724 725 /** 726 * Start a new fragment. 727 * 728 * @param fragment The fragment to start 729 * @param push If true, the current fragment will be pushed onto the back stack. If false, 730 * the current fragment will be replaced. 731 */ 732 public void startPreferenceFragment(Fragment fragment, boolean push) { 733 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 734 transaction.replace(R.id.main_content, fragment); 735 if (push) { 736 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 737 transaction.addToBackStack(BACK_STACK_PREFS); 738 } else { 739 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 740 } 741 transaction.commitAllowingStateLoss(); 742 } 743 744 /** 745 * Switch to a specific Fragment with taking care of validation, Title and BackStack 746 */ 747 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate, 748 boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) { 749 if (validate && !isValidFragment(fragmentName)) { 750 throw new IllegalArgumentException("Invalid fragment for this activity: " 751 + fragmentName); 752 } 753 Fragment f = Fragment.instantiate(this, fragmentName, args); 754 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 755 transaction.replace(R.id.main_content, f); 756 if (withTransition) { 757 TransitionManager.beginDelayedTransition(mContent); 758 } 759 if (addToBackStack) { 760 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS); 761 } 762 if (titleResId > 0) { 763 transaction.setBreadCrumbTitle(titleResId); 764 } else if (title != null) { 765 transaction.setBreadCrumbTitle(title); 766 } 767 transaction.commitAllowingStateLoss(); 768 getFragmentManager().executePendingTransactions(); 769 return f; 770 } 771 772 private void updateTilesList() { 773 // Generally the items that are will be changing from these updates will 774 // not be in the top list of tiles, so run it in the background and the 775 // SettingsDrawerActivity will pick up on the updates automatically. 776 AsyncTask.execute(new Runnable() { 777 @Override 778 public void run() { 779 doUpdateTilesList(); 780 } 781 }); 782 } 783 784 private void doUpdateTilesList() { 785 PackageManager pm = getPackageManager(); 786 final UserManager um = UserManager.get(this); 787 final boolean isAdmin = um.isAdminUser(); 788 789 String packageName = getPackageName(); 790 setTileEnabled(new ComponentName(packageName, WifiSettingsActivity.class.getName()), 791 pm.hasSystemFeature(PackageManager.FEATURE_WIFI), isAdmin); 792 793 setTileEnabled(new ComponentName(packageName, 794 Settings.BluetoothSettingsActivity.class.getName()), 795 pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH), isAdmin); 796 797 setTileEnabled(new ComponentName(packageName, 798 Settings.DataUsageSummaryActivity.class.getName()), 799 Utils.isBandwidthControlEnabled(), isAdmin); 800 801 setTileEnabled(new ComponentName(packageName, 802 Settings.SimSettingsActivity.class.getName()), 803 Utils.showSimCardTile(this), isAdmin); 804 805 setTileEnabled(new ComponentName(packageName, 806 Settings.PowerUsageSummaryActivity.class.getName()), 807 mBatteryPresent, isAdmin); 808 809 setTileEnabled(new ComponentName(packageName, 810 Settings.UserSettingsActivity.class.getName()), 811 UserHandle.MU_ENABLED && UserManager.supportsMultipleUsers() 812 && !Utils.isMonkeyRunning(), isAdmin); 813 814 setTileEnabled(new ComponentName(packageName, 815 Settings.NetworkDashboardActivity.class.getName()), 816 !UserManager.isDeviceInDemoMode(this), isAdmin); 817 818 setTileEnabled(new ComponentName(packageName, 819 Settings.ConnectedDeviceDashboardActivity.class.getName()), 820 !UserManager.isDeviceInDemoMode(this), isAdmin); 821 822 setTileEnabled(new ComponentName(packageName, 823 Settings.DateTimeSettingsActivity.class.getName()), 824 !UserManager.isDeviceInDemoMode(this), isAdmin); 825 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); 826 setTileEnabled(new ComponentName(packageName, 827 Settings.PaymentSettingsActivity.class.getName()), 828 pm.hasSystemFeature(PackageManager.FEATURE_NFC) 829 && pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION) 830 && adapter != null && adapter.isEnabled(), isAdmin); 831 832 setTileEnabled(new ComponentName(packageName, 833 Settings.PrintSettingsActivity.class.getName()), 834 pm.hasSystemFeature(PackageManager.FEATURE_PRINTING), isAdmin); 835 836 final boolean showDev = mDevelopmentPreferences.getBoolean( 837 DevelopmentSettings.PREF_SHOW, android.os.Build.TYPE.equals("eng")) 838 && !um.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES); 839 setTileEnabled(new ComponentName(packageName, 840 Settings.DevelopmentSettingsActivity.class.getName()), 841 showDev, isAdmin); 842 843 // Enable/disable backup settings depending on whether the user is admin. 844 setTileEnabled(new ComponentName(packageName, 845 BackupSettingsActivity.class.getName()), true, isAdmin); 846 847 setTileEnabled(new ComponentName(packageName, 848 Settings.WifiDisplaySettingsActivity.class.getName()), 849 WifiDisplaySettings.isAvailable(this), isAdmin); 850 851 if (UserHandle.MU_ENABLED && !isAdmin) { 852 853 // When on restricted users, disable all extra categories (but only the settings ones). 854 final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories(); 855 synchronized (categories) { 856 for (DashboardCategory category : categories) { 857 final int tileCount = category.getTilesCount(); 858 for (int i = 0; i < tileCount; i++) { 859 final ComponentName component = category.getTile(i).intent.getComponent(); 860 861 final String name = component.getClassName(); 862 final boolean isEnabledForRestricted = ArrayUtils.contains( 863 SettingsGateway.SETTINGS_FOR_RESTRICTED, name); 864 if (packageName.equals(component.getPackageName()) 865 && !isEnabledForRestricted) { 866 setTileEnabled(component, false, isAdmin); 867 } 868 } 869 } 870 } 871 } 872 873 // Final step, refresh categories. 874 updateCategories(); 875 } 876 877 private void setTileEnabled(ComponentName component, boolean enabled, boolean isAdmin) { 878 if (UserHandle.MU_ENABLED && !isAdmin && getPackageName().equals(component.getPackageName()) 879 && !ArrayUtils.contains(SettingsGateway.SETTINGS_FOR_RESTRICTED, 880 component.getClassName())) { 881 enabled = false; 882 } 883 setTileEnabled(component, enabled); 884 } 885 886 private void getMetaData() { 887 try { 888 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(), 889 PackageManager.GET_META_DATA); 890 if (ai == null || ai.metaData == null) return; 891 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS); 892 } catch (NameNotFoundException nnfe) { 893 // No recovery 894 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString()); 895 } 896 } 897 898 // give subclasses access to the Next button 899 public boolean hasNextButton() { 900 return mNextButton != null; 901 } 902 903 public Button getNextButton() { 904 return mNextButton; 905 } 906 907 @Override 908 public boolean shouldUpRecreateTask(Intent targetIntent) { 909 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class)); 910 } 911 912 public void startSuggestion(Intent intent) { 913 if (intent == null || ActivityManager.isUserAMonkey()) { 914 return; 915 } 916 mCurrentSuggestion = intent.getComponent(); 917 startActivityForResult(intent, REQUEST_SUGGESTION); 918 } 919 920 @Override 921 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 922 if (requestCode == REQUEST_SUGGESTION && mCurrentSuggestion != null 923 && resultCode != RESULT_CANCELED) { 924 getPackageManager().setComponentEnabledSetting(mCurrentSuggestion, 925 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); 926 } 927 super.onActivityResult(requestCode, resultCode, data); 928 } 929 930 @VisibleForTesting 931 Bitmap getBitmapFromXmlResource(int drawableRes) { 932 Drawable drawable = getResources().getDrawable(drawableRes, getTheme()); 933 Canvas canvas = new Canvas(); 934 Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), 935 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 936 canvas.setBitmap(bitmap); 937 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 938 drawable.draw(canvas); 939 940 return bitmap; 941 } 942 }