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