1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings; 18 19 import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.tryStartMultiPaneDeepLink; 20 import static com.android.settings.applications.appinfo.AppButtonsPreferenceController.KEY_REMOVE_TASK_WHEN_FINISHING; 21 22 import android.app.ActionBar; 23 import android.app.ActivityManager; 24 import android.app.settings.SettingsEnums; 25 import android.content.BroadcastReceiver; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.SharedPreferences; 31 import android.content.pm.ActivityInfo; 32 import android.content.pm.PackageManager; 33 import android.content.pm.PackageManager.NameNotFoundException; 34 import android.content.res.Resources; 35 import android.content.res.Resources.Theme; 36 import android.graphics.drawable.Icon; 37 import android.os.AsyncTask; 38 import android.os.Bundle; 39 import android.os.UserHandle; 40 import android.os.UserManager; 41 import android.permission.flags.Flags; 42 import android.text.TextUtils; 43 import android.util.Log; 44 import android.view.View; 45 import android.widget.Button; 46 47 import androidx.annotation.Nullable; 48 import androidx.annotation.VisibleForTesting; 49 import androidx.fragment.app.Fragment; 50 import androidx.fragment.app.FragmentManager; 51 import androidx.fragment.app.FragmentTransaction; 52 import androidx.preference.Preference; 53 import androidx.preference.PreferenceFragmentCompat; 54 import androidx.preference.PreferenceManager; 55 56 import com.android.internal.util.ArrayUtils; 57 import com.android.settings.Settings.WifiSettingsActivity; 58 import com.android.settings.activityembedding.ActivityEmbeddingUtils; 59 import com.android.settings.applications.manageapplications.ManageApplications; 60 import com.android.settings.connecteddevice.NfcAndPaymentFragment; 61 import com.android.settings.core.OnActivityResultListener; 62 import com.android.settings.core.SettingsBaseActivity; 63 import com.android.settings.core.SubSettingLauncher; 64 import com.android.settings.core.gateway.SettingsGateway; 65 import com.android.settings.dashboard.DashboardFeatureProvider; 66 import com.android.settings.homepage.SettingsHomepageActivity; 67 import com.android.settings.homepage.TopLevelSettings; 68 import com.android.settings.nfc.PaymentSettings; 69 import com.android.settings.overlay.FeatureFactory; 70 import com.android.settings.password.PasswordUtils; 71 import com.android.settings.wfd.WifiDisplaySettings; 72 import com.android.settings.widget.SettingsMainSwitchBar; 73 import com.android.settingslib.core.instrumentation.Instrumentable; 74 import com.android.settingslib.core.instrumentation.SharedPreferencesLogger; 75 import com.android.settingslib.drawer.DashboardCategory; 76 77 import com.google.android.setupcompat.util.WizardManagerHelper; 78 79 import java.util.ArrayList; 80 import java.util.List; 81 82 83 public class SettingsActivity extends SettingsBaseActivity 84 implements PreferenceManager.OnPreferenceTreeClickListener, 85 PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, 86 ButtonBarHandler, FragmentManager.OnBackStackChangedListener { 87 88 private static final String LOG_TAG = "SettingsActivity"; 89 90 // Constants for state save/restore 91 private static final String SAVE_KEY_CATEGORIES = ":settings:categories"; 92 93 /** 94 * When starting this activity, the invoking Intent can contain this extra 95 * string to specify which fragment should be initially displayed. 96 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity 97 * will call isValidFragment() to confirm that the fragment class name is valid for this 98 * activity. 99 */ 100 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment"; 101 102 /** 103 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 104 * this extra can also be specified to supply a Bundle of arguments to pass 105 * to that fragment when it is instantiated during the initial creation 106 * of the activity. 107 */ 108 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"; 109 110 /** 111 * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS} 112 */ 113 public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 114 115 // extras that allow any preference activity to be launched as part of a wizard 116 117 // show Back and Next buttons? takes boolean parameter 118 // Back will then return RESULT_CANCELED and Next RESULT_OK 119 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar"; 120 121 // add a Skip button? 122 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip"; 123 124 // specify custom text for the Back or Next buttons, or cause a button to not appear 125 // at all by setting it to null 126 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text"; 127 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text"; 128 129 /** 130 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 131 * those extra can also be specify to supply the title or title res id to be shown for 132 * that fragment. 133 */ 134 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title"; 135 /** 136 * The package name used to resolve the title resource id. 137 */ 138 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME = 139 ":settings:show_fragment_title_res_package_name"; 140 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RESID = 141 ":settings:show_fragment_title_resid"; 142 143 public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING = 144 ":settings:show_fragment_as_subsetting"; 145 public static final String EXTRA_IS_SECOND_LAYER_PAGE = ":settings:is_second_layer_page"; 146 147 /** 148 * Additional extra of Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK. 149 * Set true when the deep link intent is from a slice 150 */ 151 public static final String EXTRA_IS_FROM_SLICE = "is_from_slice"; 152 153 public static final String EXTRA_USER_HANDLE = "user_handle"; 154 public static final String EXTRA_INITIAL_CALLING_PACKAGE = "initial_calling_package"; 155 156 /** 157 * Personal or Work profile tab of {@link ProfileSelectFragment} 158 * <p>0: Personal tab. 159 * <p>1: Work profile tab. 160 */ 161 public static final String EXTRA_SHOW_FRAGMENT_TAB = 162 ":settings:show_fragment_tab"; 163 164 public static final String META_DATA_KEY_FRAGMENT_CLASS = 165 "com.android.settings.FRAGMENT_CLASS"; 166 167 public static final String META_DATA_KEY_HIGHLIGHT_MENU_KEY = 168 "com.android.settings.HIGHLIGHT_MENU_KEY"; 169 170 private static final String EXTRA_UI_OPTIONS = "settings:ui_options"; 171 172 private String mFragmentClass; 173 private String mHighlightMenuKey; 174 175 private CharSequence mInitialTitle; 176 private int mInitialTitleResId; 177 178 private boolean mBatteryPresent = true; 179 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() { 180 @Override 181 public void onReceive(Context context, Intent intent) { 182 String action = intent.getAction(); 183 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { 184 boolean batteryPresent = Utils.isBatteryPresent(intent); 185 186 if (mBatteryPresent != batteryPresent) { 187 mBatteryPresent = batteryPresent; 188 updateTilesList(); 189 } 190 } 191 } 192 }; 193 194 private SettingsMainSwitchBar mMainSwitch; 195 196 private Button mNextButton; 197 198 // Categories 199 private ArrayList<DashboardCategory> mCategories = new ArrayList<>(); 200 201 private DashboardFeatureProvider mDashboardFeatureProvider; 202 getSwitchBar()203 public SettingsMainSwitchBar getSwitchBar() { 204 return mMainSwitch; 205 } 206 207 @Override onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref)208 public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) { 209 new SubSettingLauncher(this) 210 .setDestination(pref.getFragment()) 211 .setArguments(pref.getExtras()) 212 .setSourceMetricsCategory(caller instanceof Instrumentable 213 ? ((Instrumentable) caller).getMetricsCategory() 214 : Instrumentable.METRICS_CATEGORY_UNKNOWN) 215 .setTitleRes(-1) 216 .launch(); 217 return true; 218 } 219 220 @Override onPreferenceTreeClick(Preference preference)221 public boolean onPreferenceTreeClick(Preference preference) { 222 return false; 223 } 224 225 @Override getSharedPreferences(String name, int mode)226 public SharedPreferences getSharedPreferences(String name, int mode) { 227 if (!TextUtils.equals(name, getPackageName() + "_preferences")) { 228 return super.getSharedPreferences(name, mode); 229 } 230 231 String tag = getMetricsTag(); 232 233 return new SharedPreferencesLogger(this, tag, 234 FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(), 235 lookupMetricsCategory()); 236 } 237 lookupMetricsCategory()238 private int lookupMetricsCategory() { 239 int category = SettingsEnums.PAGE_UNKNOWN; 240 Bundle args = null; 241 if (getIntent() != null) { 242 args = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 243 } 244 245 Fragment fragment = Utils.getTargetFragment(this, getMetricsTag(), args); 246 247 if (fragment instanceof Instrumentable) { 248 category = ((Instrumentable) fragment).getMetricsCategory(); 249 } 250 Log.d(LOG_TAG, "MetricsCategory is " + category); 251 252 return category; 253 } 254 getMetricsTag()255 private String getMetricsTag() { 256 String tag = null; 257 if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) { 258 tag = getInitialFragmentName(getIntent()); 259 } 260 261 if (TextUtils.isEmpty(tag)) { 262 Log.w(LOG_TAG, "MetricsTag is invalid " + tag); 263 tag = getClass().getName(); 264 } 265 return tag; 266 } 267 268 @Override onCreate(Bundle savedState)269 protected void onCreate(Bundle savedState) { 270 // Should happen before any call to getIntent() 271 getMetaData(); 272 final Intent intent = getIntent(); 273 274 if (shouldShowMultiPaneDeepLink(intent) 275 && tryStartMultiPaneDeepLink(this, intent, mHighlightMenuKey)) { 276 finish(); 277 super.onCreate(savedState); 278 return; 279 } 280 281 super.onCreate(savedState); 282 Log.d(LOG_TAG, "Starting onCreate"); 283 createUiFromIntent(savedState, intent); 284 } 285 createUiFromIntent(@ullable Bundle savedState, Intent intent)286 protected void createUiFromIntent(@Nullable Bundle savedState, Intent intent) { 287 long startTime = System.currentTimeMillis(); 288 289 final FeatureFactory factory = FeatureFactory.getFeatureFactory(); 290 mDashboardFeatureProvider = factory.getDashboardFeatureProvider(); 291 292 if (intent.hasExtra(EXTRA_UI_OPTIONS)) { 293 getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0)); 294 } 295 296 // Getting Intent properties can only be done after the super.onCreate(...) 297 final String initialFragmentName = getInitialFragmentName(intent); 298 299 // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content 300 // insets. 301 // If this is in setup flow, don't apply theme. Because light theme needs to be applied 302 // in SettingsBaseActivity#onCreate(). 303 if (isSubSettings(intent) && !WizardManagerHelper.isAnySetupWizard(getIntent())) { 304 setTheme(R.style.Theme_SubSettings); 305 } 306 307 setContentView(R.layout.settings_main_prefs); 308 309 getSupportFragmentManager().addOnBackStackChangedListener(this); 310 311 if (savedState != null) { 312 // We are restarting from a previous saved state; used that to initialize, instead 313 // of starting fresh. 314 setTitleFromIntent(intent); 315 316 ArrayList<DashboardCategory> categories = 317 savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES); 318 if (categories != null) { 319 mCategories.clear(); 320 mCategories.addAll(categories); 321 setTitleFromBackStack(); 322 } 323 } else { 324 launchSettingFragment(initialFragmentName, intent); 325 } 326 327 mMainSwitch = findViewById(R.id.switch_bar); 328 if (mMainSwitch != null) { 329 mMainSwitch.setMetricsCategory(lookupMetricsCategory()); 330 mMainSwitch.setTranslationZ(findViewById(R.id.main_content).getTranslationZ() + 1); 331 } 332 333 // see if we should show Back/Next buttons 334 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) { 335 336 View buttonBar = findViewById(R.id.button_bar); 337 if (buttonBar != null) { 338 buttonBar.setVisibility(View.VISIBLE); 339 340 Button backButton = findViewById(R.id.back_button); 341 backButton.setOnClickListener(v -> { 342 setResult(RESULT_CANCELED, null); 343 finish(); 344 }); 345 Button skipButton = findViewById(R.id.skip_button); 346 skipButton.setOnClickListener(v -> { 347 setResult(RESULT_OK, null); 348 finish(); 349 }); 350 mNextButton = findViewById(R.id.next_button); 351 mNextButton.setOnClickListener(v -> { 352 setResult(RESULT_OK, null); 353 finish(); 354 }); 355 356 // set our various button parameters 357 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) { 358 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT); 359 if (TextUtils.isEmpty(buttonText)) { 360 mNextButton.setVisibility(View.GONE); 361 } else { 362 mNextButton.setText(buttonText); 363 } 364 } 365 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) { 366 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT); 367 if (TextUtils.isEmpty(buttonText)) { 368 backButton.setVisibility(View.GONE); 369 } else { 370 backButton.setText(buttonText); 371 } 372 } 373 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) { 374 skipButton.setVisibility(View.VISIBLE); 375 } 376 } 377 } 378 379 if (DEBUG_TIMING) { 380 Log.d(LOG_TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms"); 381 } 382 } 383 setActionBarStatus()384 private void setActionBarStatus() { 385 final boolean isActionBarButtonEnabled = isActionBarButtonEnabled(getIntent()); 386 387 final ActionBar actionBar = getActionBar(); 388 if (actionBar != null) { 389 actionBar.setDisplayHomeAsUpEnabled(isActionBarButtonEnabled); 390 actionBar.setHomeButtonEnabled(isActionBarButtonEnabled); 391 actionBar.setDisplayShowTitleEnabled(true); 392 } 393 } 394 isActionBarButtonEnabled(Intent intent)395 private boolean isActionBarButtonEnabled(Intent intent) { 396 if (WizardManagerHelper.isAnySetupWizard(intent)) { 397 return false; 398 } 399 final boolean isSecondLayerPage = 400 intent.getBooleanExtra(EXTRA_IS_SECOND_LAYER_PAGE, false); 401 402 // TODO: move Settings's ActivityEmbeddingUtils to SettingsLib. 403 return !com.android.settingslib.activityembedding.ActivityEmbeddingUtils 404 .shouldHideNavigateUpButton(this, isSecondLayerPage); 405 } 406 isSubSettings(Intent intent)407 private boolean isSubSettings(Intent intent) { 408 return this instanceof SubSettings || 409 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false); 410 } 411 shouldShowMultiPaneDeepLink(Intent intent)412 private boolean shouldShowMultiPaneDeepLink(Intent intent) { 413 if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) { 414 return false; 415 } 416 417 // If the activity is task root, starting trampoline is needed in order to show two-pane UI. 418 // If FLAG_ACTIVITY_NEW_TASK is set, the activity will become the start of a new task on 419 // this history stack, so starting trampoline is needed in order to notify the homepage that 420 // the highlight key is changed. 421 if (!isTaskRoot() && (intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { 422 return false; 423 } 424 425 // Only starts trampoline for deep links. Should return false for all the cases that 426 // Settings app starts SettingsActivity or SubSetting by itself. 427 if (intent.getAction() == null) { 428 // Other apps should send deep link intent which matches intent filter of the Activity. 429 return false; 430 } 431 432 // If the activity's launch mode is "singleInstance", it can't be embedded in Settings since 433 // it will be created in a new task. 434 ActivityInfo info = intent.resolveActivityInfo(getPackageManager(), 435 PackageManager.MATCH_DEFAULT_ONLY); 436 if (info.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { 437 Log.w(LOG_TAG, "launchMode: singleInstance"); 438 return false; 439 } 440 441 if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) { 442 // Slice deep link starts the Intent using SubSettingLauncher. Returns true to show 443 // 2-pane deep link. 444 return true; 445 } 446 447 if (isSubSettings(intent)) { 448 return false; 449 } 450 451 if (intent.getBooleanExtra(SettingsHomepageActivity.EXTRA_IS_FROM_SETTINGS_HOMEPAGE, 452 /* defaultValue */ false)) { 453 return false; 454 } 455 456 if (TextUtils.equals(intent.getAction(), Intent.ACTION_CREATE_SHORTCUT)) { 457 // Returns false to show full screen for Intent.ACTION_CREATE_SHORTCUT because 458 // - Launcher startActivityForResult for Intent.ACTION_CREATE_SHORTCUT and activity 459 // stack starts from launcher, CreateShortcutActivity will not follows SplitPaitRule 460 // registered by Settings. 461 // - There is no CreateShortcutActivity entry point from Settings app UI. 462 return false; 463 } 464 465 return true; 466 } 467 468 /** Returns the initial calling package name that launches the activity. */ getInitialCallingPackage()469 public String getInitialCallingPackage() { 470 String callingPackage = PasswordUtils.getCallingAppPackageName(getActivityToken()); 471 if (!TextUtils.equals(callingPackage, getPackageName())) { 472 return callingPackage; 473 } 474 475 String initialCallingPackage = getIntent().getStringExtra(EXTRA_INITIAL_CALLING_PACKAGE); 476 return TextUtils.isEmpty(initialCallingPackage) ? callingPackage : initialCallingPackage; 477 } 478 479 /** Returns the initial fragment name that the activity will launch. */ 480 @VisibleForTesting getInitialFragmentName(Intent intent)481 public String getInitialFragmentName(Intent intent) { 482 return intent.getStringExtra(EXTRA_SHOW_FRAGMENT); 483 } 484 485 @Override onApplyThemeResource(Theme theme, int resid, boolean first)486 protected void onApplyThemeResource(Theme theme, int resid, boolean first) { 487 theme.applyStyle(R.style.SetupWizardPartnerResource, true); 488 super.onApplyThemeResource(theme, resid, first); 489 } 490 491 @Override onActivityResult(int requestCode, int resultCode, @Nullable Intent data)492 protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 493 super.onActivityResult(requestCode, resultCode, data); 494 final List<Fragment> fragments = getSupportFragmentManager().getFragments(); 495 if (fragments != null) { 496 for (Fragment fragment : fragments) { 497 if (fragment instanceof OnActivityResultListener) { 498 fragment.onActivityResult(requestCode, resultCode, data); 499 } 500 } 501 } 502 } 503 504 @VisibleForTesting launchSettingFragment(String initialFragmentName, Intent intent)505 void launchSettingFragment(String initialFragmentName, Intent intent) { 506 if (initialFragmentName != null) { 507 if (SettingsActivityUtil.launchSpaActivity(this, initialFragmentName, intent)) { 508 finish(); 509 return; 510 } 511 512 setTitleFromIntent(intent); 513 514 Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 515 switchToFragment(initialFragmentName, initialArguments, true, 516 mInitialTitleResId, mInitialTitle); 517 } else { 518 // Show search icon as up affordance if we are displaying the main Dashboard 519 mInitialTitleResId = R.string.dashboard_title; 520 switchToFragment(TopLevelSettings.class.getName(), null /* args */, false, 521 mInitialTitleResId, mInitialTitle); 522 } 523 } 524 setTitleFromIntent(Intent intent)525 private void setTitleFromIntent(Intent intent) { 526 Log.d(LOG_TAG, "Starting to set activity title"); 527 final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1); 528 if (initialTitleResId > 0) { 529 mInitialTitle = null; 530 mInitialTitleResId = initialTitleResId; 531 532 final String initialTitleResPackageName = intent.getStringExtra( 533 EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME); 534 if (initialTitleResPackageName != null) { 535 try { 536 Context authContext = createPackageContextAsUser(initialTitleResPackageName, 537 0 /* flags */, new UserHandle(UserHandle.myUserId())); 538 mInitialTitle = authContext.getResources().getText(mInitialTitleResId); 539 setTitle(mInitialTitle); 540 mInitialTitleResId = -1; 541 return; 542 } catch (NameNotFoundException e) { 543 Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName); 544 } catch (Resources.NotFoundException resourceNotFound) { 545 Log.w(LOG_TAG, 546 "Could not find title resource in " + initialTitleResPackageName); 547 } 548 } else { 549 setTitle(mInitialTitleResId); 550 } 551 } else { 552 mInitialTitleResId = -1; 553 final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE); 554 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle(); 555 setTitle(mInitialTitle); 556 } 557 Log.d(LOG_TAG, "Done setting title"); 558 } 559 560 @Override onBackStackChanged()561 public void onBackStackChanged() { 562 setTitleFromBackStack(); 563 } 564 setTitleFromBackStack()565 private void setTitleFromBackStack() { 566 final int count = getSupportFragmentManager().getBackStackEntryCount(); 567 568 if (count == 0) { 569 if (mInitialTitleResId > 0) { 570 setTitle(mInitialTitleResId); 571 } else { 572 setTitle(mInitialTitle); 573 } 574 return; 575 } 576 577 FragmentManager.BackStackEntry bse = getSupportFragmentManager(). 578 getBackStackEntryAt(count - 1); 579 setTitleFromBackStackEntry(bse); 580 } 581 setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse)582 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) { 583 final CharSequence title; 584 final int titleRes = bse.getBreadCrumbTitleRes(); 585 if (titleRes > 0) { 586 title = getText(titleRes); 587 } else { 588 title = bse.getBreadCrumbTitle(); 589 } 590 if (title != null) { 591 setTitle(title); 592 } 593 } 594 595 @Override onSaveInstanceState(Bundle outState)596 protected void onSaveInstanceState(Bundle outState) { 597 super.onSaveInstanceState(outState); 598 saveState(outState); 599 } 600 601 /** 602 * For testing purposes to avoid crashes from final variables in Activity's onSaveInstantState. 603 */ 604 @VisibleForTesting saveState(Bundle outState)605 void saveState(Bundle outState) { 606 if (mCategories.size() > 0) { 607 outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories); 608 } 609 } 610 611 @Override onResume()612 protected void onResume() { 613 super.onResume(); 614 setActionBarStatus(); 615 616 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 617 618 updateTilesList(); 619 } 620 621 @Override onPause()622 protected void onPause() { 623 super.onPause(); 624 unregisterReceiver(mBatteryInfoReceiver); 625 } 626 627 @Override setTaskDescription(ActivityManager.TaskDescription taskDescription)628 public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { 629 taskDescription.setIcon(Icon.createWithResource(this, R.drawable.ic_launcher_settings)); 630 super.setTaskDescription(taskDescription); 631 } 632 isValidFragment(String fragmentName)633 protected boolean isValidFragment(String fragmentName) { 634 // Almost all fragments are wrapped in this, 635 // except for a few that have their own activities. 636 for (int i = 0; i < SettingsGateway.ENTRY_FRAGMENTS.length; i++) { 637 if (SettingsGateway.ENTRY_FRAGMENTS[i].equals(fragmentName)) return true; 638 } 639 return false; 640 } 641 642 @Override getIntent()643 public Intent getIntent() { 644 Intent superIntent = super.getIntent(); 645 String startingFragment = getStartingFragmentClass(superIntent); 646 // This is called from super.onCreate, isMultiPane() is not yet reliable 647 // Do not use onIsHidingHeaders either, which relies itself on this method 648 if (startingFragment != null) { 649 Intent modIntent = new Intent(superIntent); 650 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment); 651 Bundle args = superIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 652 if (args != null) { 653 args = new Bundle(args); 654 } else { 655 args = new Bundle(); 656 } 657 args.putParcelable("intent", superIntent); 658 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 659 return modIntent; 660 } 661 return superIntent; 662 } 663 664 /** 665 * Checks if the component name in the intent is different from the Settings class and 666 * returns the class name to load as a fragment. 667 */ getStartingFragmentClass(Intent intent)668 private String getStartingFragmentClass(Intent intent) { 669 if (mFragmentClass != null) return mFragmentClass; 670 671 String intentClass = intent.getComponent().getClassName(); 672 if (intentClass.equals(getClass().getName())) return null; 673 674 if ("com.android.settings.RunningServices".equals(intentClass) 675 || "com.android.settings.applications.StorageUse".equals(intentClass)) { 676 // Old names of manage apps. 677 intentClass = ManageApplications.class.getName(); 678 } 679 680 return intentClass; 681 } 682 683 /** 684 * Called by a preference panel fragment to finish itself. 685 * 686 * @param resultCode Optional result code to send back to the original 687 * launching fragment. 688 * @param resultData Optional result data to send back to the original 689 * launching fragment. 690 */ finishPreferencePanel(int resultCode, Intent resultData)691 public void finishPreferencePanel(int resultCode, Intent resultData) { 692 setResult(resultCode, resultData); 693 if (resultData != null && 694 resultData.getBooleanExtra(KEY_REMOVE_TASK_WHEN_FINISHING, false)) { 695 finishAndRemoveTask(); 696 } else { 697 finish(); 698 } 699 } 700 701 /** 702 * Switch to a specific Fragment with taking care of validation, Title and BackStack 703 */ switchToFragment(String fragmentName, Bundle args, boolean validate, int titleResId, CharSequence title)704 private void switchToFragment(String fragmentName, Bundle args, boolean validate, 705 int titleResId, CharSequence title) { 706 Log.d(LOG_TAG, "Switching to fragment " + fragmentName); 707 if (validate && !isValidFragment(fragmentName)) { 708 throw new IllegalArgumentException("Invalid fragment for this activity: " 709 + fragmentName); 710 } 711 Fragment f = Utils.getTargetFragment(this, fragmentName, args); 712 if (f == null) { 713 return; 714 } 715 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 716 transaction.replace(R.id.main_content, f); 717 if (titleResId > 0) { 718 transaction.setBreadCrumbTitle(titleResId); 719 } else if (title != null) { 720 transaction.setBreadCrumbTitle(title); 721 } 722 transaction.commitAllowingStateLoss(); 723 getSupportFragmentManager().executePendingTransactions(); 724 Log.d(LOG_TAG, "Executed frag manager pendingTransactions"); 725 } 726 updateTilesList()727 private void updateTilesList() { 728 // Generally the items that are will be changing from these updates will 729 // not be in the top list of tiles, so run it in the background and the 730 // SettingsBaseActivity will pick up on the updates automatically. 731 AsyncTask.execute(() -> doUpdateTilesList()); 732 } 733 doUpdateTilesList()734 private void doUpdateTilesList() { 735 PackageManager pm = getPackageManager(); 736 final UserManager um = UserManager.get(this); 737 final boolean isAdmin = um.isAdminUser(); 738 boolean somethingChanged = false; 739 final String packageName = getPackageName(); 740 final StringBuilder changedList = new StringBuilder(); 741 somethingChanged = setTileEnabled(changedList, 742 new ComponentName(packageName, WifiSettingsActivity.class.getName()), 743 pm.hasSystemFeature(PackageManager.FEATURE_WIFI), isAdmin) || somethingChanged; 744 745 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 746 Settings.BluetoothSettingsActivity.class.getName()), 747 pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH), isAdmin) 748 || somethingChanged; 749 750 // Enable DataUsageSummaryActivity if the data plan feature flag is turned on otherwise 751 // enable DataPlanUsageSummaryActivity. 752 somethingChanged = setTileEnabled(changedList, 753 new ComponentName(packageName, Settings.DataUsageSummaryActivity.class.getName()), 754 Utils.isBandwidthControlEnabled() /* enabled */, 755 isAdmin) || somethingChanged; 756 757 somethingChanged = setTileEnabled(changedList, 758 new ComponentName(packageName, 759 Settings.ConnectedDeviceDashboardActivity.class.getName()), 760 !UserManager.isDeviceInDemoMode(this) /* enabled */, 761 isAdmin) || somethingChanged; 762 763 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 764 Settings.PowerUsageSummaryActivity.class.getName()), 765 mBatteryPresent, isAdmin) || somethingChanged; 766 767 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 768 Settings.DataUsageSummaryActivity.class.getName()), 769 Utils.isBandwidthControlEnabled(), isAdmin) 770 || somethingChanged; 771 772 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 773 Settings.WifiDisplaySettingsActivity.class.getName()), 774 WifiDisplaySettings.isAvailable(this), isAdmin) 775 || somethingChanged; 776 777 if (UserHandle.MU_ENABLED && !isAdmin) { 778 // When on restricted users, disable all extra categories (but only the settings ones). 779 final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories(); 780 synchronized (categories) { 781 for (DashboardCategory category : categories) { 782 final int tileCount = category.getTilesCount(); 783 for (int i = 0; i < tileCount; i++) { 784 final ComponentName component = category.getTile(i) 785 .getIntent().getComponent(); 786 final String name = component.getClassName(); 787 final boolean isEnabledForRestricted = ArrayUtils.contains( 788 SettingsGateway.SETTINGS_FOR_RESTRICTED, name); 789 if (packageName.equals(component.getPackageName()) 790 && !isEnabledForRestricted) { 791 somethingChanged = 792 setTileEnabled(changedList, component, false, isAdmin) 793 || somethingChanged; 794 } 795 } 796 } 797 } 798 } 799 800 // Final step, refresh categories. 801 if (somethingChanged) { 802 Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories " 803 + changedList.toString()); 804 mCategoryMixin.updateCategories(); 805 } else { 806 Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call"); 807 } 808 } 809 810 /** 811 * @return whether or not the enabled state actually changed. 812 */ setTileEnabled(StringBuilder changedList, ComponentName component, boolean enabled, boolean isAdmin)813 private boolean setTileEnabled(StringBuilder changedList, ComponentName component, 814 boolean enabled, boolean isAdmin) { 815 if (UserHandle.MU_ENABLED && !isAdmin && getPackageName().equals(component.getPackageName()) 816 && !ArrayUtils.contains(SettingsGateway.SETTINGS_FOR_RESTRICTED, 817 component.getClassName())) { 818 enabled = false; 819 } 820 boolean changed = setTileEnabled(component, enabled); 821 if (changed) { 822 changedList.append(component.toShortString()).append(","); 823 } 824 return changed; 825 } 826 getMetaData()827 private void getMetaData() { 828 try { 829 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(), 830 PackageManager.GET_META_DATA); 831 if (ai == null || ai.metaData == null) return; 832 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS); 833 mHighlightMenuKey = ai.metaData.getString(META_DATA_KEY_HIGHLIGHT_MENU_KEY); 834 /* TODO(b/327036144) Once the Flags.walletRoleEnabled() is rolled out, we will replace 835 value for the fragment class within the com.android.settings.nfc.PaymentSettings 836 activity with com.android.settings.connecteddevice.NfcAndPaymentFragment so that this 837 code can be removed. 838 */ 839 if (shouldOverrideContactlessPaymentRouting()) { 840 overrideContactlessPaymentRouting(); 841 } 842 } catch (NameNotFoundException nnfe) { 843 // No recovery 844 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString()); 845 } 846 } 847 shouldOverrideContactlessPaymentRouting()848 private boolean shouldOverrideContactlessPaymentRouting() { 849 return Flags.walletRoleEnabled() 850 && TextUtils.equals(PaymentSettings.class.getName(), mFragmentClass); 851 } 852 overrideContactlessPaymentRouting()853 private void overrideContactlessPaymentRouting() { 854 mFragmentClass = NfcAndPaymentFragment.class.getName(); 855 } 856 857 // give subclasses access to the Next button hasNextButton()858 public boolean hasNextButton() { 859 return mNextButton != null; 860 } 861 getNextButton()862 public Button getNextButton() { 863 return mNextButton; 864 } 865 } 866