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.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO; 20 21 import android.app.ActionBar; 22 import android.app.ActivityManager; 23 import android.app.Fragment; 24 import android.app.FragmentManager; 25 import android.app.FragmentTransaction; 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.SharedPreferences; 32 import android.content.pm.ActivityInfo; 33 import android.content.pm.PackageManager; 34 import android.content.pm.PackageManager.NameNotFoundException; 35 import android.graphics.Bitmap; 36 import android.graphics.Canvas; 37 import android.graphics.drawable.Drawable; 38 import android.os.AsyncTask; 39 import android.os.Bundle; 40 import android.os.UserHandle; 41 import android.os.UserManager; 42 import android.support.annotation.VisibleForTesting; 43 import android.support.v14.preference.PreferenceFragment; 44 import android.support.v4.content.LocalBroadcastManager; 45 import android.support.v7.preference.Preference; 46 import android.support.v7.preference.PreferenceManager; 47 import android.text.TextUtils; 48 import android.transition.TransitionManager; 49 import android.util.FeatureFlagUtils; 50 import android.util.Log; 51 import android.view.View; 52 import android.view.View.OnClickListener; 53 import android.view.ViewGroup; 54 import android.widget.Button; 55 import android.widget.Toolbar; 56 57 import com.android.internal.util.ArrayUtils; 58 import com.android.settings.Settings.WifiSettingsActivity; 59 import com.android.settings.applications.manageapplications.ManageApplications; 60 import com.android.settings.backup.BackupSettingsActivity; 61 import com.android.settings.core.FeatureFlags; 62 import com.android.settings.core.SubSettingLauncher; 63 import com.android.settings.core.gateway.SettingsGateway; 64 import com.android.settings.dashboard.DashboardFeatureProvider; 65 import com.android.settings.dashboard.DashboardSummary; 66 import com.android.settings.overlay.FeatureFactory; 67 import com.android.settings.search.DeviceIndexFeatureProvider; 68 import com.android.settings.wfd.WifiDisplaySettings; 69 import com.android.settings.widget.SwitchBar; 70 import com.android.settingslib.core.instrumentation.Instrumentable; 71 import com.android.settingslib.core.instrumentation.SharedPreferencesLogger; 72 import com.android.settingslib.development.DevelopmentSettingsEnabler; 73 import com.android.settingslib.drawer.DashboardCategory; 74 import com.android.settingslib.drawer.SettingsDrawerActivity; 75 import com.android.settingslib.utils.ThreadUtils; 76 77 import java.util.ArrayList; 78 import java.util.List; 79 80 public class SettingsActivity extends SettingsDrawerActivity 81 implements PreferenceManager.OnPreferenceTreeClickListener, 82 PreferenceFragment.OnPreferenceStartFragmentCallback, 83 ButtonBarHandler, FragmentManager.OnBackStackChangedListener { 84 85 private static final String LOG_TAG = "SettingsActivity"; 86 87 // Constants for state save/restore 88 private static final String SAVE_KEY_CATEGORIES = ":settings:categories"; 89 90 /** 91 * When starting this activity, the invoking Intent can contain this extra 92 * string to specify which fragment should be initially displayed. 93 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity 94 * will call isValidFragment() to confirm that the fragment class name is valid for this 95 * activity. 96 */ 97 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment"; 98 99 /** 100 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 101 * this extra can also be specified to supply a Bundle of arguments to pass 102 * to that fragment when it is instantiated during the initial creation 103 * of the activity. 104 */ 105 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"; 106 107 /** 108 * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS} 109 */ 110 public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 111 112 public static final String BACK_STACK_PREFS = ":settings:prefs"; 113 114 // extras that allow any preference activity to be launched as part of a wizard 115 116 // show Back and Next buttons? takes boolean parameter 117 // Back will then return RESULT_CANCELED and Next RESULT_OK 118 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar"; 119 120 // add a Skip button? 121 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip"; 122 123 // specify custom text for the Back or Next buttons, or cause a button to not appear 124 // at all by setting it to null 125 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text"; 126 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text"; 127 128 /** 129 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 130 * those extra can also be specify to supply the title or title res id to be shown for 131 * that fragment. 132 */ 133 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title"; 134 /** 135 * The package name used to resolve the title resource id. 136 */ 137 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME = 138 ":settings:show_fragment_title_res_package_name"; 139 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RESID = 140 ":settings:show_fragment_title_resid"; 141 public static final String EXTRA_SHOW_FRAGMENT_AS_SHORTCUT = 142 ":settings:show_fragment_as_shortcut"; 143 144 public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING = 145 ":settings:show_fragment_as_subsetting"; 146 147 @Deprecated 148 public static final String EXTRA_HIDE_DRAWER = ":settings:hide_drawer"; 149 150 public static final String META_DATA_KEY_FRAGMENT_CLASS = 151 "com.android.settings.FRAGMENT_CLASS"; 152 153 private static final String EXTRA_UI_OPTIONS = "settings:ui_options"; 154 155 private String mFragmentClass; 156 157 private CharSequence mInitialTitle; 158 private int mInitialTitleResId; 159 160 private BroadcastReceiver mDevelopmentSettingsListener; 161 162 private boolean mBatteryPresent = true; 163 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() { 164 @Override 165 public void onReceive(Context context, Intent intent) { 166 String action = intent.getAction(); 167 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { 168 boolean batteryPresent = Utils.isBatteryPresent(intent); 169 170 if (mBatteryPresent != batteryPresent) { 171 mBatteryPresent = batteryPresent; 172 updateTilesList(); 173 } 174 } 175 } 176 }; 177 178 private SwitchBar mSwitchBar; 179 180 private Button mNextButton; 181 182 private boolean mIsShowingDashboard; 183 184 private ViewGroup mContent; 185 186 // Categories 187 private ArrayList<DashboardCategory> mCategories = new ArrayList<>(); 188 189 private DashboardFeatureProvider mDashboardFeatureProvider; 190 191 public SwitchBar getSwitchBar() { 192 return mSwitchBar; 193 } 194 195 @Override 196 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) { 197 new SubSettingLauncher(this) 198 .setDestination(pref.getFragment()) 199 .setArguments(pref.getExtras()) 200 .setSourceMetricsCategory(caller instanceof Instrumentable 201 ? ((Instrumentable) caller).getMetricsCategory() 202 : Instrumentable.METRICS_CATEGORY_UNKNOWN) 203 .setTitle(-1) 204 .launch(); 205 return true; 206 } 207 208 @Override 209 public boolean onPreferenceTreeClick(Preference preference) { 210 return false; 211 } 212 213 @Override 214 public SharedPreferences getSharedPreferences(String name, int mode) { 215 if (name.equals(getPackageName() + "_preferences")) { 216 return new SharedPreferencesLogger(this, getMetricsTag(), 217 FeatureFactory.getFactory(this).getMetricsFeatureProvider()); 218 } 219 return super.getSharedPreferences(name, mode); 220 } 221 222 private String getMetricsTag() { 223 String tag = getClass().getName(); 224 if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) { 225 tag = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT); 226 } 227 if (tag.startsWith("com.android.settings.")) { 228 tag = tag.replace("com.android.settings.", ""); 229 } 230 return tag; 231 } 232 233 @Override 234 protected void onCreate(Bundle savedState) { 235 super.onCreate(savedState); 236 Log.d(LOG_TAG, "Starting onCreate"); 237 long startTime = System.currentTimeMillis(); 238 239 final FeatureFactory factory = FeatureFactory.getFactory(this); 240 241 mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this); 242 243 // Should happen before any call to getIntent() 244 getMetaData(); 245 246 final Intent intent = getIntent(); 247 if (intent.hasExtra(EXTRA_UI_OPTIONS)) { 248 getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0)); 249 } 250 251 // Getting Intent properties can only be done after the super.onCreate(...) 252 final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); 253 254 final ComponentName cn = intent.getComponent(); 255 final String className = cn.getClassName(); 256 257 mIsShowingDashboard = className.equals(Settings.class.getName()); 258 259 // This is a "Sub Settings" when: 260 // - this is a real SubSettings 261 // - or :settings:show_fragment_as_subsetting is passed to the Intent 262 final boolean isSubSettings = this instanceof SubSettings || 263 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false); 264 265 // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content 266 // insets 267 if (isSubSettings) { 268 setTheme(R.style.Theme_SubSettings); 269 } 270 271 setContentView(mIsShowingDashboard ? 272 R.layout.settings_main_dashboard : R.layout.settings_main_prefs); 273 274 mContent = findViewById(R.id.main_content); 275 276 getFragmentManager().addOnBackStackChangedListener(this); 277 278 if (savedState != null) { 279 // We are restarting from a previous saved state; used that to initialize, instead 280 // of starting fresh. 281 setTitleFromIntent(intent); 282 283 ArrayList<DashboardCategory> categories = 284 savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES); 285 if (categories != null) { 286 mCategories.clear(); 287 mCategories.addAll(categories); 288 setTitleFromBackStack(); 289 } 290 } else { 291 launchSettingFragment(initialFragmentName, isSubSettings, intent); 292 } 293 294 if (mIsShowingDashboard) { 295 findViewById(R.id.search_bar).setVisibility(View.VISIBLE); 296 findViewById(R.id.action_bar).setVisibility(View.GONE); 297 final Toolbar toolbar = findViewById(R.id.search_action_bar); 298 FeatureFactory.getFactory(this).getSearchFeatureProvider() 299 .initSearchToolbar(this, toolbar); 300 setActionBar(toolbar); 301 302 // Please forgive me for what I am about to do. 303 // 304 // Need to make the navigation icon non-clickable so that the entire card is clickable 305 // and goes to the search UI. Also set the background to null so there's no ripple. 306 View navView = toolbar.getNavigationView(); 307 navView.setClickable(false); 308 navView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 309 navView.setBackground(null); 310 } 311 312 ActionBar actionBar = getActionBar(); 313 if (actionBar != null) { 314 boolean deviceProvisioned = Utils.isDeviceProvisioned(this); 315 actionBar.setDisplayHomeAsUpEnabled(deviceProvisioned); 316 actionBar.setHomeButtonEnabled(deviceProvisioned); 317 actionBar.setDisplayShowTitleEnabled(!mIsShowingDashboard); 318 } 319 mSwitchBar = findViewById(R.id.switch_bar); 320 if (mSwitchBar != null) { 321 mSwitchBar.setMetricsTag(getMetricsTag()); 322 } 323 324 // see if we should show Back/Next buttons 325 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) { 326 327 View buttonBar = findViewById(R.id.button_bar); 328 if (buttonBar != null) { 329 buttonBar.setVisibility(View.VISIBLE); 330 331 Button backButton = (Button) findViewById(R.id.back_button); 332 backButton.setOnClickListener(new OnClickListener() { 333 public void onClick(View v) { 334 setResult(RESULT_CANCELED, null); 335 finish(); 336 } 337 }); 338 Button skipButton = (Button) findViewById(R.id.skip_button); 339 skipButton.setOnClickListener(new OnClickListener() { 340 public void onClick(View v) { 341 setResult(RESULT_OK, null); 342 finish(); 343 } 344 }); 345 mNextButton = (Button) findViewById(R.id.next_button); 346 mNextButton.setOnClickListener(new OnClickListener() { 347 public void onClick(View v) { 348 setResult(RESULT_OK, null); 349 finish(); 350 } 351 }); 352 353 // set our various button parameters 354 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) { 355 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT); 356 if (TextUtils.isEmpty(buttonText)) { 357 mNextButton.setVisibility(View.GONE); 358 } else { 359 mNextButton.setText(buttonText); 360 } 361 } 362 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) { 363 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT); 364 if (TextUtils.isEmpty(buttonText)) { 365 backButton.setVisibility(View.GONE); 366 } else { 367 backButton.setText(buttonText); 368 } 369 } 370 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) { 371 skipButton.setVisibility(View.VISIBLE); 372 } 373 } 374 } 375 376 if (DEBUG_TIMING) { 377 Log.d(LOG_TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms"); 378 } 379 } 380 381 @VisibleForTesting 382 void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) { 383 if (!mIsShowingDashboard && initialFragmentName != null) { 384 setTitleFromIntent(intent); 385 386 Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 387 switchToFragment(initialFragmentName, initialArguments, true, false, 388 mInitialTitleResId, mInitialTitle, false); 389 } else { 390 // Show search icon as up affordance if we are displaying the main Dashboard 391 mInitialTitleResId = R.string.dashboard_title; 392 393 switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false, 394 mInitialTitleResId, mInitialTitle, false); 395 } 396 } 397 398 private void setTitleFromIntent(Intent intent) { 399 Log.d(LOG_TAG, "Starting to set activity title"); 400 final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1); 401 if (initialTitleResId > 0) { 402 mInitialTitle = null; 403 mInitialTitleResId = initialTitleResId; 404 405 final String initialTitleResPackageName = intent.getStringExtra( 406 EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME); 407 if (initialTitleResPackageName != null) { 408 try { 409 Context authContext = createPackageContextAsUser(initialTitleResPackageName, 410 0 /* flags */, new UserHandle(UserHandle.myUserId())); 411 mInitialTitle = authContext.getResources().getText(mInitialTitleResId); 412 setTitle(mInitialTitle); 413 mInitialTitleResId = -1; 414 return; 415 } catch (NameNotFoundException e) { 416 Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName); 417 } 418 } else { 419 setTitle(mInitialTitleResId); 420 } 421 } else { 422 mInitialTitleResId = -1; 423 final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE); 424 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle(); 425 setTitle(mInitialTitle); 426 } 427 Log.d(LOG_TAG, "Done setting title"); 428 } 429 430 @Override 431 public void onBackStackChanged() { 432 setTitleFromBackStack(); 433 } 434 435 private void setTitleFromBackStack() { 436 final int count = getFragmentManager().getBackStackEntryCount(); 437 438 if (count == 0) { 439 if (mInitialTitleResId > 0) { 440 setTitle(mInitialTitleResId); 441 } else { 442 setTitle(mInitialTitle); 443 } 444 return; 445 } 446 447 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1); 448 setTitleFromBackStackEntry(bse); 449 } 450 451 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) { 452 final CharSequence title; 453 final int titleRes = bse.getBreadCrumbTitleRes(); 454 if (titleRes > 0) { 455 title = getText(titleRes); 456 } else { 457 title = bse.getBreadCrumbTitle(); 458 } 459 if (title != null) { 460 setTitle(title); 461 } 462 } 463 464 @Override 465 protected void onSaveInstanceState(Bundle outState) { 466 super.onSaveInstanceState(outState); 467 saveState(outState); 468 } 469 470 /** 471 * For testing purposes to avoid crashes from final variables in Activity's onSaveInstantState. 472 */ 473 @VisibleForTesting 474 void saveState(Bundle outState) { 475 if (mCategories.size() > 0) { 476 outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories); 477 } 478 } 479 480 @Override 481 protected void onResume() { 482 super.onResume(); 483 484 mDevelopmentSettingsListener = new BroadcastReceiver() { 485 @Override 486 public void onReceive(Context context, Intent intent) { 487 updateTilesList(); 488 } 489 }; 490 LocalBroadcastManager.getInstance(this).registerReceiver(mDevelopmentSettingsListener, 491 new IntentFilter(DevelopmentSettingsEnabler.DEVELOPMENT_SETTINGS_CHANGED_ACTION)); 492 493 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 494 495 updateTilesList(); 496 updateDeviceIndex(); 497 } 498 499 @Override 500 protected void onPause() { 501 super.onPause(); 502 LocalBroadcastManager.getInstance(this).unregisterReceiver(mDevelopmentSettingsListener); 503 mDevelopmentSettingsListener = null; 504 unregisterReceiver(mBatteryInfoReceiver); 505 } 506 507 @Override 508 public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { 509 final Bitmap icon = getBitmapFromXmlResource(R.drawable.ic_launcher_settings); 510 taskDescription.setIcon(icon); 511 super.setTaskDescription(taskDescription); 512 } 513 514 protected boolean isValidFragment(String fragmentName) { 515 // Almost all fragments are wrapped in this, 516 // except for a few that have their own activities. 517 for (int i = 0; i < SettingsGateway.ENTRY_FRAGMENTS.length; i++) { 518 if (SettingsGateway.ENTRY_FRAGMENTS[i].equals(fragmentName)) return true; 519 } 520 return false; 521 } 522 523 @Override 524 public Intent getIntent() { 525 Intent superIntent = super.getIntent(); 526 String startingFragment = getStartingFragmentClass(superIntent); 527 // This is called from super.onCreate, isMultiPane() is not yet reliable 528 // Do not use onIsHidingHeaders either, which relies itself on this method 529 if (startingFragment != null) { 530 Intent modIntent = new Intent(superIntent); 531 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment); 532 Bundle args = superIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 533 if (args != null) { 534 args = new Bundle(args); 535 } else { 536 args = new Bundle(); 537 } 538 args.putParcelable("intent", superIntent); 539 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 540 return modIntent; 541 } 542 return superIntent; 543 } 544 545 /** 546 * Checks if the component name in the intent is different from the Settings class and 547 * returns the class name to load as a fragment. 548 */ 549 private String getStartingFragmentClass(Intent intent) { 550 if (mFragmentClass != null) return mFragmentClass; 551 552 String intentClass = intent.getComponent().getClassName(); 553 if (intentClass.equals(getClass().getName())) return null; 554 555 if ("com.android.settings.RunningServices".equals(intentClass) 556 || "com.android.settings.applications.StorageUse".equals(intentClass)) { 557 // Old names of manage apps. 558 intentClass = ManageApplications.class.getName(); 559 } 560 561 return intentClass; 562 } 563 564 /** 565 * Called by a preference panel fragment to finish itself. 566 * 567 * @param resultCode Optional result code to send back to the original 568 * launching fragment. 569 * @param resultData Optional result data to send back to the original 570 * launching fragment. 571 */ 572 public void finishPreferencePanel(int resultCode, Intent resultData) { 573 setResult(resultCode, resultData); 574 finish(); 575 } 576 577 /** 578 * Switch to a specific Fragment with taking care of validation, Title and BackStack 579 */ 580 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate, 581 boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) { 582 Log.d(LOG_TAG, "Switching to fragment " + fragmentName); 583 if (validate && !isValidFragment(fragmentName)) { 584 throw new IllegalArgumentException("Invalid fragment for this activity: " 585 + fragmentName); 586 } 587 Fragment f = Fragment.instantiate(this, fragmentName, args); 588 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 589 transaction.replace(R.id.main_content, f); 590 if (withTransition) { 591 TransitionManager.beginDelayedTransition(mContent); 592 } 593 if (addToBackStack) { 594 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS); 595 } 596 if (titleResId > 0) { 597 transaction.setBreadCrumbTitle(titleResId); 598 } else if (title != null) { 599 transaction.setBreadCrumbTitle(title); 600 } 601 transaction.commitAllowingStateLoss(); 602 getFragmentManager().executePendingTransactions(); 603 Log.d(LOG_TAG, "Executed frag manager pendingTransactions"); 604 return f; 605 } 606 607 private void updateTilesList() { 608 // Generally the items that are will be changing from these updates will 609 // not be in the top list of tiles, so run it in the background and the 610 // SettingsDrawerActivity will pick up on the updates automatically. 611 AsyncTask.execute(new Runnable() { 612 @Override 613 public void run() { 614 doUpdateTilesList(); 615 } 616 }); 617 } 618 619 private void updateDeviceIndex() { 620 DeviceIndexFeatureProvider indexProvider = FeatureFactory.getFactory( 621 this).getDeviceIndexFeatureProvider(); 622 623 ThreadUtils.postOnBackgroundThread( 624 () -> indexProvider.updateIndex(SettingsActivity.this, false /* force */)); 625 } 626 627 private void doUpdateTilesList() { 628 PackageManager pm = getPackageManager(); 629 final UserManager um = UserManager.get(this); 630 final boolean isAdmin = um.isAdminUser(); 631 final FeatureFactory featureFactory = FeatureFactory.getFactory(this); 632 boolean somethingChanged = false; 633 final String packageName = getPackageName(); 634 final StringBuilder changedList = new StringBuilder(); 635 somethingChanged = setTileEnabled(changedList, 636 new ComponentName(packageName, WifiSettingsActivity.class.getName()), 637 pm.hasSystemFeature(PackageManager.FEATURE_WIFI), isAdmin) || somethingChanged; 638 639 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 640 Settings.BluetoothSettingsActivity.class.getName()), 641 pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH), isAdmin) 642 || somethingChanged; 643 644 645 // Enable DataUsageSummaryActivity if the data plan feature flag is turned on otherwise 646 // enable DataPlanUsageSummaryActivity. 647 somethingChanged = setTileEnabled(changedList, 648 new ComponentName(packageName, Settings.DataUsageSummaryActivity.class.getName()), 649 Utils.isBandwidthControlEnabled() /* enabled */, 650 isAdmin) || somethingChanged; 651 652 somethingChanged = setTileEnabled(changedList, 653 new ComponentName(packageName, 654 Settings.ConnectedDeviceDashboardActivity.class.getName()), 655 !UserManager.isDeviceInDemoMode(this) /* enabled */, 656 isAdmin) || somethingChanged; 657 658 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 659 Settings.SimSettingsActivity.class.getName()), 660 Utils.showSimCardTile(this), isAdmin) 661 || somethingChanged; 662 663 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 664 Settings.PowerUsageSummaryActivity.class.getName()), 665 mBatteryPresent, isAdmin) || somethingChanged; 666 667 final boolean isDataUsageSettingsV2Enabled = 668 FeatureFlagUtils.isEnabled(this, FeatureFlags.DATA_USAGE_SETTINGS_V2); 669 // Enable new data usage page if v2 enabled 670 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 671 Settings.DataUsageSummaryActivity.class.getName()), 672 Utils.isBandwidthControlEnabled() && isDataUsageSettingsV2Enabled, isAdmin) 673 || somethingChanged; 674 // Enable legacy data usage page if v2 disabled 675 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 676 Settings.DataUsageSummaryLegacyActivity.class.getName()), 677 Utils.isBandwidthControlEnabled() && !isDataUsageSettingsV2Enabled, isAdmin) 678 || somethingChanged; 679 680 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 681 Settings.UserSettingsActivity.class.getName()), 682 UserHandle.MU_ENABLED && UserManager.supportsMultipleUsers() 683 && !Utils.isMonkeyRunning(), isAdmin) 684 || somethingChanged; 685 686 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 687 Settings.NetworkDashboardActivity.class.getName()), 688 !UserManager.isDeviceInDemoMode(this), isAdmin) 689 || somethingChanged; 690 691 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 692 Settings.DateTimeSettingsActivity.class.getName()), 693 !UserManager.isDeviceInDemoMode(this), isAdmin) 694 || somethingChanged; 695 696 final boolean showDev = DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(this) 697 && !Utils.isMonkeyRunning(); 698 final boolean isAdminOrDemo = um.isAdminUser() || um.isDemoUser(); 699 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 700 Settings.DevelopmentSettingsDashboardActivity.class.getName()), 701 showDev, isAdminOrDemo) 702 || somethingChanged; 703 704 // Enable/disable backup settings depending on whether the user is admin. 705 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 706 BackupSettingsActivity.class.getName()), true, isAdmin) 707 || somethingChanged; 708 709 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 710 Settings.WifiDisplaySettingsActivity.class.getName()), 711 WifiDisplaySettings.isAvailable(this), isAdmin) 712 || somethingChanged; 713 714 // Enable/disable the Me Card page. 715 final boolean aboutPhoneV2Enabled = featureFactory 716 .getAccountFeatureProvider() 717 .isAboutPhoneV2Enabled(this); 718 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 719 Settings.MyDeviceInfoActivity.class.getName()), 720 aboutPhoneV2Enabled, isAdmin) 721 || somethingChanged; 722 somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, 723 Settings.DeviceInfoSettingsActivity.class.getName()), 724 !aboutPhoneV2Enabled, isAdmin) 725 || somethingChanged; 726 727 if (UserHandle.MU_ENABLED && !isAdmin) { 728 729 // When on restricted users, disable all extra categories (but only the settings ones). 730 final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories(); 731 synchronized (categories) { 732 for (DashboardCategory category : categories) { 733 final int tileCount = category.getTilesCount(); 734 for (int i = 0; i < tileCount; i++) { 735 final ComponentName component = category.getTile(i).intent.getComponent(); 736 final String name = component.getClassName(); 737 final boolean isEnabledForRestricted = ArrayUtils.contains( 738 SettingsGateway.SETTINGS_FOR_RESTRICTED, name) || (isAdminOrDemo 739 && Settings.DevelopmentSettingsDashboardActivity.class.getName() 740 .equals(name)); 741 if (packageName.equals(component.getPackageName()) 742 && !isEnabledForRestricted) { 743 somethingChanged = 744 setTileEnabled(changedList, component, false, isAdmin) 745 || somethingChanged; 746 } 747 } 748 } 749 } 750 } 751 752 // Final step, refresh categories. 753 if (somethingChanged) { 754 Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories " 755 + changedList.toString()); 756 updateCategories(); 757 } else { 758 Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call"); 759 } 760 } 761 762 /** 763 * @return whether or not the enabled state actually changed. 764 */ 765 private boolean setTileEnabled(StringBuilder changedList, ComponentName component, 766 boolean enabled, boolean isAdmin) { 767 if (UserHandle.MU_ENABLED && !isAdmin && getPackageName().equals(component.getPackageName()) 768 && !ArrayUtils.contains(SettingsGateway.SETTINGS_FOR_RESTRICTED, 769 component.getClassName())) { 770 enabled = false; 771 } 772 boolean changed = setTileEnabled(component, enabled); 773 if (changed) { 774 changedList.append(component.toShortString()).append(","); 775 } 776 return changed; 777 } 778 779 private void getMetaData() { 780 try { 781 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(), 782 PackageManager.GET_META_DATA); 783 if (ai == null || ai.metaData == null) return; 784 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS); 785 } catch (NameNotFoundException nnfe) { 786 // No recovery 787 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString()); 788 } 789 } 790 791 // give subclasses access to the Next button 792 public boolean hasNextButton() { 793 return mNextButton != null; 794 } 795 796 public Button getNextButton() { 797 return mNextButton; 798 } 799 800 @VisibleForTesting 801 Bitmap getBitmapFromXmlResource(int drawableRes) { 802 Drawable drawable = getResources().getDrawable(drawableRes, getTheme()); 803 Canvas canvas = new Canvas(); 804 Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), 805 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 806 canvas.setBitmap(bitmap); 807 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 808 drawable.draw(canvas); 809 810 return bitmap; 811 } 812 } 813