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