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