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