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