From 22ce392b9161f85cee413ee27ea5bb0f2c9f1714 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Tue, 31 Aug 2021 18:11:57 +0800 Subject: [PATCH] 2 panes deep link for large screen devices This change supports deep link to Settings app internal pages and external pages outside Settings app. Apps need android.permission.ALLOW_TWO_PANES_DEEP_LINK_IN_SETTINGS to send the intent of Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK. Settings app will startActivity for the intent from Settings#EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI. Bug: 197048599 Test: build pass Change-Id: Idaf4a8be4603c1308f16fb4e378266c1e52acb40 --- AndroidManifest.xml | 13 +++ .../android/settings/SettingsActivity.java | 23 ++++++ .../ActivityEmbeddingRulesController.java | 50 ++++++------ .../homepage/SettingsHomepageActivity.java | 80 +++++++++++++++++++ 4 files changed, 142 insertions(+), 24 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d8bbe8f2de7..2c3eb057fc3 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -107,6 +107,8 @@ + + + + + + + + + + diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index f3cdd6c1640..178892e6f5f 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -58,6 +58,8 @@ import com.android.settings.core.SettingsBaseActivity; import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.gateway.SettingsGateway; import com.android.settings.dashboard.DashboardFeatureProvider; +import com.android.settings.activityembedding.ActivityEmbeddingUtils; +import com.android.settings.homepage.SettingsHomepageActivity; import com.android.settings.homepage.TopLevelSettings; import com.android.settings.overlay.FeatureFactory; import com.android.settings.wfd.WifiDisplaySettings; @@ -240,7 +242,22 @@ public class SettingsActivity extends SettingsBaseActivity // Should happen before any call to getIntent() getMetaData(); + // If it's a deep link intent, start the Activity from SettingsHomepageActivity for 2-pane. final Intent intent = getIntent(); + final boolean isFromSettingsHomepage = intent.getBooleanExtra( + SettingsHomepageActivity.EXTRA_IS_FROM_SETTINGS_HOMEPAGE, /* defaultValue */ false); + if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this) && !isFromSettingsHomepage + && isOnlyOneActivityInActivityStack()) { + final Intent trampolineIntent = + new Intent(android.provider.Settings.ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK); + trampolineIntent.putExtra( + android.provider.Settings.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI, + intent.toUri(Intent.URI_INTENT_SCHEME)); + startActivity(trampolineIntent); + finish(); + return; + } + if (intent.hasExtra(EXTRA_UI_OPTIONS)) { getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0)); } @@ -347,6 +364,12 @@ public class SettingsActivity extends SettingsBaseActivity } } + private boolean isOnlyOneActivityInActivityStack() { + final ActivityManager activityManager = getSystemService(ActivityManager.class); + List taskList = activityManager.getRunningTasks(2); + return taskList.get(0).numActivities == 1; + } + /** Returns the initial fragment name that the activity will launch. */ @VisibleForTesting public String getInitialFragmentName(Intent intent) { diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java index f1a1ecd023b..994098083ba 100644 --- a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java +++ b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java @@ -60,12 +60,34 @@ public class ActivityEmbeddingRulesController { mSplitController.clearRegisteredRules(); // Set a placeholder for home page. - mSplitController.registerRule(getHomepagePlaceholderRule()); + registerHomepagePlaceholderRule(); // Set subsettings rule. - mSplitController.registerRule(getSubSettingsPairRule()); + registerTwoPanePairRule(mContext, + getComponentName(Settings.class), + getComponentName(SubSettings.class), + true /* finishPrimaryWithSecondary */, + true /* finishSecondaryWithPrimary */); } - private SplitPlaceholderRule getHomepagePlaceholderRule() { + /** Register a SplitPairRule for 2-pane. */ + public static void registerTwoPanePairRule(Context context, + ComponentName primary, ComponentName secondary, + boolean finishPrimaryWithSecondary, boolean finishSecondaryWithPrimary) { + final Set filters = new HashSet<>(); + filters.add(new SplitPairFilter(primary, secondary, + null /* secondaryActivityIntentAction */, + null /* secondaryActivityIntentCategory */)); + + new SplitController(context).registerRule(new SplitPairRule(filters, + finishPrimaryWithSecondary, + finishSecondaryWithPrimary, true /* clearTop */, + ActivityEmbeddingUtils.getMinCurrentScreenSplitWidthPx(context), + ActivityEmbeddingUtils.getMinSmallestScreenSplitWidthPx(context), + ActivityEmbeddingUtils.SPLIT_RATIO, + LayoutDirection.LOCALE)); + } + + private void registerHomepagePlaceholderRule() { final Set activityFilters = new HashSet<>(); activityFilters.add(new ActivityFilter(getComponentName(Settings.class))); final Intent intent = new Intent(); @@ -78,27 +100,7 @@ public class ActivityEmbeddingRulesController { ActivityEmbeddingUtils.SPLIT_RATIO, LayoutDirection.LOCALE); - return placeholderRule; - } - - private SplitPairRule getSubSettingsPairRule() { - final Set pairFilters = new HashSet<>(); - pairFilters.add(new SplitPairFilter( - getComponentName(Settings.class), - getComponentName(SubSettings.class), - null /* secondaryActivityIntentAction */, - null /* secondaryActivityIntentCategory */)); - final SplitPairRule rule = new SplitPairRule( - pairFilters, - true /* finishPrimaryWithSecondary */, - true /* finishSecondaryWithPrimary */, - true /* clearTop */, - ActivityEmbeddingUtils.getMinCurrentScreenSplitWidthPx(mContext), - ActivityEmbeddingUtils.getMinSmallestScreenSplitWidthPx(mContext), - ActivityEmbeddingUtils.SPLIT_RATIO, - LayoutDirection.LOCALE); - - return rule; + mSplitController.registerRule(placeholderRule); } @NonNull diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 1d7b5dc14b9..73f0abb1f2c 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -18,8 +18,14 @@ package com.android.settings.homepage; import android.animation.LayoutTransition; import android.app.ActivityManager; +import android.app.PendingIntent; import android.app.settings.SettingsEnums; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Bundle; +import android.provider.Settings; +import android.text.TextUtils; import android.util.FeatureFlagUtils; import android.util.Log; import android.view.View; @@ -31,21 +37,33 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import androidx.window.embedding.SplitController; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settings.accounts.AvatarViewMixin; import com.android.settings.core.CategoryMixin; import com.android.settings.core.FeatureFlags; +import com.android.settings.activityembedding.ActivityEmbeddingRulesController; +import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; +import java.net.URISyntaxException; + /** Settings homepage activity */ public class SettingsHomepageActivity extends FragmentActivity implements CategoryMixin.CategoryHandler { private static final String TAG = "SettingsHomepageActivity"; + // Put true value to the intent when startActivity for a deep link intent from this Activity. + public static final String EXTRA_IS_FROM_SETTINGS_HOMEPAGE = "is_from_settings_homepage"; + + // An alias class name of SettingsHomepageActivity. + private static final String ALIAS_DEEP_LINK = "com.android.settings.DeepLinkHomepageActivity"; + private static final long HOMEPAGE_LOADING_TIMEOUT_MS = 300; private View mHomepageView; @@ -105,6 +123,20 @@ public class SettingsHomepageActivity extends FragmentActivity implements showFragment(new TopLevelSettings(), R.id.main_content); ((FrameLayout) findViewById(R.id.main_content)) .getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); + + // Launch the intent from deep link for large screen devices. + launchDeepLinkIntentToRight(); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + // When it's large screen 2-pane and Settings app is in background. Receiving a Intent + // in this Activity will not finish nor onCreate. setIntent here for this case. + setIntent(intent); + // Launch the intent from deep link for large screen devices. + launchDeepLinkIntentToRight(); } private void showSuggestionFragment() { @@ -141,6 +173,54 @@ public class SettingsHomepageActivity extends FragmentActivity implements fragmentTransaction.commit(); } + private void launchDeepLinkIntentToRight() { + if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) { + return; + } + + final Intent intent = getIntent(); + if (intent == null || !TextUtils.equals(intent.getAction(), + Settings.ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK)) { + return; + } + + final String intentUriString = intent.getStringExtra( + Settings.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI); + if (TextUtils.isEmpty(intentUriString)) { + Log.e(TAG, "No EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI to deep link"); + finish(); + return; + } + + final Intent targetIntent; + try { + targetIntent = Intent.parseUri(intentUriString, Intent.URI_INTENT_SCHEME); + } catch (URISyntaxException e) { + Log.e(TAG, "Failed to parse deep link intent: " + e); + finish(); + return; + } + + final ComponentName targetComponentName = targetIntent.resolveActivity(getPackageManager()); + if (targetComponentName == null) { + Log.e(TAG, "No valid target for the deep link intent: " + targetIntent); + finish(); + return; + } + + targetIntent.setFlags(targetIntent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); + + targetIntent.putExtra(EXTRA_IS_FROM_SETTINGS_HOMEPAGE, true); + + // Set 2-pane pair rule for the external deep link page. + ActivityEmbeddingRulesController.registerTwoPanePairRule(this, + new ComponentName(Utils.SETTINGS_PACKAGE_NAME, ALIAS_DEEP_LINK), + targetComponentName, + true /* finishPrimaryWithSecondary */, + true /* finishSecondaryWithPrimary */); + startActivity(targetIntent); + } + private void initHomepageContainer() { final View view = findViewById(R.id.homepage_container); // Prevent inner RecyclerView gets focus and invokes scrolling.