• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.homepage;
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.SettingsActivity.EXTRA_USER_HANDLE;
24 
25 import android.animation.LayoutTransition;
26 import android.app.ActivityManager;
27 import android.app.settings.SettingsEnums;
28 import android.content.ComponentName;
29 import android.content.Intent;
30 import android.content.pm.ActivityInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackageManager.ApplicationInfoFlags;
33 import android.content.pm.UserInfo;
34 import android.content.res.Configuration;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.os.Process;
38 import android.os.UserHandle;
39 import android.os.UserManager;
40 import android.text.TextUtils;
41 import android.util.ArraySet;
42 import android.util.FeatureFlagUtils;
43 import android.util.Log;
44 import android.view.View;
45 import android.view.Window;
46 import android.view.WindowManager;
47 import android.widget.FrameLayout;
48 import android.widget.ImageView;
49 import android.widget.Toolbar;
50 
51 import androidx.annotation.VisibleForTesting;
52 import androidx.core.graphics.Insets;
53 import androidx.core.view.ViewCompat;
54 import androidx.core.view.WindowCompat;
55 import androidx.core.view.WindowInsetsCompat;
56 import androidx.fragment.app.Fragment;
57 import androidx.fragment.app.FragmentActivity;
58 import androidx.fragment.app.FragmentManager;
59 import androidx.fragment.app.FragmentTransaction;
60 import androidx.window.embedding.SplitRule;
61 
62 import com.android.settings.R;
63 import com.android.settings.Settings;
64 import com.android.settings.SettingsActivity;
65 import com.android.settings.SettingsApplication;
66 import com.android.settings.accounts.AvatarViewMixin;
67 import com.android.settings.activityembedding.ActivityEmbeddingRulesController;
68 import com.android.settings.activityembedding.ActivityEmbeddingUtils;
69 import com.android.settings.core.CategoryMixin;
70 import com.android.settings.core.FeatureFlags;
71 import com.android.settings.homepage.contextualcards.ContextualCardsFragment;
72 import com.android.settings.overlay.FeatureFactory;
73 import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
74 import com.android.settingslib.Utils;
75 import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin;
76 
77 import com.google.android.setupcompat.util.WizardManagerHelper;
78 
79 import java.net.URISyntaxException;
80 import java.util.Set;
81 
82 /** Settings homepage activity */
83 public class SettingsHomepageActivity extends FragmentActivity implements
84         CategoryMixin.CategoryHandler {
85 
86     private static final String TAG = "SettingsHomepageActivity";
87 
88     // Additional extra of Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK.
89     // Put true value to the intent when startActivity for a deep link intent from this Activity.
90     public static final String EXTRA_IS_FROM_SETTINGS_HOMEPAGE = "is_from_settings_homepage";
91 
92     // Additional extra of Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK.
93     // Set & get Uri of the Intent separately to prevent failure of Intent#ParseUri.
94     public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA =
95             "settings_large_screen_deep_link_intent_data";
96 
97     // The referrer who fires the initial intent to start the homepage
98     @VisibleForTesting
99     static final String EXTRA_INITIAL_REFERRER = "initial_referrer";
100 
101     static final int DEFAULT_HIGHLIGHT_MENU_KEY = R.string.menu_key_network;
102     private static final long HOMEPAGE_LOADING_TIMEOUT_MS = 300;
103 
104     private TopLevelSettings mMainFragment;
105     private View mHomepageView;
106     private View mSuggestionView;
107     private View mTwoPaneSuggestionView;
108     private CategoryMixin mCategoryMixin;
109     private Set<HomepageLoadedListener> mLoadedListeners;
110     private boolean mIsEmbeddingActivityEnabled;
111     private boolean mIsTwoPane;
112     // A regular layout shows icons on homepage, whereas a simplified layout doesn't.
113     private boolean mIsRegularLayout = true;
114 
115     /** A listener receiving homepage loaded events. */
116     public interface HomepageLoadedListener {
117         /** Called when the homepage is loaded. */
onHomepageLoaded()118         void onHomepageLoaded();
119     }
120 
121     private interface FragmentCreator<T extends Fragment> {
create()122         T create();
123 
124         /** To initialize after {@link #create} */
init(Fragment fragment)125         default void init(Fragment fragment) {}
126     }
127 
128     /**
129      * Try to add a {@link HomepageLoadedListener}. If homepage is already loaded, the listener
130      * will not be notified.
131      *
132      * @return Whether the listener is added.
133      */
addHomepageLoadedListener(HomepageLoadedListener listener)134     public boolean addHomepageLoadedListener(HomepageLoadedListener listener) {
135         if (mHomepageView == null) {
136             return false;
137         } else {
138             if (!mLoadedListeners.contains(listener)) {
139                 mLoadedListeners.add(listener);
140             }
141             return true;
142         }
143     }
144 
145     /**
146      * Shows the homepage and shows/hides the suggestion together. Only allows to be executed once
147      * to avoid the flicker caused by the suggestion suddenly appearing/disappearing.
148      */
showHomepageWithSuggestion(boolean showSuggestion)149     public void showHomepageWithSuggestion(boolean showSuggestion) {
150         if (mHomepageView == null) {
151             return;
152         }
153         Log.i(TAG, "showHomepageWithSuggestion: " + showSuggestion);
154         final View homepageView = mHomepageView;
155         mSuggestionView.setVisibility(showSuggestion ? View.VISIBLE : View.GONE);
156         mTwoPaneSuggestionView.setVisibility(showSuggestion ? View.VISIBLE : View.GONE);
157         mHomepageView = null;
158 
159         mLoadedListeners.forEach(listener -> listener.onHomepageLoaded());
160         mLoadedListeners.clear();
161         homepageView.setVisibility(View.VISIBLE);
162     }
163 
164     /** Returns the main content fragment */
getMainFragment()165     public TopLevelSettings getMainFragment() {
166         return mMainFragment;
167     }
168 
169     @Override
getCategoryMixin()170     public CategoryMixin getCategoryMixin() {
171         return mCategoryMixin;
172     }
173 
174     @Override
onCreate(Bundle savedInstanceState)175     protected void onCreate(Bundle savedInstanceState) {
176         super.onCreate(savedInstanceState);
177 
178         mIsEmbeddingActivityEnabled = ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this);
179         if (mIsEmbeddingActivityEnabled) {
180             final UserManager um = getSystemService(UserManager.class);
181             final UserInfo userInfo = um.getUserInfo(getUserId());
182             if (userInfo.isManagedProfile()) {
183                 final Intent intent = new Intent(getIntent())
184                         .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
185                         .putExtra(EXTRA_USER_HANDLE, getUser())
186                         .putExtra(EXTRA_INITIAL_REFERRER, getCurrentReferrer());
187                 if (TextUtils.equals(intent.getAction(), ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY)
188                         && this instanceof DeepLinkHomepageActivity) {
189                     intent.setClass(this, DeepLinkHomepageActivityInternal.class);
190                 }
191                 intent.removeFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
192                 startActivityAsUser(intent, um.getProfileParent(userInfo.id).getUserHandle());
193                 finish();
194                 return;
195             }
196         }
197 
198         setupEdgeToEdge();
199         setContentView(R.layout.settings_homepage_container);
200 
201         mIsTwoPane = ActivityEmbeddingUtils.isAlreadyEmbedded(this);
202 
203         updateAppBarMinHeight();
204         initHomepageContainer();
205         updateHomepageAppBar();
206         updateHomepageBackground();
207         mLoadedListeners = new ArraySet<>();
208 
209         initSearchBarView();
210 
211         getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
212         mCategoryMixin = new CategoryMixin(this);
213         getLifecycle().addObserver(mCategoryMixin);
214 
215         final String highlightMenuKey = getHighlightMenuKey();
216         // Only allow features on high ram devices.
217         if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
218             initAvatarView();
219             final boolean scrollNeeded = mIsEmbeddingActivityEnabled
220                     && !TextUtils.equals(getString(DEFAULT_HIGHLIGHT_MENU_KEY), highlightMenuKey);
221             showSuggestionFragment(scrollNeeded);
222             if (FeatureFlagUtils.isEnabled(this, FeatureFlags.CONTEXTUAL_HOME)) {
223                 showFragment(() -> new ContextualCardsFragment(), R.id.contextual_cards_content);
224                 ((FrameLayout) findViewById(R.id.main_content))
225                         .getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
226             }
227         }
228         mMainFragment = showFragment(() -> {
229             final TopLevelSettings fragment = new TopLevelSettings();
230             fragment.getArguments().putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
231                     highlightMenuKey);
232             return fragment;
233         }, R.id.main_content);
234 
235         // Launch the intent from deep link for large screen devices.
236         if (shouldLaunchDeepLinkIntentToRight()) {
237             launchDeepLinkIntentToRight();
238         }
239 
240         // Settings app may be launched on an existing task. Reset SplitPairRule of SubSettings here
241         // to prevent SplitPairRule of an existing task applied on a new started Settings app.
242         if (mIsEmbeddingActivityEnabled
243                 && (getIntent().getFlags() & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
244             initSplitPairRules();
245         }
246 
247         updateHomepagePaddings();
248         updateSplitLayout();
249 
250         enableTaskLocaleOverride();
251     }
252 
253     @VisibleForTesting
initSplitPairRules()254     void initSplitPairRules() {
255         new ActivityEmbeddingRulesController(getApplicationContext()).initRules();
256     }
257 
258     @Override
onStart()259     protected void onStart() {
260         ((SettingsApplication) getApplication()).setHomeActivity(this);
261         super.onStart();
262     }
263 
264     @Override
onNewIntent(Intent intent)265     protected void onNewIntent(Intent intent) {
266         super.onNewIntent(intent);
267 
268         // When it's large screen 2-pane and Settings app is in the background, receiving an Intent
269         // will not recreate this activity. Update the intent for this case.
270         setIntent(intent);
271         reloadHighlightMenuKey();
272         if (isFinishing()) {
273             return;
274         }
275         // Launch the intent from deep link for large screen devices.
276         if (shouldLaunchDeepLinkIntentToRight()) {
277             launchDeepLinkIntentToRight();
278         }
279     }
280 
281     @Override
onConfigurationChanged(Configuration newConfig)282     public void onConfigurationChanged(Configuration newConfig) {
283         super.onConfigurationChanged(newConfig);
284         final boolean newTwoPaneState = ActivityEmbeddingUtils.isAlreadyEmbedded(this);
285         if (mIsTwoPane != newTwoPaneState) {
286             mIsTwoPane = newTwoPaneState;
287             updateHomepageAppBar();
288             updateHomepageBackground();
289             updateHomepagePaddings();
290         }
291         updateSplitLayout();
292     }
293 
updateSplitLayout()294     private void updateSplitLayout() {
295         if (!mIsEmbeddingActivityEnabled) {
296             return;
297         }
298 
299         if (mIsTwoPane) {
300             if (mIsRegularLayout == ActivityEmbeddingUtils.isRegularHomepageLayout(this)) {
301                 // Layout unchanged
302                 return;
303             }
304         } else if (mIsRegularLayout) {
305             // One pane mode with the regular layout, not needed to change
306             return;
307         }
308         mIsRegularLayout = !mIsRegularLayout;
309 
310         // Update search title padding
311         View searchTitle = findViewById(R.id.search_bar_title);
312         if (searchTitle != null) {
313             int paddingStart = getResources().getDimensionPixelSize(
314                     mIsRegularLayout
315                             ? R.dimen.search_bar_title_padding_start_regular_two_pane
316                             : R.dimen.search_bar_title_padding_start);
317             searchTitle.setPaddingRelative(paddingStart, 0, 0, 0);
318         }
319         // Notify fragments
320         getSupportFragmentManager().getFragments().forEach(fragment -> {
321             if (fragment instanceof SplitLayoutListener) {
322                 ((SplitLayoutListener) fragment).onSplitLayoutChanged(mIsRegularLayout);
323             }
324         });
325     }
326 
setupEdgeToEdge()327     private void setupEdgeToEdge() {
328         WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
329         ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content),
330                 (v, windowInsets) -> {
331                     Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
332                     // Apply the insets paddings to the view.
333                     v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
334 
335                     // Return CONSUMED if you don't want the window insets to keep being
336                     // passed down to descendant views.
337                     return WindowInsetsCompat.CONSUMED;
338                 });
339     }
340 
initSearchBarView()341     private void initSearchBarView() {
342         final Toolbar toolbar = findViewById(R.id.search_action_bar);
343         FeatureFactory.getFactory(this).getSearchFeatureProvider()
344                 .initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);
345 
346         if (mIsEmbeddingActivityEnabled) {
347             final Toolbar toolbarTwoPaneVersion = findViewById(R.id.search_action_bar_two_pane);
348             FeatureFactory.getFactory(this).getSearchFeatureProvider()
349                     .initSearchToolbar(this /* activity */, toolbarTwoPaneVersion,
350                             SettingsEnums.SETTINGS_HOMEPAGE);
351         }
352     }
353 
initAvatarView()354     private void initAvatarView() {
355         final ImageView avatarView = findViewById(R.id.account_avatar);
356         final ImageView avatarTwoPaneView = findViewById(R.id.account_avatar_two_pane_version);
357         if (AvatarViewMixin.isAvatarSupported(this)) {
358             avatarView.setVisibility(View.VISIBLE);
359             getLifecycle().addObserver(new AvatarViewMixin(this, avatarView));
360 
361             if (mIsEmbeddingActivityEnabled) {
362                 avatarTwoPaneView.setVisibility(View.VISIBLE);
363                 getLifecycle().addObserver(new AvatarViewMixin(this, avatarTwoPaneView));
364             }
365         }
366     }
367 
updateHomepageBackground()368     private void updateHomepageBackground() {
369         if (!mIsEmbeddingActivityEnabled) {
370             return;
371         }
372 
373         final Window window = getWindow();
374         final int color = mIsTwoPane
375                 ? getColor(R.color.settings_two_pane_background_color)
376                 : Utils.getColorAttrDefaultColor(this, android.R.attr.colorBackground);
377 
378         window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
379         // Update status bar color
380         window.setStatusBarColor(color);
381         // Update content background.
382         findViewById(android.R.id.content).setBackgroundColor(color);
383     }
384 
showSuggestionFragment(boolean scrollNeeded)385     private void showSuggestionFragment(boolean scrollNeeded) {
386         final Class<? extends Fragment> fragmentClass = FeatureFactory.getFactory(this)
387                 .getSuggestionFeatureProvider().getContextualSuggestionFragment();
388         if (fragmentClass == null) {
389             return;
390         }
391 
392         mSuggestionView = findViewById(R.id.suggestion_content);
393         mTwoPaneSuggestionView = findViewById(R.id.two_pane_suggestion_content);
394         mHomepageView = findViewById(R.id.settings_homepage_container);
395         // Hide the homepage for preparing the suggestion. If scrolling is needed, the list views
396         // should be initialized in the invisible homepage view to prevent a scroll flicker.
397         mHomepageView.setVisibility(scrollNeeded ? View.INVISIBLE : View.GONE);
398         // Schedule a timer to show the homepage and hide the suggestion on timeout.
399         mHomepageView.postDelayed(() -> showHomepageWithSuggestion(false),
400                 HOMEPAGE_LOADING_TIMEOUT_MS);
401         showFragment(new SuggestionFragCreator(fragmentClass, /* isTwoPaneLayout= */ false),
402                 R.id.suggestion_content);
403         if (mIsEmbeddingActivityEnabled) {
404             showFragment(new SuggestionFragCreator(fragmentClass, /* isTwoPaneLayout= */ true),
405                     R.id.two_pane_suggestion_content);
406         }
407     }
408 
showFragment(FragmentCreator<T> fragmentCreator, int id)409     private <T extends Fragment> T showFragment(FragmentCreator<T> fragmentCreator, int id) {
410         final FragmentManager fragmentManager = getSupportFragmentManager();
411         final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
412         T showFragment = (T) fragmentManager.findFragmentById(id);
413 
414         if (showFragment == null) {
415             showFragment = fragmentCreator.create();
416             fragmentCreator.init(showFragment);
417             fragmentTransaction.add(id, showFragment);
418         } else {
419             fragmentCreator.init(showFragment);
420             fragmentTransaction.show(showFragment);
421         }
422         fragmentTransaction.commit();
423         return showFragment;
424     }
425 
shouldLaunchDeepLinkIntentToRight()426     private boolean shouldLaunchDeepLinkIntentToRight() {
427         if (!ActivityEmbeddingUtils.isSettingsSplitEnabled(this)
428                 || !FeatureFlagUtils.isEnabled(this,
429                         FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN)) {
430             return false;
431         }
432 
433         Intent intent = getIntent();
434         return intent != null && TextUtils.equals(intent.getAction(),
435                 ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY);
436     }
437 
launchDeepLinkIntentToRight()438     private void launchDeepLinkIntentToRight() {
439         if (!(this instanceof DeepLinkHomepageActivity
440                 || this instanceof DeepLinkHomepageActivityInternal)) {
441             Log.e(TAG, "Not a deep link component");
442             finish();
443             return;
444         }
445 
446         if (!WizardManagerHelper.isUserSetupComplete(this)) {
447             Log.e(TAG, "Cancel deep link before SUW completed");
448             finish();
449             return;
450         }
451 
452         final Intent intent = getIntent();
453         final String intentUriString = intent.getStringExtra(
454                 EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI);
455         if (TextUtils.isEmpty(intentUriString)) {
456             Log.e(TAG, "No EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI to deep link");
457             finish();
458             return;
459         }
460 
461         final Intent targetIntent;
462         try {
463             targetIntent = Intent.parseUri(intentUriString, Intent.URI_INTENT_SCHEME);
464         } catch (URISyntaxException e) {
465             Log.e(TAG, "Failed to parse deep link intent: " + e);
466             finish();
467             return;
468         }
469 
470         targetIntent.setData(intent.getParcelableExtra(
471                 SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA));
472         final ComponentName targetComponentName = targetIntent.resolveActivity(getPackageManager());
473         if (targetComponentName == null) {
474             Log.e(TAG, "No valid target for the deep link intent: " + targetIntent);
475             finish();
476             return;
477         }
478 
479         ActivityInfo targetActivityInfo;
480         try {
481             targetActivityInfo = getPackageManager().getActivityInfo(targetComponentName,
482                     /* flags= */ 0);
483         } catch (PackageManager.NameNotFoundException e) {
484             Log.e(TAG, "Failed to get target ActivityInfo: " + e);
485             finish();
486             return;
487         }
488 
489         UserHandle user = intent.getParcelableExtra(EXTRA_USER_HANDLE, UserHandle.class);
490         String caller = getInitialReferrer();
491         int callerUid = -1;
492         if (caller != null) {
493             try {
494                 callerUid = getPackageManager().getApplicationInfoAsUser(caller,
495                         ApplicationInfoFlags.of(/* flags= */ 0),
496                         user != null ? user.getIdentifier() : getUserId()).uid;
497             } catch (PackageManager.NameNotFoundException e) {
498                 Log.e(TAG, "Not able to get callerUid: " + e);
499                 finish();
500                 return;
501             }
502         }
503 
504         if (!hasPrivilegedAccess(caller, callerUid, targetActivityInfo.packageName)) {
505             if (!targetActivityInfo.exported) {
506                 Log.e(TAG, "Target Activity is not exported");
507                 finish();
508                 return;
509             }
510 
511             if (!isCallingAppPermitted(targetActivityInfo.permission, callerUid)) {
512                 Log.e(TAG, "Calling app must have the permission of deep link Activity");
513                 finish();
514                 return;
515             }
516         }
517 
518         // Only allow FLAG_GRANT_READ/WRITE_URI_PERMISSION if calling app has the permission to
519         // access specified Uri.
520         int uriPermissionFlags = targetIntent.getFlags()
521                 & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
522         if (targetIntent.getData() != null
523                 && uriPermissionFlags != 0
524                 && checkUriPermission(targetIntent.getData(), /* pid= */ -1, callerUid,
525                         uriPermissionFlags) == PackageManager.PERMISSION_DENIED) {
526             Log.e(TAG, "Calling app must have the permission to access Uri and grant permission");
527             finish();
528             return;
529         }
530 
531         targetIntent.setComponent(targetComponentName);
532 
533         // To prevent launchDeepLinkIntentToRight again for configuration change.
534         intent.setAction(null);
535 
536         targetIntent.removeFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
537         targetIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
538 
539         // Sender of intent may want to send intent extra data to the destination of targetIntent.
540         targetIntent.replaceExtras(intent);
541 
542         targetIntent.putExtra(EXTRA_IS_FROM_SETTINGS_HOMEPAGE, true);
543         targetIntent.putExtra(SettingsActivity.EXTRA_IS_FROM_SLICE, false);
544 
545         // Set 2-pane pair rule for the deep link page.
546         ActivityEmbeddingRulesController.registerTwoPanePairRule(this,
547                 new ComponentName(getApplicationContext(), getClass()),
548                 targetComponentName,
549                 targetIntent.getAction(),
550                 SplitRule.FinishBehavior.ALWAYS,
551                 SplitRule.FinishBehavior.ALWAYS,
552                 true /* clearTop */);
553         ActivityEmbeddingRulesController.registerTwoPanePairRule(this,
554                 new ComponentName(getApplicationContext(), Settings.class),
555                 targetComponentName,
556                 targetIntent.getAction(),
557                 SplitRule.FinishBehavior.ALWAYS,
558                 SplitRule.FinishBehavior.ALWAYS,
559                 true /* clearTop */);
560 
561         if (user != null) {
562             startActivityAsUser(targetIntent, user);
563         } else {
564             startActivity(targetIntent);
565         }
566     }
567 
568     // Check if the caller has privileged access to launch the target page.
hasPrivilegedAccess(String callerPkg, int callerUid, String targetPackage)569     private boolean hasPrivilegedAccess(String callerPkg, int callerUid, String targetPackage) {
570         if (TextUtils.equals(callerPkg, getPackageName())) {
571             return true;
572         }
573 
574         int targetUid = -1;
575         try {
576             targetUid = getPackageManager().getApplicationInfo(targetPackage,
577                     ApplicationInfoFlags.of(/* flags= */ 0)).uid;
578         } catch (PackageManager.NameNotFoundException e) {
579             Log.e(TAG, "Not able to get targetUid: " + e);
580             return false;
581         }
582 
583         // When activityInfo.exported is false, Activity still can be launched if applications have
584         // the same user ID.
585         if (UserHandle.isSameApp(callerUid, targetUid)) {
586             return true;
587         }
588 
589         // When activityInfo.exported is false, Activity still can be launched if calling app has
590         // root or system privilege.
591         int callingAppId = UserHandle.getAppId(callerUid);
592         if (callingAppId == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID) {
593             return true;
594         }
595 
596         return false;
597     }
598 
599     @VisibleForTesting
getInitialReferrer()600     String getInitialReferrer() {
601         String referrer = getCurrentReferrer();
602         if (!TextUtils.equals(referrer, getPackageName())) {
603             return referrer;
604         }
605 
606         String initialReferrer = getIntent().getStringExtra(EXTRA_INITIAL_REFERRER);
607         return TextUtils.isEmpty(initialReferrer) ? referrer : initialReferrer;
608     }
609 
610     @VisibleForTesting
getCurrentReferrer()611     String getCurrentReferrer() {
612         Intent intent = getIntent();
613         // Clear extras to get the real referrer
614         intent.removeExtra(Intent.EXTRA_REFERRER);
615         intent.removeExtra(Intent.EXTRA_REFERRER_NAME);
616         Uri referrer = getReferrer();
617         return referrer != null ? referrer.getHost() : null;
618     }
619 
620     @VisibleForTesting
isCallingAppPermitted(String permission, int callerUid)621     boolean isCallingAppPermitted(String permission, int callerUid) {
622         return TextUtils.isEmpty(permission)
623                 || checkPermission(permission, /* pid= */ -1, callerUid)
624                         == PackageManager.PERMISSION_GRANTED;
625     }
626 
getHighlightMenuKey()627     private String getHighlightMenuKey() {
628         final Intent intent = getIntent();
629         if (intent != null && TextUtils.equals(intent.getAction(),
630                 ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY)) {
631             final String menuKey = intent.getStringExtra(
632                     EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY);
633             if (!TextUtils.isEmpty(menuKey)) {
634                 return maybeRemapMenuKey(menuKey);
635             }
636         }
637         return getString(DEFAULT_HIGHLIGHT_MENU_KEY);
638     }
639 
maybeRemapMenuKey(String menuKey)640     private String maybeRemapMenuKey(String menuKey) {
641         boolean isPrivacyOrSecurityMenuKey =
642                 getString(R.string.menu_key_privacy).equals(menuKey)
643                         || getString(R.string.menu_key_security).equals(menuKey);
644         boolean isSafetyCenterMenuKey = getString(R.string.menu_key_safety_center).equals(menuKey);
645 
646         if (isPrivacyOrSecurityMenuKey && SafetyCenterManagerWrapper.get().isEnabled(this)) {
647             return getString(R.string.menu_key_safety_center);
648         }
649         if (isSafetyCenterMenuKey && !SafetyCenterManagerWrapper.get().isEnabled(this)) {
650             // We don't know if security or privacy, default to security as it is above.
651             return getString(R.string.menu_key_security);
652         }
653         return menuKey;
654     }
655 
reloadHighlightMenuKey()656     private void reloadHighlightMenuKey() {
657         mMainFragment.getArguments().putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
658                 getHighlightMenuKey());
659         mMainFragment.reloadHighlightMenuKey();
660     }
661 
initHomepageContainer()662     private void initHomepageContainer() {
663         final View view = findViewById(R.id.homepage_container);
664         // Prevent inner RecyclerView gets focus and invokes scrolling.
665         view.setFocusableInTouchMode(true);
666         view.requestFocus();
667     }
668 
updateHomepageAppBar()669     private void updateHomepageAppBar() {
670         if (!mIsEmbeddingActivityEnabled) {
671             return;
672         }
673         updateAppBarMinHeight();
674         if (mIsTwoPane) {
675             findViewById(R.id.homepage_app_bar_regular_phone_view).setVisibility(View.GONE);
676             findViewById(R.id.homepage_app_bar_two_pane_view).setVisibility(View.VISIBLE);
677             findViewById(R.id.suggestion_container_two_pane).setVisibility(View.VISIBLE);
678         } else {
679             findViewById(R.id.homepage_app_bar_regular_phone_view).setVisibility(View.VISIBLE);
680             findViewById(R.id.homepage_app_bar_two_pane_view).setVisibility(View.GONE);
681             findViewById(R.id.suggestion_container_two_pane).setVisibility(View.GONE);
682         }
683     }
684 
updateHomepagePaddings()685     private void updateHomepagePaddings() {
686         if (!mIsEmbeddingActivityEnabled) {
687             return;
688         }
689         if (mIsTwoPane) {
690             int padding = getResources().getDimensionPixelSize(
691                     R.dimen.homepage_padding_horizontal_two_pane);
692             mMainFragment.setPaddingHorizontal(padding);
693         } else {
694             mMainFragment.setPaddingHorizontal(0);
695         }
696         mMainFragment.updatePreferencePadding(mIsTwoPane);
697     }
698 
updateAppBarMinHeight()699     private void updateAppBarMinHeight() {
700         final int searchBarHeight = getResources().getDimensionPixelSize(R.dimen.search_bar_height);
701         final int margin = getResources().getDimensionPixelSize(
702                 mIsEmbeddingActivityEnabled && mIsTwoPane
703                         ? R.dimen.homepage_app_bar_padding_two_pane
704                         : R.dimen.search_bar_margin);
705         findViewById(R.id.app_bar_container).setMinimumHeight(searchBarHeight + margin * 2);
706     }
707 
708     private static class SuggestionFragCreator implements FragmentCreator {
709 
710         private final Class<? extends Fragment> mClass;
711         private final boolean mIsTwoPaneLayout;
712 
SuggestionFragCreator(Class<? extends Fragment> clazz, boolean isTwoPaneLayout)713         SuggestionFragCreator(Class<? extends Fragment> clazz, boolean isTwoPaneLayout) {
714             mClass = clazz;
715             mIsTwoPaneLayout = isTwoPaneLayout;
716         }
717 
718         @Override
create()719         public Fragment create() {
720             try {
721                 Fragment fragment = mClass.getConstructor().newInstance();
722                 return fragment;
723             } catch (Exception e) {
724                 Log.w(TAG, "Cannot show fragment", e);
725             }
726             return null;
727         }
728 
729         @Override
init(Fragment fragment)730         public void init(Fragment fragment) {
731             if (fragment instanceof SplitLayoutListener) {
732                 ((SplitLayoutListener) fragment).setSplitLayoutSupported(mIsTwoPaneLayout);
733             }
734         }
735     }
736 }
737