1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings;
18 
19 import static android.provider.Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY;
20 import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY;
21 import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI;
22 
23 import static com.android.settings.applications.appinfo.AppButtonsPreferenceController.KEY_REMOVE_TASK_WHEN_FINISHING;
24 
25 import android.app.ActionBar;
26 import android.app.ActivityManager;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.SharedPreferences;
33 import android.content.pm.ActivityInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.PackageManager.NameNotFoundException;
36 import android.content.res.Resources;
37 import android.content.res.Resources.Theme;
38 import android.graphics.drawable.Icon;
39 import android.os.AsyncTask;
40 import android.os.Bundle;
41 import android.os.UserHandle;
42 import android.os.UserManager;
43 import android.text.TextUtils;
44 import android.util.Log;
45 import android.view.View;
46 import android.widget.Button;
47 
48 import androidx.annotation.Nullable;
49 import androidx.annotation.VisibleForTesting;
50 import androidx.fragment.app.Fragment;
51 import androidx.fragment.app.FragmentManager;
52 import androidx.fragment.app.FragmentTransaction;
53 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
54 import androidx.preference.Preference;
55 import androidx.preference.PreferenceFragmentCompat;
56 import androidx.preference.PreferenceManager;
57 
58 import com.android.internal.util.ArrayUtils;
59 import com.android.settings.Settings.WifiSettingsActivity;
60 import com.android.settings.activityembedding.ActivityEmbeddingUtils;
61 import com.android.settings.applications.manageapplications.ManageApplications;
62 import com.android.settings.core.OnActivityResultListener;
63 import com.android.settings.core.SettingsBaseActivity;
64 import com.android.settings.core.SubSettingLauncher;
65 import com.android.settings.core.gateway.SettingsGateway;
66 import com.android.settings.dashboard.DashboardFeatureProvider;
67 import com.android.settings.homepage.SettingsHomepageActivity;
68 import com.android.settings.homepage.SliceDeepLinkHomepageActivity;
69 import com.android.settings.homepage.TopLevelSettings;
70 import com.android.settings.overlay.FeatureFactory;
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.development.DevelopmentSettingsEnabler;
76 import com.android.settingslib.drawer.DashboardCategory;
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 
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     /**
154      * Personal or Work profile tab of {@link ProfileSelectFragment}
155      * <p>0: Personal tab.
156      * <p>1: Work profile tab.
157      */
158     public static final String EXTRA_SHOW_FRAGMENT_TAB =
159             ":settings:show_fragment_tab";
160 
161     public static final String META_DATA_KEY_FRAGMENT_CLASS =
162             "com.android.settings.FRAGMENT_CLASS";
163 
164     public static final String META_DATA_KEY_HIGHLIGHT_MENU_KEY =
165             "com.android.settings.HIGHLIGHT_MENU_KEY";
166 
167     private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
168 
169     private String mFragmentClass;
170     private String mHighlightMenuKey;
171 
172     private CharSequence mInitialTitle;
173     private int mInitialTitleResId;
174 
175     private BroadcastReceiver mDevelopmentSettingsListener;
176 
177     private boolean mBatteryPresent = true;
178     private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
179         @Override
180         public void onReceive(Context context, Intent intent) {
181             String action = intent.getAction();
182             if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
183                 boolean batteryPresent = Utils.isBatteryPresent(intent);
184 
185                 if (mBatteryPresent != batteryPresent) {
186                     mBatteryPresent = batteryPresent;
187                     updateTilesList();
188                 }
189             }
190         }
191     };
192 
193     private SettingsMainSwitchBar mMainSwitch;
194 
195     private Button mNextButton;
196 
197     // Categories
198     private ArrayList<DashboardCategory> mCategories = new ArrayList<>();
199 
200     private DashboardFeatureProvider mDashboardFeatureProvider;
201 
getSwitchBar()202     public SettingsMainSwitchBar getSwitchBar() {
203         return mMainSwitch;
204     }
205 
206     @Override
onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref)207     public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
208         new SubSettingLauncher(this)
209                 .setDestination(pref.getFragment())
210                 .setArguments(pref.getExtras())
211                 .setSourceMetricsCategory(caller instanceof Instrumentable
212                         ? ((Instrumentable) caller).getMetricsCategory()
213                         : Instrumentable.METRICS_CATEGORY_UNKNOWN)
214                 .setTitleRes(-1)
215                 .launch();
216         return true;
217     }
218 
219     @Override
onPreferenceTreeClick(Preference preference)220     public boolean onPreferenceTreeClick(Preference preference) {
221         return false;
222     }
223 
224     @Override
getSharedPreferences(String name, int mode)225     public SharedPreferences getSharedPreferences(String name, int mode) {
226         if (name.equals(getPackageName() + "_preferences")) {
227             return new SharedPreferencesLogger(this, getMetricsTag(),
228                     FeatureFactory.getFactory(this).getMetricsFeatureProvider());
229         }
230         return super.getSharedPreferences(name, mode);
231     }
232 
getMetricsTag()233     private String getMetricsTag() {
234         String tag = null;
235         if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) {
236             tag = getInitialFragmentName(getIntent());
237         }
238         if (TextUtils.isEmpty(tag)) {
239             Log.w(LOG_TAG, "MetricsTag is invalid " + tag);
240             tag = getClass().getName();
241         }
242         if (tag.startsWith("com.android.settings.")) {
243             tag = tag.replace("com.android.settings.", "");
244         }
245         return tag;
246     }
247 
248     @Override
onCreate(Bundle savedState)249     protected void onCreate(Bundle savedState) {
250         // Should happen before any call to getIntent()
251         getMetaData();
252         final Intent intent = getIntent();
253 
254         if (shouldShowTwoPaneDeepLink(intent)) {
255             launchHomepageForTwoPaneDeepLink(intent);
256             finishAndRemoveTask();
257             super.onCreate(savedState);
258             return;
259         }
260 
261         super.onCreate(savedState);
262         Log.d(LOG_TAG, "Starting onCreate");
263 
264         long startTime = System.currentTimeMillis();
265 
266         final FeatureFactory factory = FeatureFactory.getFactory(this);
267         mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
268 
269         if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
270             getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
271         }
272 
273         // Getting Intent properties can only be done after the super.onCreate(...)
274         final String initialFragmentName = getInitialFragmentName(intent);
275 
276         // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content
277         // insets.
278         // If this is in setup flow, don't apply theme. Because light theme needs to be applied
279         // in SettingsBaseActivity#onCreate().
280         if (isSubSettings(intent) && !WizardManagerHelper.isAnySetupWizard(getIntent())) {
281             setTheme(R.style.Theme_SubSettings);
282         }
283 
284         setContentView(R.layout.settings_main_prefs);
285 
286         getSupportFragmentManager().addOnBackStackChangedListener(this);
287 
288         if (savedState != null) {
289             // We are restarting from a previous saved state; used that to initialize, instead
290             // of starting fresh.
291             setTitleFromIntent(intent);
292 
293             ArrayList<DashboardCategory> categories =
294                     savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
295             if (categories != null) {
296                 mCategories.clear();
297                 mCategories.addAll(categories);
298                 setTitleFromBackStack();
299             }
300         } else {
301             launchSettingFragment(initialFragmentName, intent);
302         }
303 
304         final boolean isInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
305 
306         final ActionBar actionBar = getActionBar();
307         if (actionBar != null) {
308             actionBar.setDisplayHomeAsUpEnabled(!isInSetupWizard);
309             actionBar.setHomeButtonEnabled(!isInSetupWizard);
310             actionBar.setDisplayShowTitleEnabled(true);
311         }
312         mMainSwitch = findViewById(R.id.switch_bar);
313         if (mMainSwitch != null) {
314             mMainSwitch.setMetricsTag(getMetricsTag());
315             mMainSwitch.setTranslationZ(findViewById(R.id.main_content).getTranslationZ() + 1);
316         }
317 
318         // see if we should show Back/Next buttons
319         if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
320 
321             View buttonBar = findViewById(R.id.button_bar);
322             if (buttonBar != null) {
323                 buttonBar.setVisibility(View.VISIBLE);
324 
325                 Button backButton = findViewById(R.id.back_button);
326                 backButton.setOnClickListener(v -> {
327                     setResult(RESULT_CANCELED, null);
328                     finish();
329                 });
330                 Button skipButton = findViewById(R.id.skip_button);
331                 skipButton.setOnClickListener(v -> {
332                     setResult(RESULT_OK, null);
333                     finish();
334                 });
335                 mNextButton = findViewById(R.id.next_button);
336                 mNextButton.setOnClickListener(v -> {
337                     setResult(RESULT_OK, null);
338                     finish();
339                 });
340 
341                 // set our various button parameters
342                 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
343                     String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
344                     if (TextUtils.isEmpty(buttonText)) {
345                         mNextButton.setVisibility(View.GONE);
346                     } else {
347                         mNextButton.setText(buttonText);
348                     }
349                 }
350                 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
351                     String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
352                     if (TextUtils.isEmpty(buttonText)) {
353                         backButton.setVisibility(View.GONE);
354                     } else {
355                         backButton.setText(buttonText);
356                     }
357                 }
358                 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
359                     skipButton.setVisibility(View.VISIBLE);
360                 }
361             }
362         }
363 
364         if (DEBUG_TIMING) {
365             Log.d(LOG_TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms");
366         }
367     }
368 
isSubSettings(Intent intent)369     private boolean isSubSettings(Intent intent) {
370         return this instanceof SubSettings ||
371             intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
372     }
373 
374     /**
375      * Returns the deep link trampoline intent for large screen devices.
376      */
getTrampolineIntent(Intent intent, String highlightMenuKey)377     public static Intent getTrampolineIntent(Intent intent, String highlightMenuKey) {
378         final Intent detailIntent = new Intent(intent);
379         // It's a deep link intent, SettingsHomepageActivity will set SplitPairRule and start it.
380         final Intent trampolineIntent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY)
381                 .setPackage(Utils.SETTINGS_PACKAGE_NAME)
382                 .replaceExtras(detailIntent);
383 
384         // Relay detail intent data to prevent failure of Intent#ParseUri.
385         // If Intent#getData() is not null, Intent#toUri will return an Uri which has the scheme of
386         // Intent#getData() and it may not be the scheme of an Intent.
387         trampolineIntent.putExtra(
388                 SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA,
389                 detailIntent.getData());
390         detailIntent.setData(null);
391 
392         trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI,
393                 detailIntent.toUri(Intent.URI_INTENT_SCHEME));
394 
395         trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY,
396                 highlightMenuKey);
397         trampolineIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
398         return trampolineIntent;
399     }
400 
launchHomepageForTwoPaneDeepLink(Intent intent)401     private void launchHomepageForTwoPaneDeepLink(Intent intent) {
402         final Intent trampolineIntent;
403         if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) {
404             // Get menu key for slice deep link case.
405             final String highlightMenuKey = intent.getStringExtra(
406                     EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY);
407             if (!TextUtils.isEmpty(highlightMenuKey)) {
408                 mHighlightMenuKey = highlightMenuKey;
409             }
410             trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey);
411             trampolineIntent.setClass(this, SliceDeepLinkHomepageActivity.class);
412         } else {
413             trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey);
414         }
415         startActivity(trampolineIntent);
416     }
417 
shouldShowTwoPaneDeepLink(Intent intent)418     private boolean shouldShowTwoPaneDeepLink(Intent intent) {
419         if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) {
420             return false;
421         }
422 
423         // If the activity is not the task root, it should not start trampoline for deep links.
424         if (!isTaskRoot()) {
425             return false;
426         }
427 
428         // Only starts trampoline for deep links. Should return false for all the cases that
429         // Settings app starts SettingsActivity or SubSetting by itself.
430         if (intent.getAction() == null) {
431             // Other apps should send deep link intent which matches intent filter of the Activity.
432             return false;
433         }
434 
435         if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) {
436             // Slice deep link starts the Intent using SubSettingLauncher. Returns true to show
437             // 2-pane deep link.
438             return true;
439         }
440 
441         if (isSubSettings(intent)) {
442             return false;
443         }
444 
445         if (intent.getBooleanExtra(SettingsHomepageActivity.EXTRA_IS_FROM_SETTINGS_HOMEPAGE,
446                 /* defaultValue */ false)) {
447             return false;
448         }
449 
450         if (TextUtils.equals(intent.getAction(), Intent.ACTION_CREATE_SHORTCUT)) {
451             // Returns false to show full screen for Intent.ACTION_CREATE_SHORTCUT because
452             // - Launcher startActivityForResult for Intent.ACTION_CREATE_SHORTCUT and activity
453             //   stack starts from launcher, CreateShortcutActivity will not follows SplitPaitRule
454             //   registered by Settings.
455             // - There is no CreateShortcutActivity entry point from Settings app UI.
456             return false;
457         }
458 
459         return true;
460     }
461 
462     /** Returns the initial fragment name that the activity will launch. */
463     @VisibleForTesting
getInitialFragmentName(Intent intent)464     public String getInitialFragmentName(Intent intent) {
465         return intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
466     }
467 
468     @Override
onApplyThemeResource(Theme theme, int resid, boolean first)469     protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
470         theme.applyStyle(R.style.SetupWizardPartnerResource, true);
471         super.onApplyThemeResource(theme, resid, first);
472     }
473 
474     @Override
onActivityResult(int requestCode, int resultCode, @Nullable Intent data)475     protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
476         super.onActivityResult(requestCode, resultCode, data);
477         final List<Fragment> fragments = getSupportFragmentManager().getFragments();
478         if (fragments != null) {
479             for (Fragment fragment : fragments) {
480                 if (fragment instanceof OnActivityResultListener) {
481                     fragment.onActivityResult(requestCode, resultCode, data);
482                 }
483             }
484         }
485     }
486 
487     @VisibleForTesting
launchSettingFragment(String initialFragmentName, Intent intent)488     void launchSettingFragment(String initialFragmentName, Intent intent) {
489         if (initialFragmentName != null) {
490             setTitleFromIntent(intent);
491 
492             Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
493             switchToFragment(initialFragmentName, initialArguments, true,
494                     mInitialTitleResId, mInitialTitle);
495         } else {
496             // Show search icon as up affordance if we are displaying the main Dashboard
497             mInitialTitleResId = R.string.dashboard_title;
498             switchToFragment(TopLevelSettings.class.getName(), null /* args */, false,
499                     mInitialTitleResId, mInitialTitle);
500         }
501     }
502 
setTitleFromIntent(Intent intent)503     private void setTitleFromIntent(Intent intent) {
504         Log.d(LOG_TAG, "Starting to set activity title");
505         final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1);
506         if (initialTitleResId > 0) {
507             mInitialTitle = null;
508             mInitialTitleResId = initialTitleResId;
509 
510             final String initialTitleResPackageName = intent.getStringExtra(
511                     EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME);
512             if (initialTitleResPackageName != null) {
513                 try {
514                     Context authContext = createPackageContextAsUser(initialTitleResPackageName,
515                             0 /* flags */, new UserHandle(UserHandle.myUserId()));
516                     mInitialTitle = authContext.getResources().getText(mInitialTitleResId);
517                     setTitle(mInitialTitle);
518                     mInitialTitleResId = -1;
519                     return;
520                 } catch (NameNotFoundException e) {
521                     Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName);
522                 } catch (Resources.NotFoundException resourceNotFound) {
523                     Log.w(LOG_TAG,
524                             "Could not find title resource in " + initialTitleResPackageName);
525                 }
526             } else {
527                 setTitle(mInitialTitleResId);
528             }
529         } else {
530             mInitialTitleResId = -1;
531             final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
532             mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
533             setTitle(mInitialTitle);
534         }
535         Log.d(LOG_TAG, "Done setting title");
536     }
537 
538     @Override
onBackStackChanged()539     public void onBackStackChanged() {
540         setTitleFromBackStack();
541     }
542 
setTitleFromBackStack()543     private void setTitleFromBackStack() {
544         final int count = getSupportFragmentManager().getBackStackEntryCount();
545 
546         if (count == 0) {
547             if (mInitialTitleResId > 0) {
548                 setTitle(mInitialTitleResId);
549             } else {
550                 setTitle(mInitialTitle);
551             }
552             return;
553         }
554 
555         FragmentManager.BackStackEntry bse = getSupportFragmentManager().
556                 getBackStackEntryAt(count - 1);
557         setTitleFromBackStackEntry(bse);
558     }
559 
setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse)560     private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
561         final CharSequence title;
562         final int titleRes = bse.getBreadCrumbTitleRes();
563         if (titleRes > 0) {
564             title = getText(titleRes);
565         } else {
566             title = bse.getBreadCrumbTitle();
567         }
568         if (title != null) {
569             setTitle(title);
570         }
571     }
572 
573     @Override
onSaveInstanceState(Bundle outState)574     protected void onSaveInstanceState(Bundle outState) {
575         super.onSaveInstanceState(outState);
576         saveState(outState);
577     }
578 
579     /**
580      * For testing purposes to avoid crashes from final variables in Activity's onSaveInstantState.
581      */
582     @VisibleForTesting
saveState(Bundle outState)583     void saveState(Bundle outState) {
584         if (mCategories.size() > 0) {
585             outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories);
586         }
587     }
588 
589     @Override
onResume()590     protected void onResume() {
591         super.onResume();
592 
593         mDevelopmentSettingsListener = new BroadcastReceiver() {
594             @Override
595             public void onReceive(Context context, Intent intent) {
596                 updateTilesList();
597             }
598         };
599         LocalBroadcastManager.getInstance(this).registerReceiver(mDevelopmentSettingsListener,
600                 new IntentFilter(DevelopmentSettingsEnabler.DEVELOPMENT_SETTINGS_CHANGED_ACTION));
601 
602         registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
603 
604         updateTilesList();
605     }
606 
607     @Override
onPause()608     protected void onPause() {
609         super.onPause();
610         LocalBroadcastManager.getInstance(this).unregisterReceiver(mDevelopmentSettingsListener);
611         mDevelopmentSettingsListener = null;
612         unregisterReceiver(mBatteryInfoReceiver);
613     }
614 
615     @Override
setTaskDescription(ActivityManager.TaskDescription taskDescription)616     public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
617         taskDescription.setIcon(Icon.createWithResource(this, R.drawable.ic_launcher_settings));
618         super.setTaskDescription(taskDescription);
619     }
620 
isValidFragment(String fragmentName)621     protected boolean isValidFragment(String fragmentName) {
622         // Almost all fragments are wrapped in this,
623         // except for a few that have their own activities.
624         for (int i = 0; i < SettingsGateway.ENTRY_FRAGMENTS.length; i++) {
625             if (SettingsGateway.ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
626         }
627         return false;
628     }
629 
630     @Override
getIntent()631     public Intent getIntent() {
632         Intent superIntent = super.getIntent();
633         String startingFragment = getStartingFragmentClass(superIntent);
634         // This is called from super.onCreate, isMultiPane() is not yet reliable
635         // Do not use onIsHidingHeaders either, which relies itself on this method
636         if (startingFragment != null) {
637             Intent modIntent = new Intent(superIntent);
638             modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
639             Bundle args = superIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
640             if (args != null) {
641                 args = new Bundle(args);
642             } else {
643                 args = new Bundle();
644             }
645             args.putParcelable("intent", superIntent);
646             modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
647             return modIntent;
648         }
649         return superIntent;
650     }
651 
652     /**
653      * Checks if the component name in the intent is different from the Settings class and
654      * returns the class name to load as a fragment.
655      */
getStartingFragmentClass(Intent intent)656     private String getStartingFragmentClass(Intent intent) {
657         if (mFragmentClass != null) return mFragmentClass;
658 
659         String intentClass = intent.getComponent().getClassName();
660         if (intentClass.equals(getClass().getName())) return null;
661 
662         if ("com.android.settings.RunningServices".equals(intentClass)
663                 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
664             // Old names of manage apps.
665             intentClass = ManageApplications.class.getName();
666         }
667 
668         return intentClass;
669     }
670 
671     /**
672      * Called by a preference panel fragment to finish itself.
673      *
674      * @param resultCode Optional result code to send back to the original
675      *                   launching fragment.
676      * @param resultData Optional result data to send back to the original
677      *                   launching fragment.
678      */
finishPreferencePanel(int resultCode, Intent resultData)679     public void finishPreferencePanel(int resultCode, Intent resultData) {
680         setResult(resultCode, resultData);
681         if (resultData != null &&
682                 resultData.getBooleanExtra(KEY_REMOVE_TASK_WHEN_FINISHING, false)) {
683             finishAndRemoveTask();
684         } else {
685             finish();
686         }
687     }
688 
689     /**
690      * Switch to a specific Fragment with taking care of validation, Title and BackStack
691      */
switchToFragment(String fragmentName, Bundle args, boolean validate, int titleResId, CharSequence title)692     private void switchToFragment(String fragmentName, Bundle args, boolean validate,
693             int titleResId, CharSequence title) {
694         Log.d(LOG_TAG, "Switching to fragment " + fragmentName);
695         if (validate && !isValidFragment(fragmentName)) {
696             throw new IllegalArgumentException("Invalid fragment for this activity: "
697                     + fragmentName);
698         }
699         Fragment f = Utils.getTargetFragment(this, fragmentName, args);
700         if (f == null) {
701             return;
702         }
703         FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
704         transaction.replace(R.id.main_content, f);
705         if (titleResId > 0) {
706             transaction.setBreadCrumbTitle(titleResId);
707         } else if (title != null) {
708             transaction.setBreadCrumbTitle(title);
709         }
710         transaction.commitAllowingStateLoss();
711         getSupportFragmentManager().executePendingTransactions();
712         Log.d(LOG_TAG, "Executed frag manager pendingTransactions");
713     }
714 
updateTilesList()715     private void updateTilesList() {
716         // Generally the items that are will be changing from these updates will
717         // not be in the top list of tiles, so run it in the background and the
718         // SettingsBaseActivity will pick up on the updates automatically.
719         AsyncTask.execute(() -> doUpdateTilesList());
720     }
721 
doUpdateTilesList()722     private void doUpdateTilesList() {
723         PackageManager pm = getPackageManager();
724         final UserManager um = UserManager.get(this);
725         final boolean isAdmin = um.isAdminUser();
726         boolean somethingChanged = false;
727         final String packageName = getPackageName();
728         final StringBuilder changedList = new StringBuilder();
729         somethingChanged = setTileEnabled(changedList,
730                 new ComponentName(packageName, WifiSettingsActivity.class.getName()),
731                 pm.hasSystemFeature(PackageManager.FEATURE_WIFI), isAdmin) || somethingChanged;
732 
733         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
734                         Settings.BluetoothSettingsActivity.class.getName()),
735                 pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH), isAdmin)
736                 || somethingChanged;
737 
738         // Enable DataUsageSummaryActivity if the data plan feature flag is turned on otherwise
739         // enable DataPlanUsageSummaryActivity.
740         somethingChanged = setTileEnabled(changedList,
741                 new ComponentName(packageName, Settings.DataUsageSummaryActivity.class.getName()),
742                 Utils.isBandwidthControlEnabled() /* enabled */,
743                 isAdmin) || somethingChanged;
744 
745         somethingChanged = setTileEnabled(changedList,
746                 new ComponentName(packageName,
747                         Settings.ConnectedDeviceDashboardActivity.class.getName()),
748                 !UserManager.isDeviceInDemoMode(this) /* enabled */,
749                 isAdmin) || somethingChanged;
750 
751         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
752                         Settings.PowerUsageSummaryActivity.class.getName()),
753                 mBatteryPresent, isAdmin) || somethingChanged;
754 
755         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
756                         Settings.DataUsageSummaryActivity.class.getName()),
757                 Utils.isBandwidthControlEnabled(), isAdmin)
758                 || somethingChanged;
759 
760         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
761                         Settings.UserSettingsActivity.class.getName()),
762                 UserHandle.MU_ENABLED && UserManager.supportsMultipleUsers()
763                         && !Utils.isMonkeyRunning(), isAdmin)
764                 || somethingChanged;
765 
766         final boolean showDev = DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(this)
767                 && !Utils.isMonkeyRunning();
768         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
769                         Settings.DevelopmentSettingsDashboardActivity.class.getName()),
770                 showDev, isAdmin)
771                 || somethingChanged;
772 
773         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
774                         Settings.WifiDisplaySettingsActivity.class.getName()),
775                 WifiDisplaySettings.isAvailable(this), isAdmin)
776                 || somethingChanged;
777 
778         if (UserHandle.MU_ENABLED && !isAdmin) {
779             // When on restricted users, disable all extra categories (but only the settings ones).
780             final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories();
781             synchronized (categories) {
782                 for (DashboardCategory category : categories) {
783                     final int tileCount = category.getTilesCount();
784                     for (int i = 0; i < tileCount; i++) {
785                         final ComponentName component = category.getTile(i)
786                                 .getIntent().getComponent();
787                         final String name = component.getClassName();
788                         final boolean isEnabledForRestricted = ArrayUtils.contains(
789                                 SettingsGateway.SETTINGS_FOR_RESTRICTED, name);
790                         if (packageName.equals(component.getPackageName())
791                                 && !isEnabledForRestricted) {
792                             somethingChanged =
793                                     setTileEnabled(changedList, component, false, isAdmin)
794                                             || somethingChanged;
795                         }
796                     }
797                 }
798             }
799         }
800 
801         // Final step, refresh categories.
802         if (somethingChanged) {
803             Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories "
804                     + changedList.toString());
805             mCategoryMixin.updateCategories();
806         } else {
807             Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call");
808         }
809     }
810 
811     /**
812      * @return whether or not the enabled state actually changed.
813      */
setTileEnabled(StringBuilder changedList, ComponentName component, boolean enabled, boolean isAdmin)814     private boolean setTileEnabled(StringBuilder changedList, ComponentName component,
815             boolean enabled, boolean isAdmin) {
816         if (UserHandle.MU_ENABLED && !isAdmin && getPackageName().equals(component.getPackageName())
817                 && !ArrayUtils.contains(SettingsGateway.SETTINGS_FOR_RESTRICTED,
818                 component.getClassName())) {
819             enabled = false;
820         }
821         boolean changed = setTileEnabled(component, enabled);
822         if (changed) {
823             changedList.append(component.toShortString()).append(",");
824         }
825         return changed;
826     }
827 
getMetaData()828     private void getMetaData() {
829         try {
830             ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
831                     PackageManager.GET_META_DATA);
832             if (ai == null || ai.metaData == null) return;
833             mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
834             mHighlightMenuKey = ai.metaData.getString(META_DATA_KEY_HIGHLIGHT_MENU_KEY);
835         } catch (NameNotFoundException nnfe) {
836             // No recovery
837             Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
838         }
839     }
840 
841     // give subclasses access to the Next button
hasNextButton()842     public boolean hasNextButton() {
843         return mNextButton != null;
844     }
845 
getNextButton()846     public Button getNextButton() {
847         return mNextButton;
848     }
849 }
850