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