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 android.app.ActionBar; 20 import android.app.ActivityManager; 21 import android.app.Fragment; 22 import android.app.FragmentManager; 23 import android.app.FragmentTransaction; 24 import android.content.BroadcastReceiver; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.content.SharedPreferences; 30 import android.content.pm.ActivityInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.PackageManager.NameNotFoundException; 33 import android.graphics.Bitmap; 34 import android.graphics.Canvas; 35 import android.graphics.drawable.Drawable; 36 import android.os.AsyncTask; 37 import android.os.Bundle; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.support.annotation.VisibleForTesting; 41 import android.support.v14.preference.PreferenceFragment; 42 import android.support.v7.preference.Preference; 43 import android.support.v7.preference.PreferenceManager; 44 import android.text.TextUtils; 45 import android.transition.TransitionManager; 46 import android.util.Log; 47 import android.view.View; 48 import android.view.View.OnClickListener; 49 import android.view.ViewGroup; 50 import android.widget.Button; 51 import android.widget.Toolbar; 52 import com.android.internal.util.ArrayUtils; 53 import com.android.settings.Settings.WifiSettingsActivity; 54 import com.android.settings.backup.BackupSettingsActivity; 55 import com.android.settings.core.gateway.SettingsGateway; 56 import com.android.settings.core.instrumentation.MetricsFeatureProvider; 57 import com.android.settings.core.instrumentation.SharedPreferencesLogger; 58 import com.android.settings.dashboard.DashboardFeatureProvider; 59 import com.android.settings.dashboard.DashboardSummary; 60 import com.android.settings.development.DevelopmentSettings; 61 import com.android.settings.overlay.FeatureFactory; 62 import com.android.settings.search.SearchActivity; 63 import com.android.settings.wfd.WifiDisplaySettings; 64 import com.android.settings.widget.SwitchBar; 65 import com.android.settingslib.drawer.DashboardCategory; 66 import com.android.settingslib.drawer.SettingsDrawerActivity; 67 import java.util.ArrayList; 68 import java.util.List; 69 import java.util.Set; 70 71 public class SettingsActivity extends SettingsDrawerActivity 72 implements PreferenceManager.OnPreferenceTreeClickListener, 73 PreferenceFragment.OnPreferenceStartFragmentCallback, 74 ButtonBarHandler, FragmentManager.OnBackStackChangedListener, OnClickListener { 75 76 private static final String LOG_TAG = "Settings"; 77 78 // Constants for state save/restore 79 private static final String SAVE_KEY_CATEGORIES = ":settings:categories"; 80 @VisibleForTesting 81 static final String SAVE_KEY_SHOW_HOME_AS_UP = ":settings:show_home_as_up"; 82 83 /** 84 * When starting this activity, the invoking Intent can contain this extra 85 * string to specify which fragment should be initially displayed. 86 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity 87 * will call isValidFragment() to confirm that the fragment class name is valid for this 88 * activity. 89 */ 90 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment"; 91 92 /** 93 * The metrics category constant for logging source when a setting fragment is opened. 94 */ 95 public static final String EXTRA_SOURCE_METRICS_CATEGORY = ":settings:source_metrics"; 96 97 /** 98 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 99 * this extra can also be specified to supply a Bundle of arguments to pass 100 * to that fragment when it is instantiated during the initial creation 101 * of the activity. 102 */ 103 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"; 104 105 /** 106 * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS} 107 */ 108 public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 109 110 public static final String BACK_STACK_PREFS = ":settings:prefs"; 111 112 // extras that allow any preference activity to be launched as part of a wizard 113 114 // show Back and Next buttons? takes boolean parameter 115 // Back will then return RESULT_CANCELED and Next RESULT_OK 116 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar"; 117 118 // add a Skip button? 119 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip"; 120 121 // specify custom text for the Back or Next buttons, or cause a button to not appear 122 // at all by setting it to null 123 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text"; 124 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text"; 125 126 /** 127 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 128 * those extra can also be specify to supply the title or title res id to be shown for 129 * that fragment. 130 */ 131 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title"; 132 /** 133 * The package name used to resolve the title resource id. 134 */ 135 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME = 136 ":settings:show_fragment_title_res_package_name"; 137 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RESID = 138 ":settings:show_fragment_title_resid"; 139 public static final String EXTRA_SHOW_FRAGMENT_AS_SHORTCUT = 140 ":settings:show_fragment_as_shortcut"; 141 142 public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING = 143 ":settings:show_fragment_as_subsetting"; 144 145 @Deprecated 146 public static final String EXTRA_HIDE_DRAWER = ":settings:hide_drawer"; 147 148 public static final String META_DATA_KEY_FRAGMENT_CLASS = 149 "com.android.settings.FRAGMENT_CLASS"; 150 151 private static final String EXTRA_UI_OPTIONS = "settings:ui_options"; 152 153 private static final int REQUEST_SUGGESTION = 42; 154 155 private String mFragmentClass; 156 157 private CharSequence mInitialTitle; 158 private int mInitialTitleResId; 159 160 private static final String[] LIKE_SHORTCUT_INTENT_ACTION_ARRAY = { 161 "android.settings.APPLICATION_DETAILS_SETTINGS" 162 }; 163 164 private SharedPreferences mDevelopmentPreferences; 165 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener; 166 167 private boolean mBatteryPresent = true; 168 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() { 169 @Override 170 public void onReceive(Context context, Intent intent) { 171 String action = intent.getAction(); 172 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { 173 boolean batteryPresent = Utils.isBatteryPresent(intent); 174 175 if (mBatteryPresent != batteryPresent) { 176 mBatteryPresent = batteryPresent; 177 updateTilesList(); 178 } 179 } 180 } 181 }; 182 183 private SwitchBar mSwitchBar; 184 185 private Button mNextButton; 186 187 @VisibleForTesting 188 boolean mDisplayHomeAsUpEnabled; 189 190 private boolean mIsShowingDashboard; 191 private boolean mIsShortcut; 192 193 private ViewGroup mContent; 194 195 private MetricsFeatureProvider mMetricsFeatureProvider; 196 197 // Categories 198 private ArrayList<DashboardCategory> mCategories = new ArrayList<>(); 199 200 private DashboardFeatureProvider mDashboardFeatureProvider; 201 private ComponentName mCurrentSuggestion; 202 getSwitchBar()203 public SwitchBar getSwitchBar() { 204 return mSwitchBar; 205 } 206 207 @Override onPreferenceStartFragment(PreferenceFragment caller, Preference pref)208 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) { 209 startPreferencePanel(caller, pref.getFragment(), pref.getExtras(), -1, pref.getTitle(), 210 null, 0); 211 return true; 212 } 213 214 @Override onPreferenceTreeClick(Preference preference)215 public boolean onPreferenceTreeClick(Preference preference) { 216 return false; 217 } 218 219 @Override getSharedPreferences(String name, int mode)220 public SharedPreferences getSharedPreferences(String name, int mode) { 221 if (name.equals(getPackageName() + "_preferences")) { 222 return new SharedPreferencesLogger(this, getMetricsTag()); 223 } 224 return super.getSharedPreferences(name, mode); 225 } 226 getMetricsTag()227 private String getMetricsTag() { 228 String tag = getClass().getName(); 229 if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) { 230 tag = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT); 231 } 232 if (tag.startsWith("com.android.settings.")) { 233 tag = tag.replace("com.android.settings.", ""); 234 } 235 return tag; 236 } 237 isShortCutIntent(final Intent intent)238 private static boolean isShortCutIntent(final Intent intent) { 239 Set<String> categories = intent.getCategories(); 240 return (categories != null) && categories.contains("com.android.settings.SHORTCUT"); 241 } 242 isLikeShortCutIntent(final Intent intent)243 private static boolean isLikeShortCutIntent(final Intent intent) { 244 String action = intent.getAction(); 245 if (action == null) { 246 return false; 247 } 248 for (int i = 0; i < LIKE_SHORTCUT_INTENT_ACTION_ARRAY.length; i++) { 249 if (LIKE_SHORTCUT_INTENT_ACTION_ARRAY[i].equals(action)) return true; 250 } 251 return false; 252 } 253 254 @Override onCreate(Bundle savedState)255 protected void onCreate(Bundle savedState) { 256 super.onCreate(savedState); 257 258 if (isLockTaskModePinned() && !isSettingsRunOnTop() && !isLaunchableInTaskModePinned()) { 259 Log.w(LOG_TAG, "Devices lock task mode pinned."); 260 finish(); 261 } 262 263 long startTime = System.currentTimeMillis(); 264 265 final FeatureFactory factory = FeatureFactory.getFactory(this); 266 267 mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this); 268 mMetricsFeatureProvider = factory.getMetricsFeatureProvider(); 269 270 // Should happen before any call to getIntent() 271 getMetaData(); 272 273 final Intent intent = getIntent(); 274 if (intent.hasExtra(EXTRA_UI_OPTIONS)) { 275 getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0)); 276 } 277 278 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE, 279 Context.MODE_PRIVATE); 280 281 // Getting Intent properties can only be done after the super.onCreate(...) 282 final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); 283 284 mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) || 285 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false); 286 287 final ComponentName cn = intent.getComponent(); 288 final String className = cn.getClassName(); 289 290 mIsShowingDashboard = className.equals(Settings.class.getName()); 291 292 // This is a "Sub Settings" when: 293 // - this is a real SubSettings 294 // - or :settings:show_fragment_as_subsetting is passed to the Intent 295 final boolean isSubSettings = this instanceof SubSettings || 296 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false); 297 298 // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content 299 // insets 300 if (isSubSettings) { 301 setTheme(R.style.Theme_SubSettings); 302 } 303 304 setContentView(mIsShowingDashboard ? 305 R.layout.settings_main_dashboard : R.layout.settings_main_prefs); 306 307 mContent = findViewById(R.id.main_content); 308 309 getFragmentManager().addOnBackStackChangedListener(this); 310 311 if (savedState != null) { 312 // We are restarting from a previous saved state; used that to initialize, instead 313 // of starting fresh. 314 setTitleFromIntent(intent); 315 316 ArrayList<DashboardCategory> categories = 317 savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES); 318 if (categories != null) { 319 mCategories.clear(); 320 mCategories.addAll(categories); 321 setTitleFromBackStack(); 322 } 323 324 mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP); 325 326 } else { 327 launchSettingFragment(initialFragmentName, isSubSettings, intent); 328 } 329 330 if (mIsShowingDashboard) { 331 setSearchBarVisibility(); 332 findViewById(R.id.action_bar).setVisibility(View.GONE); 333 Toolbar toolbar = findViewById(R.id.search_action_bar); 334 toolbar.setOnClickListener(this); 335 setActionBar(toolbar); 336 337 // Please forgive me for what I am about to do. 338 // 339 // Need to make the navigation icon non-clickable so that the entire card is clickable 340 // and goes to the search UI. Also set the background to null so there's no ripple. 341 View navView = toolbar.getNavigationView(); 342 navView.setClickable(false); 343 navView.setBackground(null); 344 } 345 346 ActionBar actionBar = getActionBar(); 347 if (actionBar != null) { 348 actionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled); 349 actionBar.setHomeButtonEnabled(mDisplayHomeAsUpEnabled); 350 } 351 mSwitchBar = findViewById(R.id.switch_bar); 352 if (mSwitchBar != null) { 353 mSwitchBar.setMetricsTag(getMetricsTag()); 354 } 355 356 // see if we should show Back/Next buttons 357 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) { 358 359 View buttonBar = findViewById(R.id.button_bar); 360 if (buttonBar != null) { 361 buttonBar.setVisibility(View.VISIBLE); 362 363 Button backButton = (Button)findViewById(R.id.back_button); 364 backButton.setOnClickListener(new OnClickListener() { 365 public void onClick(View v) { 366 setResult(RESULT_CANCELED, null); 367 finish(); 368 } 369 }); 370 Button skipButton = (Button)findViewById(R.id.skip_button); 371 skipButton.setOnClickListener(new OnClickListener() { 372 public void onClick(View v) { 373 setResult(RESULT_OK, null); 374 finish(); 375 } 376 }); 377 mNextButton = (Button)findViewById(R.id.next_button); 378 mNextButton.setOnClickListener(new OnClickListener() { 379 public void onClick(View v) { 380 setResult(RESULT_OK, null); 381 finish(); 382 } 383 }); 384 385 // set our various button parameters 386 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) { 387 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT); 388 if (TextUtils.isEmpty(buttonText)) { 389 mNextButton.setVisibility(View.GONE); 390 } 391 else { 392 mNextButton.setText(buttonText); 393 } 394 } 395 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) { 396 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT); 397 if (TextUtils.isEmpty(buttonText)) { 398 backButton.setVisibility(View.GONE); 399 } 400 else { 401 backButton.setText(buttonText); 402 } 403 } 404 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) { 405 skipButton.setVisibility(View.VISIBLE); 406 } 407 } 408 } 409 410 if (DEBUG_TIMING) { 411 Log.d(LOG_TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms"); 412 } 413 } 414 415 @VisibleForTesting setSearchBarVisibility()416 void setSearchBarVisibility() { 417 findViewById(R.id.search_bar).setVisibility( 418 Utils.isDeviceProvisioned(this) ? View.VISIBLE : View.INVISIBLE); 419 } 420 421 @VisibleForTesting launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent)422 void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) { 423 if (!mIsShowingDashboard && initialFragmentName != null) { 424 // UP will be shown only if it is a sub settings 425 if (mIsShortcut) { 426 mDisplayHomeAsUpEnabled = isSubSettings; 427 } else if (isSubSettings) { 428 mDisplayHomeAsUpEnabled = true; 429 } else { 430 mDisplayHomeAsUpEnabled = false; 431 } 432 setTitleFromIntent(intent); 433 434 Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 435 switchToFragment(initialFragmentName, initialArguments, true, false, 436 mInitialTitleResId, mInitialTitle, false); 437 } else { 438 // Show search icon as up affordance if we are displaying the main Dashboard 439 mDisplayHomeAsUpEnabled = true; 440 mInitialTitleResId = R.string.dashboard_title; 441 442 switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false, 443 mInitialTitleResId, mInitialTitle, false); 444 } 445 } 446 setTitleFromIntent(Intent intent)447 private void setTitleFromIntent(Intent intent) { 448 final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1); 449 if (initialTitleResId > 0) { 450 mInitialTitle = null; 451 mInitialTitleResId = initialTitleResId; 452 453 final String initialTitleResPackageName = intent.getStringExtra( 454 EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME); 455 if (initialTitleResPackageName != null) { 456 try { 457 Context authContext = createPackageContextAsUser(initialTitleResPackageName, 458 0 /* flags */, new UserHandle(UserHandle.myUserId())); 459 mInitialTitle = authContext.getResources().getText(mInitialTitleResId); 460 setTitle(mInitialTitle); 461 mInitialTitleResId = -1; 462 return; 463 } catch (NameNotFoundException e) { 464 Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName); 465 } 466 } else { 467 setTitle(mInitialTitleResId); 468 } 469 } else { 470 mInitialTitleResId = -1; 471 final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE); 472 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle(); 473 setTitle(mInitialTitle); 474 } 475 } 476 477 @Override onBackStackChanged()478 public void onBackStackChanged() { 479 setTitleFromBackStack(); 480 } 481 setTitleFromBackStack()482 private void setTitleFromBackStack() { 483 final int count = getFragmentManager().getBackStackEntryCount(); 484 485 if (count == 0) { 486 if (mInitialTitleResId > 0) { 487 setTitle(mInitialTitleResId); 488 } else { 489 setTitle(mInitialTitle); 490 } 491 return; 492 } 493 494 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1); 495 setTitleFromBackStackEntry(bse); 496 } 497 setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse)498 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) { 499 final CharSequence title; 500 final int titleRes = bse.getBreadCrumbTitleRes(); 501 if (titleRes > 0) { 502 title = getText(titleRes); 503 } else { 504 title = bse.getBreadCrumbTitle(); 505 } 506 if (title != null) { 507 setTitle(title); 508 } 509 } 510 511 @Override onSaveInstanceState(Bundle outState)512 protected void onSaveInstanceState(Bundle outState) { 513 super.onSaveInstanceState(outState); 514 saveState(outState); 515 } 516 517 /** 518 * For testing purposes to avoid crashes from final variables in Activity's onSaveInstantState. 519 */ 520 @VisibleForTesting saveState(Bundle outState)521 void saveState(Bundle outState) { 522 if (mCategories.size() > 0) { 523 outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories); 524 } 525 526 outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled); 527 } 528 529 @Override onRestoreInstanceState(Bundle savedInstanceState)530 protected void onRestoreInstanceState(Bundle savedInstanceState) { 531 super.onRestoreInstanceState(savedInstanceState); 532 533 mDisplayHomeAsUpEnabled = savedInstanceState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP); 534 } 535 536 @Override onResume()537 protected void onResume() { 538 super.onResume(); 539 540 mDevelopmentPreferencesListener = (sharedPreferences, key) -> updateTilesList(); 541 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener( 542 mDevelopmentPreferencesListener); 543 544 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 545 546 updateTilesList(); 547 } 548 549 @Override onPause()550 protected void onPause() { 551 super.onPause(); 552 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener( 553 mDevelopmentPreferencesListener); 554 mDevelopmentPreferencesListener = null; 555 unregisterReceiver(mBatteryInfoReceiver); 556 } 557 558 @Override setTaskDescription(ActivityManager.TaskDescription taskDescription)559 public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { 560 final Bitmap icon = getBitmapFromXmlResource(R.drawable.ic_launcher_settings); 561 taskDescription.setIcon(icon); 562 super.setTaskDescription(taskDescription); 563 } 564 isValidFragment(String fragmentName)565 protected boolean isValidFragment(String fragmentName) { 566 // Almost all fragments are wrapped in this, 567 // except for a few that have their own activities. 568 for (int i = 0; i < SettingsGateway.ENTRY_FRAGMENTS.length; i++) { 569 if (SettingsGateway.ENTRY_FRAGMENTS[i].equals(fragmentName)) return true; 570 } 571 return false; 572 } 573 574 @Override getIntent()575 public Intent getIntent() { 576 Intent superIntent = super.getIntent(); 577 String startingFragment = getStartingFragmentClass(superIntent); 578 // This is called from super.onCreate, isMultiPane() is not yet reliable 579 // Do not use onIsHidingHeaders either, which relies itself on this method 580 if (startingFragment != null) { 581 Intent modIntent = new Intent(superIntent); 582 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment); 583 Bundle args = superIntent.getExtras(); 584 if (args != null) { 585 args = new Bundle(args); 586 } else { 587 args = new Bundle(); 588 } 589 args.putParcelable("intent", superIntent); 590 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 591 return modIntent; 592 } 593 return superIntent; 594 } 595 596 /** 597 * Checks if the component name in the intent is different from the Settings class and 598 * returns the class name to load as a fragment. 599 */ getStartingFragmentClass(Intent intent)600 private String getStartingFragmentClass(Intent intent) { 601 if (mFragmentClass != null) return mFragmentClass; 602 603 String intentClass = intent.getComponent().getClassName(); 604 if (intentClass.equals(getClass().getName())) return null; 605 606 if ("com.android.settings.ManageApplications".equals(intentClass) 607 || "com.android.settings.RunningServices".equals(intentClass) 608 || "com.android.settings.applications.StorageUse".equals(intentClass)) { 609 // Old names of manage apps. 610 intentClass = com.android.settings.applications.ManageApplications.class.getName(); 611 } 612 613 return intentClass; 614 } 615 616 /** 617 * Start a new fragment containing a preference panel. If the preferences 618 * are being displayed in multi-pane mode, the given fragment class will 619 * be instantiated and placed in the appropriate pane. If running in 620 * single-pane mode, a new activity will be launched in which to show the 621 * fragment. 622 * 623 * @param fragmentClass Full name of the class implementing the fragment. 624 * @param args Any desired arguments to supply to the fragment. 625 * @param titleRes Optional resource identifier of the title of this 626 * fragment. 627 * @param titleText Optional text of the title of this fragment. 628 * @param resultTo Optional fragment that result data should be sent to. 629 * If non-null, resultTo.onActivityResult() will be called when this 630 * preference panel is done. The launched panel must use 631 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done. 632 * @param resultRequestCode If resultTo is non-null, this is the caller's 633 * request code to be received with the result. 634 */ startPreferencePanel(Fragment caller, String fragmentClass, Bundle args, int titleRes, CharSequence titleText, Fragment resultTo, int resultRequestCode)635 public void startPreferencePanel(Fragment caller, String fragmentClass, Bundle args, 636 int titleRes, CharSequence titleText, Fragment resultTo, int resultRequestCode) { 637 String title = null; 638 if (titleRes < 0) { 639 if (titleText != null) { 640 title = titleText.toString(); 641 } else { 642 // There not much we can do in that case 643 title = ""; 644 } 645 } 646 Utils.startWithFragment(this, fragmentClass, args, resultTo, resultRequestCode, 647 titleRes, title, mIsShortcut, mMetricsFeatureProvider.getMetricsCategory(caller)); 648 } 649 650 /** 651 * Start a new fragment in a new activity containing a preference panel for a given user. If the 652 * preferences are being displayed in multi-pane mode, the given fragment class will be 653 * instantiated and placed in the appropriate pane. If running in single-pane mode, a new 654 * activity will be launched in which to show the fragment. 655 * 656 * @param fragmentClass Full name of the class implementing the fragment. 657 * @param args Any desired arguments to supply to the fragment. 658 * @param titleRes Optional resource identifier of the title of this fragment. 659 * @param titleText Optional text of the title of this fragment. 660 * @param userHandle The user for which the panel has to be started. 661 */ startPreferencePanelAsUser(Fragment caller, String fragmentClass, Bundle args, int titleRes, CharSequence titleText, UserHandle userHandle)662 public void startPreferencePanelAsUser(Fragment caller, String fragmentClass, 663 Bundle args, int titleRes, CharSequence titleText, UserHandle userHandle) { 664 // This is a workaround. 665 // 666 // Calling startWithFragmentAsUser() without specifying FLAG_ACTIVITY_NEW_TASK to the intent 667 // starting the fragment could cause a native stack corruption. See b/17523189. However, 668 // adding that flag and start the preference panel with the same UserHandler will make it 669 // impossible to use back button to return to the previous screen. See b/20042570. 670 // 671 // We work around this issue by adding FLAG_ACTIVITY_NEW_TASK to the intent, while doing 672 // another check here to call startPreferencePanel() instead of startWithFragmentAsUser() 673 // when we're calling it as the same user. 674 if (userHandle.getIdentifier() == UserHandle.myUserId()) { 675 startPreferencePanel(caller, fragmentClass, args, titleRes, titleText, null, 0); 676 } else { 677 String title = null; 678 if (titleRes < 0) { 679 if (titleText != null) { 680 title = titleText.toString(); 681 } else { 682 // There not much we can do in that case 683 title = ""; 684 } 685 } 686 Utils.startWithFragmentAsUser(this, fragmentClass, args, titleRes, title, 687 mIsShortcut, mMetricsFeatureProvider.getMetricsCategory(caller), userHandle); 688 } 689 } 690 691 /** 692 * Called by a preference panel fragment to finish itself. 693 * 694 * @param caller The fragment that is asking to be finished. 695 * @param resultCode Optional result code to send back to the original 696 * launching fragment. 697 * @param resultData Optional result data to send back to the original 698 * launching fragment. 699 */ finishPreferencePanel(Fragment caller, int resultCode, Intent resultData)700 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) { 701 setResult(resultCode, resultData); 702 finish(); 703 } 704 705 /** 706 * Start a new fragment. 707 * 708 * @param fragment The fragment to start 709 * @param push If true, the current fragment will be pushed onto the back stack. If false, 710 * the current fragment will be replaced. 711 */ startPreferenceFragment(Fragment fragment, boolean push)712 public void startPreferenceFragment(Fragment fragment, boolean push) { 713 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 714 transaction.replace(R.id.main_content, fragment); 715 if (push) { 716 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 717 transaction.addToBackStack(BACK_STACK_PREFS); 718 } else { 719 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 720 } 721 transaction.commitAllowingStateLoss(); 722 } 723 724 /** 725 * Switch to a specific Fragment with taking care of validation, Title and BackStack 726 */ switchToFragment(String fragmentName, Bundle args, boolean validate, boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition)727 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate, 728 boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) { 729 if (validate && !isValidFragment(fragmentName)) { 730 throw new IllegalArgumentException("Invalid fragment for this activity: " 731 + fragmentName); 732 } 733 Fragment f = Fragment.instantiate(this, fragmentName, args); 734 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 735 transaction.replace(R.id.main_content, f); 736 if (withTransition) { 737 TransitionManager.beginDelayedTransition(mContent); 738 } 739 if (addToBackStack) { 740 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS); 741 } 742 if (titleResId > 0) { 743 transaction.setBreadCrumbTitle(titleResId); 744 } else if (title != null) { 745 transaction.setBreadCrumbTitle(title); 746 } 747 transaction.commitAllowingStateLoss(); 748 getFragmentManager().executePendingTransactions(); 749 return f; 750 } 751 updateTilesList()752 private void updateTilesList() { 753 // Generally the items that are will be changing from these updates will 754 // not be in the top list of tiles, so run it in the background and the 755 // SettingsDrawerActivity will pick up on the updates automatically. 756 AsyncTask.execute(new Runnable() { 757 @Override 758 public void run() { 759 doUpdateTilesList(); 760 } 761 }); 762 } 763 doUpdateTilesList()764 private void doUpdateTilesList() { 765 PackageManager pm = getPackageManager(); 766 final UserManager um = UserManager.get(this); 767 final boolean isAdmin = um.isAdminUser(); 768 boolean somethingChanged = false; 769 String packageName = getPackageName(); 770 somethingChanged = setTileEnabled( 771 new ComponentName(packageName, WifiSettingsActivity.class.getName()), 772 pm.hasSystemFeature(PackageManager.FEATURE_WIFI), isAdmin) || somethingChanged; 773 774 somethingChanged = setTileEnabled(new ComponentName(packageName, 775 Settings.BluetoothSettingsActivity.class.getName()), 776 pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH), isAdmin) 777 || somethingChanged; 778 779 boolean isDataPlanFeatureEnabled = FeatureFactory.getFactory(this) 780 .getDataPlanFeatureProvider() 781 .isEnabled(); 782 783 // When the data plan feature flag is turned on we disable DataUsageSummaryActivity 784 // and enable DataPlanUsageSummaryActivity. When the feature flag is turned off we do the 785 // reverse. 786 787 // Disable DataUsageSummaryActivity if the data plan feature flag is turned on otherwise 788 // disable DataPlanUsageSummaryActivity. 789 somethingChanged = setTileEnabled( 790 new ComponentName(packageName, 791 isDataPlanFeatureEnabled 792 ? Settings.DataUsageSummaryActivity.class.getName() 793 : Settings.DataPlanUsageSummaryActivity.class.getName()), 794 false /* enabled */, 795 isAdmin) || somethingChanged; 796 797 // Enable DataUsageSummaryActivity if the data plan feature flag is turned on otherwise 798 // enable DataPlanUsageSummaryActivity. 799 somethingChanged = setTileEnabled( 800 new ComponentName(packageName, 801 isDataPlanFeatureEnabled 802 ? Settings.DataPlanUsageSummaryActivity.class.getName() 803 : Settings.DataUsageSummaryActivity.class.getName()), 804 Utils.isBandwidthControlEnabled() /* enabled */, 805 isAdmin) || somethingChanged; 806 807 somethingChanged = setTileEnabled(new ComponentName(packageName, 808 Settings.SimSettingsActivity.class.getName()), 809 Utils.showSimCardTile(this), isAdmin) 810 || somethingChanged; 811 812 somethingChanged = setTileEnabled(new ComponentName(packageName, 813 Settings.PowerUsageSummaryActivity.class.getName()), 814 mBatteryPresent, isAdmin) || somethingChanged; 815 816 somethingChanged = setTileEnabled(new ComponentName(packageName, 817 Settings.UserSettingsActivity.class.getName()), 818 UserHandle.MU_ENABLED && UserManager.supportsMultipleUsers() 819 && !Utils.isMonkeyRunning(), isAdmin) 820 || somethingChanged; 821 822 somethingChanged = setTileEnabled(new ComponentName(packageName, 823 Settings.NetworkDashboardActivity.class.getName()), 824 !UserManager.isDeviceInDemoMode(this), isAdmin) 825 || somethingChanged; 826 827 somethingChanged = setTileEnabled(new ComponentName(packageName, 828 Settings.ConnectedDeviceDashboardActivity.class.getName()), 829 !UserManager.isDeviceInDemoMode(this), isAdmin) 830 || somethingChanged; 831 832 somethingChanged = setTileEnabled(new ComponentName(packageName, 833 Settings.DateTimeSettingsActivity.class.getName()), 834 !UserManager.isDeviceInDemoMode(this), isAdmin) 835 || somethingChanged; 836 837 somethingChanged = setTileEnabled(new ComponentName(packageName, 838 Settings.PrintSettingsActivity.class.getName()), 839 pm.hasSystemFeature(PackageManager.FEATURE_PRINTING), isAdmin) 840 || somethingChanged; 841 842 final boolean showDev = mDevelopmentPreferences.getBoolean( 843 DevelopmentSettings.PREF_SHOW, android.os.Build.TYPE.equals("eng")) 844 && !um.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES); 845 somethingChanged = setTileEnabled(new ComponentName(packageName, 846 Settings.DevelopmentSettingsActivity.class.getName()), 847 showDev, isAdmin) 848 || somethingChanged; 849 850 // Enable/disable backup settings depending on whether the user is admin. 851 somethingChanged = setTileEnabled(new ComponentName(packageName, 852 BackupSettingsActivity.class.getName()), true, isAdmin) 853 || somethingChanged; 854 855 somethingChanged = setTileEnabled(new ComponentName(packageName, 856 Settings.WifiDisplaySettingsActivity.class.getName()), 857 WifiDisplaySettings.isAvailable(this), isAdmin) 858 || somethingChanged; 859 860 if (UserHandle.MU_ENABLED && !isAdmin) { 861 862 // When on restricted users, disable all extra categories (but only the settings ones). 863 final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories(); 864 synchronized (categories) { 865 for (DashboardCategory category : categories) { 866 final int tileCount = category.getTilesCount(); 867 for (int i = 0; i < tileCount; i++) { 868 final ComponentName component = category.getTile(i).intent.getComponent(); 869 870 final String name = component.getClassName(); 871 final boolean isEnabledForRestricted = ArrayUtils.contains( 872 SettingsGateway.SETTINGS_FOR_RESTRICTED, name); 873 if (packageName.equals(component.getPackageName()) 874 && !isEnabledForRestricted) { 875 somethingChanged = setTileEnabled(component, false, isAdmin) 876 || somethingChanged; 877 } 878 } 879 } 880 } 881 } 882 883 // Final step, refresh categories. 884 if (somethingChanged) { 885 Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories"); 886 updateCategories(); 887 } else { 888 Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call"); 889 } 890 } 891 892 /** 893 * @return whether or not the enabled state actually changed. 894 */ setTileEnabled(ComponentName component, boolean enabled, boolean isAdmin)895 private boolean setTileEnabled(ComponentName component, boolean enabled, boolean isAdmin) { 896 if (UserHandle.MU_ENABLED && !isAdmin && getPackageName().equals(component.getPackageName()) 897 && !ArrayUtils.contains(SettingsGateway.SETTINGS_FOR_RESTRICTED, 898 component.getClassName())) { 899 enabled = false; 900 } 901 return setTileEnabled(component, enabled); 902 } 903 getMetaData()904 private void getMetaData() { 905 try { 906 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(), 907 PackageManager.GET_META_DATA); 908 if (ai == null || ai.metaData == null) return; 909 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS); 910 } catch (NameNotFoundException nnfe) { 911 // No recovery 912 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString()); 913 } 914 } 915 916 // give subclasses access to the Next button hasNextButton()917 public boolean hasNextButton() { 918 return mNextButton != null; 919 } 920 getNextButton()921 public Button getNextButton() { 922 return mNextButton; 923 } 924 925 @Override shouldUpRecreateTask(Intent targetIntent)926 public boolean shouldUpRecreateTask(Intent targetIntent) { 927 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class)); 928 } 929 startSuggestion(Intent intent)930 public void startSuggestion(Intent intent) { 931 if (intent == null || ActivityManager.isUserAMonkey()) { 932 return; 933 } 934 mCurrentSuggestion = intent.getComponent(); 935 startActivityForResult(intent, REQUEST_SUGGESTION); 936 } 937 938 @Override onActivityResult(int requestCode, int resultCode, Intent data)939 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 940 if (requestCode == REQUEST_SUGGESTION && mCurrentSuggestion != null 941 && resultCode != RESULT_CANCELED) { 942 getPackageManager().setComponentEnabledSetting(mCurrentSuggestion, 943 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); 944 } 945 super.onActivityResult(requestCode, resultCode, data); 946 } 947 948 @VisibleForTesting getBitmapFromXmlResource(int drawableRes)949 Bitmap getBitmapFromXmlResource(int drawableRes) { 950 Drawable drawable = getResources().getDrawable(drawableRes, getTheme()); 951 Canvas canvas = new Canvas(); 952 Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), 953 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 954 canvas.setBitmap(bitmap); 955 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 956 drawable.draw(canvas); 957 958 return bitmap; 959 } 960 961 @Override onClick(View v)962 public void onClick(View v) { 963 Intent intent = new Intent(this, SearchActivity.class); 964 startActivity(intent); 965 } 966 967 /** 968 * @return whether or not the activity can be launched from other apps in the pinning screen. 969 */ isLaunchableInTaskModePinned()970 public boolean isLaunchableInTaskModePinned() { 971 return false; 972 } 973 isLockTaskModePinned()974 private boolean isLockTaskModePinned() { 975 final ActivityManager activityManager = 976 getApplicationContext().getSystemService(ActivityManager.class); 977 return activityManager.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED; 978 } 979 isSettingsRunOnTop()980 private boolean isSettingsRunOnTop() { 981 final ActivityManager activityManager = 982 getApplicationContext().getSystemService(ActivityManager.class); 983 final String taskPkgName = activityManager.getRunningTasks(1 /* maxNum */) 984 .get(0 /* index */).baseActivity.getPackageName(); 985 return TextUtils.equals(getPackageName(), taskPkgName); 986 } 987 } 988