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