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 android.provider.Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY; 20 import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY; 21 import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI; 22 23 import static com.android.settings.applications.appinfo.AppButtonsPreferenceController.KEY_REMOVE_TASK_WHEN_FINISHING; 24 25 import android.app.ActionBar; 26 import android.app.ActivityManager; 27 import android.content.BroadcastReceiver; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.SharedPreferences; 33 import android.content.pm.ActivityInfo; 34 import android.content.pm.PackageManager; 35 import android.content.pm.PackageManager.NameNotFoundException; 36 import android.content.res.Resources; 37 import android.content.res.Resources.Theme; 38 import android.graphics.drawable.Icon; 39 import android.os.AsyncTask; 40 import android.os.Bundle; 41 import android.os.UserHandle; 42 import android.os.UserManager; 43 import android.text.TextUtils; 44 import android.util.Log; 45 import android.view.View; 46 import android.widget.Button; 47 48 import androidx.annotation.Nullable; 49 import androidx.annotation.VisibleForTesting; 50 import androidx.fragment.app.Fragment; 51 import androidx.fragment.app.FragmentManager; 52 import androidx.fragment.app.FragmentTransaction; 53 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 54 import androidx.preference.Preference; 55 import androidx.preference.PreferenceFragmentCompat; 56 import androidx.preference.PreferenceManager; 57 58 import com.android.internal.util.ArrayUtils; 59 import com.android.settings.Settings.WifiSettingsActivity; 60 import com.android.settings.activityembedding.ActivityEmbeddingUtils; 61 import com.android.settings.applications.manageapplications.ManageApplications; 62 import com.android.settings.core.OnActivityResultListener; 63 import com.android.settings.core.SettingsBaseActivity; 64 import com.android.settings.core.SubSettingLauncher; 65 import com.android.settings.core.gateway.SettingsGateway; 66 import com.android.settings.dashboard.DashboardFeatureProvider; 67 import com.android.settings.homepage.SettingsHomepageActivity; 68 import com.android.settings.homepage.SliceDeepLinkHomepageActivity; 69 import com.android.settings.homepage.TopLevelSettings; 70 import com.android.settings.overlay.FeatureFactory; 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.development.DevelopmentSettingsEnabler; 76 import com.android.settingslib.drawer.DashboardCategory; 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 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 /** 154 * Personal or Work profile tab of {@link ProfileSelectFragment} 155 * <p>0: Personal tab. 156 * <p>1: Work profile tab. 157 */ 158 public static final String EXTRA_SHOW_FRAGMENT_TAB = 159 ":settings:show_fragment_tab"; 160 161 public static final String META_DATA_KEY_FRAGMENT_CLASS = 162 "com.android.settings.FRAGMENT_CLASS"; 163 164 public static final String META_DATA_KEY_HIGHLIGHT_MENU_KEY = 165 "com.android.settings.HIGHLIGHT_MENU_KEY"; 166 167 private static final String EXTRA_UI_OPTIONS = "settings:ui_options"; 168 169 private String mFragmentClass; 170 private String mHighlightMenuKey; 171 172 private CharSequence mInitialTitle; 173 private int mInitialTitleResId; 174 175 private BroadcastReceiver mDevelopmentSettingsListener; 176 177 private boolean mBatteryPresent = true; 178 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() { 179 @Override 180 public void onReceive(Context context, Intent intent) { 181 String action = intent.getAction(); 182 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { 183 boolean batteryPresent = Utils.isBatteryPresent(intent); 184 185 if (mBatteryPresent != batteryPresent) { 186 mBatteryPresent = batteryPresent; 187 updateTilesList(); 188 } 189 } 190 } 191 }; 192 193 private SettingsMainSwitchBar mMainSwitch; 194 195 private Button mNextButton; 196 197 // Categories 198 private ArrayList<DashboardCategory> mCategories = new ArrayList<>(); 199 200 private DashboardFeatureProvider mDashboardFeatureProvider; 201 getSwitchBar()202 public SettingsMainSwitchBar getSwitchBar() { 203 return mMainSwitch; 204 } 205 206 @Override onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref)207 public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) { 208 new SubSettingLauncher(this) 209 .setDestination(pref.getFragment()) 210 .setArguments(pref.getExtras()) 211 .setSourceMetricsCategory(caller instanceof Instrumentable 212 ? ((Instrumentable) caller).getMetricsCategory() 213 : Instrumentable.METRICS_CATEGORY_UNKNOWN) 214 .setTitleRes(-1) 215 .launch(); 216 return true; 217 } 218 219 @Override onPreferenceTreeClick(Preference preference)220 public boolean onPreferenceTreeClick(Preference preference) { 221 return false; 222 } 223 224 @Override getSharedPreferences(String name, int mode)225 public SharedPreferences getSharedPreferences(String name, int mode) { 226 if (name.equals(getPackageName() + "_preferences")) { 227 return new SharedPreferencesLogger(this, getMetricsTag(), 228 FeatureFactory.getFactory(this).getMetricsFeatureProvider()); 229 } 230 return super.getSharedPreferences(name, mode); 231 } 232 getMetricsTag()233 private String getMetricsTag() { 234 String tag = null; 235 if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) { 236 tag = getInitialFragmentName(getIntent()); 237 } 238 if (TextUtils.isEmpty(tag)) { 239 Log.w(LOG_TAG, "MetricsTag is invalid " + tag); 240 tag = getClass().getName(); 241 } 242 if (tag.startsWith("com.android.settings.")) { 243 tag = tag.replace("com.android.settings.", ""); 244 } 245 return tag; 246 } 247 248 @Override onCreate(Bundle savedState)249 protected void onCreate(Bundle savedState) { 250 // Should happen before any call to getIntent() 251 getMetaData(); 252 final Intent intent = getIntent(); 253 254 if (shouldShowTwoPaneDeepLink(intent)) { 255 launchHomepageForTwoPaneDeepLink(intent); 256 finishAndRemoveTask(); 257 super.onCreate(savedState); 258 return; 259 } 260 261 super.onCreate(savedState); 262 Log.d(LOG_TAG, "Starting onCreate"); 263 264 long startTime = System.currentTimeMillis(); 265 266 final FeatureFactory factory = FeatureFactory.getFactory(this); 267 mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this); 268 269 if (intent.hasExtra(EXTRA_UI_OPTIONS)) { 270 getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0)); 271 } 272 273 // Getting Intent properties can only be done after the super.onCreate(...) 274 final String initialFragmentName = getInitialFragmentName(intent); 275 276 // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content 277 // insets. 278 // If this is in setup flow, don't apply theme. Because light theme needs to be applied 279 // in SettingsBaseActivity#onCreate(). 280 if (isSubSettings(intent) && !WizardManagerHelper.isAnySetupWizard(getIntent())) { 281 setTheme(R.style.Theme_SubSettings); 282 } 283 284 setContentView(R.layout.settings_main_prefs); 285 286 getSupportFragmentManager().addOnBackStackChangedListener(this); 287 288 if (savedState != null) { 289 // We are restarting from a previous saved state; used that to initialize, instead 290 // of starting fresh. 291 setTitleFromIntent(intent); 292 293 ArrayList<DashboardCategory> categories = 294 savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES); 295 if (categories != null) { 296 mCategories.clear(); 297 mCategories.addAll(categories); 298 setTitleFromBackStack(); 299 } 300 } else { 301 launchSettingFragment(initialFragmentName, intent); 302 } 303 304 final boolean isInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); 305 306 final ActionBar actionBar = getActionBar(); 307 if (actionBar != null) { 308 actionBar.setDisplayHomeAsUpEnabled(!isInSetupWizard); 309 actionBar.setHomeButtonEnabled(!isInSetupWizard); 310 actionBar.setDisplayShowTitleEnabled(true); 311 } 312 mMainSwitch = findViewById(R.id.switch_bar); 313 if (mMainSwitch != null) { 314 mMainSwitch.setMetricsTag(getMetricsTag()); 315 mMainSwitch.setTranslationZ(findViewById(R.id.main_content).getTranslationZ() + 1); 316 } 317 318 // see if we should show Back/Next buttons 319 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) { 320 321 View buttonBar = findViewById(R.id.button_bar); 322 if (buttonBar != null) { 323 buttonBar.setVisibility(View.VISIBLE); 324 325 Button backButton = findViewById(R.id.back_button); 326 backButton.setOnClickListener(v -> { 327 setResult(RESULT_CANCELED, null); 328 finish(); 329 }); 330 Button skipButton = findViewById(R.id.skip_button); 331 skipButton.setOnClickListener(v -> { 332 setResult(RESULT_OK, null); 333 finish(); 334 }); 335 mNextButton = findViewById(R.id.next_button); 336 mNextButton.setOnClickListener(v -> { 337 setResult(RESULT_OK, null); 338 finish(); 339 }); 340 341 // set our various button parameters 342 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) { 343 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT); 344 if (TextUtils.isEmpty(buttonText)) { 345 mNextButton.setVisibility(View.GONE); 346 } else { 347 mNextButton.setText(buttonText); 348 } 349 } 350 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) { 351 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT); 352 if (TextUtils.isEmpty(buttonText)) { 353 backButton.setVisibility(View.GONE); 354 } else { 355 backButton.setText(buttonText); 356 } 357 } 358 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) { 359 skipButton.setVisibility(View.VISIBLE); 360 } 361 } 362 } 363 364 if (DEBUG_TIMING) { 365 Log.d(LOG_TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms"); 366 } 367 } 368 isSubSettings(Intent intent)369 private boolean isSubSettings(Intent intent) { 370 return this instanceof SubSettings || 371 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false); 372 } 373 374 /** 375 * Returns the deep link trampoline intent for large screen devices. 376 */ getTrampolineIntent(Intent intent, String highlightMenuKey)377 public static Intent getTrampolineIntent(Intent intent, String highlightMenuKey) { 378 final Intent detailIntent = new Intent(intent); 379 // It's a deep link intent, SettingsHomepageActivity will set SplitPairRule and start it. 380 final Intent trampolineIntent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY) 381 .setPackage(Utils.SETTINGS_PACKAGE_NAME) 382 .replaceExtras(detailIntent); 383 384 // Relay detail intent data to prevent failure of Intent#ParseUri. 385 // If Intent#getData() is not null, Intent#toUri will return an Uri which has the scheme of 386 // Intent#getData() and it may not be the scheme of an Intent. 387 trampolineIntent.putExtra( 388 SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA, 389 detailIntent.getData()); 390 detailIntent.setData(null); 391 392 trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI, 393 detailIntent.toUri(Intent.URI_INTENT_SCHEME)); 394 395 trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, 396 highlightMenuKey); 397 trampolineIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 398 return trampolineIntent; 399 } 400 launchHomepageForTwoPaneDeepLink(Intent intent)401 private void launchHomepageForTwoPaneDeepLink(Intent intent) { 402 final Intent trampolineIntent; 403 if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) { 404 // Get menu key for slice deep link case. 405 final String highlightMenuKey = intent.getStringExtra( 406 EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY); 407 if (!TextUtils.isEmpty(highlightMenuKey)) { 408 mHighlightMenuKey = highlightMenuKey; 409 } 410 trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey); 411 trampolineIntent.setClass(this, SliceDeepLinkHomepageActivity.class); 412 } else { 413 trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey); 414 } 415 startActivity(trampolineIntent); 416 } 417 shouldShowTwoPaneDeepLink(Intent intent)418 private boolean shouldShowTwoPaneDeepLink(Intent intent) { 419 if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) { 420 return false; 421 } 422 423 // If the activity is not the task root, it should not start trampoline for deep links. 424 if (!isTaskRoot()) { 425 return false; 426 } 427 428 // Only starts trampoline for deep links. Should return false for all the cases that 429 // Settings app starts SettingsActivity or SubSetting by itself. 430 if (intent.getAction() == null) { 431 // Other apps should send deep link intent which matches intent filter of the Activity. 432 return false; 433 } 434 435 if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) { 436 // Slice deep link starts the Intent using SubSettingLauncher. Returns true to show 437 // 2-pane deep link. 438 return true; 439 } 440 441 if (isSubSettings(intent)) { 442 return false; 443 } 444 445 if (intent.getBooleanExtra(SettingsHomepageActivity.EXTRA_IS_FROM_SETTINGS_HOMEPAGE, 446 /* defaultValue */ false)) { 447 return false; 448 } 449 450 if (TextUtils.equals(intent.getAction(), Intent.ACTION_CREATE_SHORTCUT)) { 451 // Returns false to show full screen for Intent.ACTION_CREATE_SHORTCUT because 452 // - Launcher startActivityForResult for Intent.ACTION_CREATE_SHORTCUT and activity 453 // stack starts from launcher, CreateShortcutActivity will not follows SplitPaitRule 454 // registered by Settings. 455 // - There is no CreateShortcutActivity entry point from Settings app UI. 456 return false; 457 } 458 459 return true; 460 } 461 462 /** Returns the initial fragment name that the activity will launch. */ 463 @VisibleForTesting getInitialFragmentName(Intent intent)464 public String getInitialFragmentName(Intent intent) { 465 return intent.getStringExtra(EXTRA_SHOW_FRAGMENT); 466 } 467 468 @Override onApplyThemeResource(Theme theme, int resid, boolean first)469 protected void onApplyThemeResource(Theme theme, int resid, boolean first) { 470 theme.applyStyle(R.style.SetupWizardPartnerResource, true); 471 super.onApplyThemeResource(theme, resid, first); 472 } 473 474 @Override onActivityResult(int requestCode, int resultCode, @Nullable Intent data)475 protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 476 super.onActivityResult(requestCode, resultCode, data); 477 final List<Fragment> fragments = getSupportFragmentManager().getFragments(); 478 if (fragments != null) { 479 for (Fragment fragment : fragments) { 480 if (fragment instanceof OnActivityResultListener) { 481 fragment.onActivityResult(requestCode, resultCode, data); 482 } 483 } 484 } 485 } 486 487 @VisibleForTesting launchSettingFragment(String initialFragmentName, Intent intent)488 void launchSettingFragment(String initialFragmentName, Intent intent) { 489 if (initialFragmentName != null) { 490 setTitleFromIntent(intent); 491 492 Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 493 switchToFragment(initialFragmentName, initialArguments, true, 494 mInitialTitleResId, mInitialTitle); 495 } else { 496 // Show search icon as up affordance if we are displaying the main Dashboard 497 mInitialTitleResId = R.string.dashboard_title; 498 switchToFragment(TopLevelSettings.class.getName(), null /* args */, false, 499 mInitialTitleResId, mInitialTitle); 500 } 501 } 502 setTitleFromIntent(Intent intent)503 private void setTitleFromIntent(Intent intent) { 504 Log.d(LOG_TAG, "Starting to set activity title"); 505 final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1); 506 if (initialTitleResId > 0) { 507 mInitialTitle = null; 508 mInitialTitleResId = initialTitleResId; 509 510 final String initialTitleResPackageName = intent.getStringExtra( 511 EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME); 512 if (initialTitleResPackageName != null) { 513 try { 514 Context authContext = createPackageContextAsUser(initialTitleResPackageName, 515 0 /* flags */, new UserHandle(UserHandle.myUserId())); 516 mInitialTitle = authContext.getResources().getText(mInitialTitleResId); 517 setTitle(mInitialTitle); 518 mInitialTitleResId = -1; 519 return; 520 } catch (NameNotFoundException e) { 521 Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName); 522 } catch (Resources.NotFoundException resourceNotFound) { 523 Log.w(LOG_TAG, 524 "Could not find title resource in " + initialTitleResPackageName); 525 } 526 } else { 527 setTitle(mInitialTitleResId); 528 } 529 } else { 530 mInitialTitleResId = -1; 531 final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE); 532 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle(); 533 setTitle(mInitialTitle); 534 } 535 Log.d(LOG_TAG, "Done setting title"); 536 } 537 538 @Override onBackStackChanged()539 public void onBackStackChanged() { 540 setTitleFromBackStack(); 541 } 542 setTitleFromBackStack()543 private void setTitleFromBackStack() { 544 final int count = getSupportFragmentManager().getBackStackEntryCount(); 545 546 if (count == 0) { 547 if (mInitialTitleResId > 0) { 548 setTitle(mInitialTitleResId); 549 } else { 550 setTitle(mInitialTitle); 551 } 552 return; 553 } 554 555 FragmentManager.BackStackEntry bse = getSupportFragmentManager(). 556 getBackStackEntryAt(count - 1); 557 setTitleFromBackStackEntry(bse); 558 } 559 setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse)560 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) { 561 final CharSequence title; 562 final int titleRes = bse.getBreadCrumbTitleRes(); 563 if (titleRes > 0) { 564 title = getText(titleRes); 565 } else { 566 title = bse.getBreadCrumbTitle(); 567 } 568 if (title != null) { 569 setTitle(title); 570 } 571 } 572 573 @Override onSaveInstanceState(Bundle outState)574 protected void onSaveInstanceState(Bundle outState) { 575 super.onSaveInstanceState(outState); 576 saveState(outState); 577 } 578 579 /** 580 * For testing purposes to avoid crashes from final variables in Activity's onSaveInstantState. 581 */ 582 @VisibleForTesting saveState(Bundle outState)583 void saveState(Bundle outState) { 584 if (mCategories.size() > 0) { 585 outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories); 586 } 587 } 588 589 @Override onResume()590 protected void onResume() { 591 super.onResume(); 592 593 mDevelopmentSettingsListener = new BroadcastReceiver() { 594 @Override 595 public void onReceive(Context context, Intent intent) { 596 updateTilesList(); 597 } 598 }; 599 LocalBroadcastManager.getInstance(this).registerReceiver(mDevelopmentSettingsListener, 600 new IntentFilter(DevelopmentSettingsEnabler.DEVELOPMENT_SETTINGS_CHANGED_ACTION)); 601 602 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 603 604 updateTilesList(); 605 } 606 607 @Override onPause()608 protected void onPause() { 609 super.onPause(); 610 LocalBroadcastManager.getInstance(this).unregisterReceiver(mDevelopmentSettingsListener); 611 mDevelopmentSettingsListener = null; 612 unregisterReceiver(mBatteryInfoReceiver); 613 } 614 615 @Override setTaskDescription(ActivityManager.TaskDescription taskDescription)616 public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { 617 taskDescription.setIcon(Icon.createWithResource(this, R.drawable.ic_launcher_settings)); 618 super.setTaskDescription(taskDescription); 619 } 620 isValidFragment(String fragmentName)621 protected boolean isValidFragment(String fragmentName) { 622 // Almost all fragments are wrapped in this, 623 // except for a few that have their own activities. 624 for (int i = 0; i < SettingsGateway.ENTRY_FRAGMENTS.length; i++) { 625 if (SettingsGateway.ENTRY_FRAGMENTS[i].equals(fragmentName)) return true; 626 } 627 return false; 628 } 629 630 @Override getIntent()631 public Intent getIntent() { 632 Intent superIntent = super.getIntent(); 633 String startingFragment = getStartingFragmentClass(superIntent); 634 // This is called from super.onCreate, isMultiPane() is not yet reliable 635 // Do not use onIsHidingHeaders either, which relies itself on this method 636 if (startingFragment != null) { 637 Intent modIntent = new Intent(superIntent); 638 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment); 639 Bundle args = superIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 640 if (args != null) { 641 args = new Bundle(args); 642 } else { 643 args = new Bundle(); 644 } 645 args.putParcelable("intent", superIntent); 646 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 647 return modIntent; 648 } 649 return superIntent; 650 } 651 652 /** 653 * Checks if the component name in the intent is different from the Settings class and 654 * returns the class name to load as a fragment. 655 */ getStartingFragmentClass(Intent intent)656 private String getStartingFragmentClass(Intent intent) { 657 if (mFragmentClass != null) return mFragmentClass; 658 659 String intentClass = intent.getComponent().getClassName(); 660 if (intentClass.equals(getClass().getName())) return null; 661 662 if ("com.android.settings.RunningServices".equals(intentClass) 663 || "com.android.settings.applications.StorageUse".equals(intentClass)) { 664 // Old names of manage apps. 665 intentClass = ManageApplications.class.getName(); 666 } 667 668 return intentClass; 669 } 670 671 /** 672 * Called by a preference panel fragment to finish itself. 673 * 674 * @param resultCode Optional result code to send back to the original 675 * launching fragment. 676 * @param resultData Optional result data to send back to the original 677 * launching fragment. 678 */ finishPreferencePanel(int resultCode, Intent resultData)679 public void finishPreferencePanel(int resultCode, Intent resultData) { 680 setResult(resultCode, resultData); 681 if (resultData != null && 682 resultData.getBooleanExtra(KEY_REMOVE_TASK_WHEN_FINISHING, false)) { 683 finishAndRemoveTask(); 684 } else { 685 finish(); 686 } 687 } 688 689 /** 690 * Switch to a specific Fragment with taking care of validation, Title and BackStack 691 */ switchToFragment(String fragmentName, Bundle args, boolean validate, int titleResId, CharSequence title)692 private void switchToFragment(String fragmentName, Bundle args, boolean validate, 693 int titleResId, CharSequence title) { 694 Log.d(LOG_TAG, "Switching to fragment " + fragmentName); 695 if (validate && !isValidFragment(fragmentName)) { 696 throw new IllegalArgumentException("Invalid fragment for this activity: " 697 + fragmentName); 698 } 699 Fragment f = Utils.getTargetFragment(this, fragmentName, args); 700 if (f == null) { 701 return; 702 } 703 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 704 transaction.replace(R.id.main_content, f); 705 if (titleResId > 0) { 706 transaction.setBreadCrumbTitle(titleResId); 707 } else if (title != null) { 708 transaction.setBreadCrumbTitle(title); 709 } 710 transaction.commitAllowingStateLoss(); 711 getSupportFragmentManager().executePendingTransactions(); 712 Log.d(LOG_TAG, "Executed frag manager pendingTransactions"); 713 } 714 updateTilesList()715 private void updateTilesList() { 716 // Generally the items that are will be changing from these updates will 717 // not be in the top list of tiles, so run it in the background and the 718 // SettingsBaseActivity will pick up on the updates automatically. 719 AsyncTask.execute(() -> doUpdateTilesList()); 720 } 721 doUpdateTilesList()722 private void doUpdateTilesList() { 723 PackageManager pm = getPackageManager(); 724 final UserManager um = UserManager.get(this); 725 final boolean isAdmin = um.isAdminUser(); 726 boolean somethingChanged = false; 727 final String packageName = getPackageName(); 728 final StringBuilder changedList = new StringBuilder(); 729 somethingChanged = setTileEnabled(changedList, 730 new ComponentName(packageName, WifiSettingsActivity.class.getName()), 731 pm.hasSystemFeature(PackageManager.FEATURE_WIFI), isAdmin) || somethingChanged; 732 733 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 734 Settings.BluetoothSettingsActivity.class.getName()), 735 pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH), isAdmin) 736 || somethingChanged; 737 738 // Enable DataUsageSummaryActivity if the data plan feature flag is turned on otherwise 739 // enable DataPlanUsageSummaryActivity. 740 somethingChanged = setTileEnabled(changedList, 741 new ComponentName(packageName, Settings.DataUsageSummaryActivity.class.getName()), 742 Utils.isBandwidthControlEnabled() /* enabled */, 743 isAdmin) || somethingChanged; 744 745 somethingChanged = setTileEnabled(changedList, 746 new ComponentName(packageName, 747 Settings.ConnectedDeviceDashboardActivity.class.getName()), 748 !UserManager.isDeviceInDemoMode(this) /* enabled */, 749 isAdmin) || somethingChanged; 750 751 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 752 Settings.PowerUsageSummaryActivity.class.getName()), 753 mBatteryPresent, isAdmin) || somethingChanged; 754 755 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 756 Settings.DataUsageSummaryActivity.class.getName()), 757 Utils.isBandwidthControlEnabled(), isAdmin) 758 || somethingChanged; 759 760 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 761 Settings.UserSettingsActivity.class.getName()), 762 UserHandle.MU_ENABLED && UserManager.supportsMultipleUsers() 763 && !Utils.isMonkeyRunning(), isAdmin) 764 || somethingChanged; 765 766 final boolean showDev = DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(this) 767 && !Utils.isMonkeyRunning(); 768 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 769 Settings.DevelopmentSettingsDashboardActivity.class.getName()), 770 showDev, isAdmin) 771 || somethingChanged; 772 773 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 774 Settings.WifiDisplaySettingsActivity.class.getName()), 775 WifiDisplaySettings.isAvailable(this), isAdmin) 776 || somethingChanged; 777 778 if (UserHandle.MU_ENABLED && !isAdmin) { 779 // When on restricted users, disable all extra categories (but only the settings ones). 780 final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories(); 781 synchronized (categories) { 782 for (DashboardCategory category : categories) { 783 final int tileCount = category.getTilesCount(); 784 for (int i = 0; i < tileCount; i++) { 785 final ComponentName component = category.getTile(i) 786 .getIntent().getComponent(); 787 final String name = component.getClassName(); 788 final boolean isEnabledForRestricted = ArrayUtils.contains( 789 SettingsGateway.SETTINGS_FOR_RESTRICTED, name); 790 if (packageName.equals(component.getPackageName()) 791 && !isEnabledForRestricted) { 792 somethingChanged = 793 setTileEnabled(changedList, component, false, isAdmin) 794 || somethingChanged; 795 } 796 } 797 } 798 } 799 } 800 801 // Final step, refresh categories. 802 if (somethingChanged) { 803 Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories " 804 + changedList.toString()); 805 mCategoryMixin.updateCategories(); 806 } else { 807 Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call"); 808 } 809 } 810 811 /** 812 * @return whether or not the enabled state actually changed. 813 */ setTileEnabled(StringBuilder changedList, ComponentName component, boolean enabled, boolean isAdmin)814 private boolean setTileEnabled(StringBuilder changedList, ComponentName component, 815 boolean enabled, boolean isAdmin) { 816 if (UserHandle.MU_ENABLED && !isAdmin && getPackageName().equals(component.getPackageName()) 817 && !ArrayUtils.contains(SettingsGateway.SETTINGS_FOR_RESTRICTED, 818 component.getClassName())) { 819 enabled = false; 820 } 821 boolean changed = setTileEnabled(component, enabled); 822 if (changed) { 823 changedList.append(component.toShortString()).append(","); 824 } 825 return changed; 826 } 827 getMetaData()828 private void getMetaData() { 829 try { 830 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(), 831 PackageManager.GET_META_DATA); 832 if (ai == null || ai.metaData == null) return; 833 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS); 834 mHighlightMenuKey = ai.metaData.getString(META_DATA_KEY_HIGHLIGHT_MENU_KEY); 835 } catch (NameNotFoundException nnfe) { 836 // No recovery 837 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString()); 838 } 839 } 840 841 // give subclasses access to the Next button hasNextButton()842 public boolean hasNextButton() { 843 return mNextButton != null; 844 } 845 getNextButton()846 public Button getNextButton() { 847 return mNextButton; 848 } 849 } 850