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