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 getSwitchBar()217 public SwitchBar getSwitchBar() { 218 return mSwitchBar; 219 } 220 221 @Override onPreferenceStartFragment(PreferenceFragment caller, Preference pref)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 onPreferenceTreeClick(Preference preference)229 public boolean onPreferenceTreeClick(Preference preference) { 230 return false; 231 } 232 233 @Override onCreateOptionsMenu(Menu menu)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 getSharedPreferences(String name, int mode)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 getMetricsTag()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 isShortCutIntent(final Intent intent)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 isLikeShortCutIntent(final Intent intent)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 onCreate(Bundle savedState)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 launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent)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 setDisplaySearchMenu(boolean displaySearch)446 public void setDisplaySearchMenu(boolean displaySearch) { 447 if (displaySearch != mDisplaySearch) { 448 mDisplaySearch = displaySearch; 449 invalidateOptionsMenu(); 450 } 451 } 452 setTitleFromIntent(Intent intent)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 onBackStackChanged()484 public void onBackStackChanged() { 485 setTitleFromBackStack(); 486 } 487 setTitleFromBackStack()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 setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse)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 onSaveInstanceState(Bundle outState)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 saveState(Bundle outState)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 onRestoreInstanceState(Bundle savedInstanceState)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 onResume()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 onPause()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 setTaskDescription(ActivityManager.TaskDescription taskDescription)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 isValidFragment(String fragmentName)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 getIntent()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 */ getStartingFragmentClass(Intent intent)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 */ startPreferencePanel(Fragment caller, String fragmentClass, Bundle args, int titleRes, CharSequence titleText, Fragment resultTo, int resultRequestCode)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 */ startPreferencePanelAsUser(Fragment caller, String fragmentClass, Bundle args, int titleRes, CharSequence titleText, UserHandle userHandle)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 */ finishPreferencePanel(Fragment caller, int resultCode, Intent resultData)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 */ startPreferenceFragment(Fragment fragment, boolean push)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 */ switchToFragment(String fragmentName, Bundle args, boolean validate, boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition)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 updateTilesList()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 doUpdateTilesList()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 setTileEnabled(ComponentName component, boolean enabled, boolean isAdmin)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 getMetaData()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 hasNextButton()899 public boolean hasNextButton() { 900 return mNextButton != null; 901 } 902 getNextButton()903 public Button getNextButton() { 904 return mNextButton; 905 } 906 907 @Override shouldUpRecreateTask(Intent targetIntent)908 public boolean shouldUpRecreateTask(Intent targetIntent) { 909 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class)); 910 } 911 startSuggestion(Intent intent)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 onActivityResult(int requestCode, int resultCode, Intent data)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 getBitmapFromXmlResource(int drawableRes)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 }