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