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