Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 62e64c59 authored by Chaohui Wang's avatar Chaohui Wang Committed by Android (Google) Code Review
Browse files

Merge "Let SpaBridgeActivity support ActivityEmbedding" into main

parents cb33938d 0d338cd1
Loading
Loading
Loading
Loading
+4 −75
Original line number Diff line number Diff line
@@ -16,16 +16,12 @@

package com.android.settings;

import static android.provider.Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY;
import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY;
import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI;

import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.tryStartMultiPaneDeepLink;
import static com.android.settings.applications.appinfo.AppButtonsPreferenceController.KEY_REMOVE_TASK_WHEN_FINISHING;

import android.app.ActionBar;
import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -35,7 +31,6 @@ import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.drawable.Icon;
@@ -67,7 +62,6 @@ 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.homepage.DeepLinkHomepageActivityInternal;
import com.android.settings.homepage.SettingsHomepageActivity;
import com.android.settings.homepage.TopLevelSettings;
import com.android.settings.overlay.FeatureFactory;
@@ -278,7 +272,8 @@ public class SettingsActivity extends SettingsBaseActivity
        getMetaData();
        final Intent intent = getIntent();

        if (shouldShowTwoPaneDeepLink(intent) && tryStartTwoPaneDeepLink(intent)) {
        if (shouldShowMultiPaneDeepLink(intent)
                && tryStartMultiPaneDeepLink(this, intent, mHighlightMenuKey)) {
            finish();
            super.onCreate(savedState);
            return;
@@ -415,73 +410,7 @@ public class SettingsActivity extends SettingsBaseActivity
            intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
    }

    /**
     * Returns the deep link trampoline intent for large screen devices.
     */
    public static Intent getTrampolineIntent(Intent intent, String highlightMenuKey) {
        final Intent detailIntent = new Intent(intent);
        // Guard against the arbitrary Intent injection.
        if (detailIntent.getSelector() != null) {
            detailIntent.setSelector(null);
        }
        // It's a deep link intent, SettingsHomepageActivity will set SplitPairRule and start it.
        final Intent trampolineIntent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY)
                .setPackage(Utils.SETTINGS_PACKAGE_NAME)
                .replaceExtras(detailIntent);

        // Relay detail intent data to prevent failure of Intent#ParseUri.
        // If Intent#getData() is not null, Intent#toUri will return an Uri which has the scheme of
        // Intent#getData() and it may not be the scheme of an Intent.
        trampolineIntent.putExtra(
                SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA,
                detailIntent.getData());
        detailIntent.setData(null);

        trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI,
                detailIntent.toUri(Intent.URI_INTENT_SCHEME));

        trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY,
                highlightMenuKey);
        trampolineIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
        return trampolineIntent;
    }

    private boolean tryStartTwoPaneDeepLink(Intent intent) {
        intent.putExtra(EXTRA_INITIAL_CALLING_PACKAGE, PasswordUtils.getCallingAppPackageName(
                getActivityToken()));
        final Intent trampolineIntent;
        if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) {
            // Get menu key for slice deep link case.
            final String highlightMenuKey = intent.getStringExtra(
                    EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY);
            if (!TextUtils.isEmpty(highlightMenuKey)) {
                mHighlightMenuKey = highlightMenuKey;
            }
            trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey);
            trampolineIntent.setClass(this, DeepLinkHomepageActivityInternal.class);
        } else {
            trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey);
        }

        try {
            final UserManager um = getSystemService(UserManager.class);
            final UserInfo userInfo = um.getUserInfo(getUser().getIdentifier());
            if (userInfo.isManagedProfile()) {
                trampolineIntent.setClass(this, DeepLinkHomepageActivityInternal.class)
                        .putExtra(EXTRA_USER_HANDLE, getUser());
                startActivityAsUser(trampolineIntent,
                        um.getProfileParent(userInfo.id).getUserHandle());
            } else {
                startActivity(trampolineIntent);
            }
        } catch (ActivityNotFoundException e) {
            Log.e(LOG_TAG, "Deep link homepage is not available to show 2-pane UI");
            return false;
        }
        return true;
    }

    private boolean shouldShowTwoPaneDeepLink(Intent intent) {
    private boolean shouldShowMultiPaneDeepLink(Intent intent) {
        if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) {
            return false;
        }
+12 −11
Original line number Diff line number Diff line
@@ -28,7 +28,7 @@ import com.android.settings.applications.appinfo.WriteSettingsDetails
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetails
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureSettings
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp
import com.android.settings.spa.SpaAppBridgeActivity.Companion.getDestinationForApp
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
@@ -72,17 +72,18 @@ object SettingsActivityUtil {

    @JvmStatic
    fun Context.launchSpaActivity(fragmentName: String, intent: Intent): Boolean {
        if (!FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_ENABLE_SPA)) {
            return false
        }
        FRAGMENT_TO_SPA_DESTINATION_MAP[fragmentName]?.let { destination ->
        if (FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_ENABLE_SPA)) {
            getDestination(fragmentName, intent)?.let { destination ->
                startSpaActivity(destination)
                return true
            }
        FRAGMENT_TO_SPA_APP_DESTINATION_PREFIX_MAP[fragmentName]?.let { appDestinationPrefix ->
            startSpaActivityForApp(appDestinationPrefix, intent)
            return true
        }
        return false
    }

    private fun getDestination(fragmentName: String, intent: Intent): String? =
        FRAGMENT_TO_SPA_DESTINATION_MAP[fragmentName]
            ?: FRAGMENT_TO_SPA_APP_DESTINATION_PREFIX_MAP[fragmentName]?.let { destinationPrefix ->
                getDestinationForApp(destinationPrefix, intent)
            }
}
+113 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.activityembedding

import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.util.Log
import com.android.settings.SettingsActivity
import com.android.settings.Utils
import com.android.settings.homepage.DeepLinkHomepageActivityInternal
import com.android.settings.homepage.SettingsHomepageActivity
import com.android.settings.password.PasswordUtils
import com.android.settingslib.spaprivileged.framework.common.userManager

object EmbeddedDeepLinkUtils {
    private const val TAG = "EmbeddedDeepLinkUtils"

    @JvmStatic
    fun Activity.tryStartMultiPaneDeepLink(
        intent: Intent,
        highlightMenuKey: String? = null,
    ): Boolean {
        intent.putExtra(
            SettingsActivity.EXTRA_INITIAL_CALLING_PACKAGE,
            PasswordUtils.getCallingAppPackageName(activityToken),
        )
        val trampolineIntent: Intent
        if (intent.getBooleanExtra(SettingsActivity.EXTRA_IS_FROM_SLICE, false)) {
            // Get menu key for slice deep link case.
            var sliceHighlightMenuKey: String? = intent.getStringExtra(
                Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY
            )
            if (sliceHighlightMenuKey.isNullOrEmpty()) {
                sliceHighlightMenuKey = highlightMenuKey
            }
            trampolineIntent = getTrampolineIntent(intent, sliceHighlightMenuKey)
            trampolineIntent.setClass(this, DeepLinkHomepageActivityInternal::class.java)
        } else {
            trampolineIntent = getTrampolineIntent(intent, highlightMenuKey)
        }
        return startTrampolineIntent(trampolineIntent)
    }

    /**
     * Returns the deep link trampoline intent for large screen devices.
     */
    @JvmStatic
    fun getTrampolineIntent(intent: Intent, highlightMenuKey: String?): Intent {
        val detailIntent = Intent(intent)
        // Guard against the arbitrary Intent injection.
        if (detailIntent.selector != null) {
            detailIntent.setSelector(null)
        }
        // It's a deep link intent, SettingsHomepageActivity will set SplitPairRule and start it.
        return Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY).apply {
            setPackage(Utils.SETTINGS_PACKAGE_NAME)
            replaceExtras(detailIntent)

            // Relay detail intent data to prevent failure of Intent#ParseUri.
            // If Intent#getData() is not null, Intent#toUri will return an Uri which has the scheme
            // of Intent#getData() and it may not be the scheme of an Intent.
            putExtra(
                SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA,
                detailIntent.data
            )
            detailIntent.setData(null)
            putExtra(
                Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI,
                detailIntent.toUri(Intent.URI_INTENT_SCHEME)
            )
            putExtra(
                Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY,
                highlightMenuKey
            )
            addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
        }
    }

    private fun Context.startTrampolineIntent(trampolineIntent: Intent): Boolean = try {
        val userInfo = userManager.getUserInfo(user.identifier)
        if (userInfo.isManagedProfile) {
            trampolineIntent.setClass(this, DeepLinkHomepageActivityInternal::class.java)
                .putExtra(SettingsActivity.EXTRA_USER_HANDLE, user)
            startActivityAsUser(
                trampolineIntent,
                userManager.getProfileParent(userInfo.id).userHandle
            )
        } else {
            startActivity(trampolineIntent)
        }
        true
    } catch (e: ActivityNotFoundException) {
        Log.e(TAG, "Deep link homepage is not available to show 2-pane UI")
        false
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.settings.search;

import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS;
import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB;
import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.getTrampolineIntent;

import android.app.Activity;
import android.content.ComponentName;
@@ -107,7 +108,7 @@ public class SearchResultTrampoline extends Activity {
            startActivity(intent);
        } else if (isSettingsIntelligence(callingActivity)) {
            if (FeatureFlagUtils.isEnabled(this, FeatureFlags.SETTINGS_SEARCH_ALWAYS_EXPAND)) {
                startActivity(SettingsActivity.getTrampolineIntent(intent, highlightMenuKey)
                startActivity(getTrampolineIntent(intent, highlightMenuKey)
                        .setClass(this, DeepLinkHomepageActivityInternal.class)
                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS));
@@ -130,7 +131,7 @@ public class SearchResultTrampoline extends Activity {
            }
        } else {
            // Two-pane case
            startActivity(SettingsActivity.getTrampolineIntent(intent, highlightMenuKey)
            startActivity(getTrampolineIntent(intent, highlightMenuKey)
                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
        }

+2 −27
Original line number Diff line number Diff line
@@ -16,18 +16,14 @@

package com.android.settings.spa

import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
import com.android.settingslib.spa.framework.BrowseActivity
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.util.SESSION_BROWSE
import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL
import com.android.settingslib.spa.framework.util.appendSpaParams
import com.google.android.setupcompat.util.WizardManagerHelper

@@ -44,7 +40,7 @@ class SpaActivity : BrowseActivity() {
        @VisibleForTesting
        fun Context.isSuwAndPageBlocked(name: String): Boolean =
            if (name in SuwBlockedPages && !WizardManagerHelper.isDeviceProvisioned(this)) {
                Log.w(TAG, "$name blocked before SUW completed.");
                Log.w(TAG, "$name blocked before SUW completed.")
                true
            } else {
                false
@@ -54,29 +50,8 @@ class SpaActivity : BrowseActivity() {
        fun Context.startSpaActivity(destination: String) {
            val intent = Intent(this, SpaActivity::class.java)
                .appendSpaParams(destination = destination)
            if (isLaunchedFromInternal()) {
                intent.appendSpaParams(sessionName = SESSION_BROWSE)
            } else {
                intent.appendSpaParams(sessionName = SESSION_EXTERNAL)
            }
                .appendSpaParams(sessionName = SESSION_BROWSE)
            startActivity(intent)
        }

        @JvmStatic
        fun Context.startSpaActivityForApp(destinationPrefix: String, intent: Intent): Boolean {
            val packageName = intent.data?.schemeSpecificPart ?: return false
            startSpaActivity("$destinationPrefix/$packageName/${UserHandle.myUserId()}")
            return true
        }

        fun Context.isLaunchedFromInternal(): Boolean {
            var pkg: String? = null
            try {
                pkg = ActivityManager.getService().getLaunchedFromPackage(getActivityToken())
            } catch (e: RemoteException) {
                Log.v(TAG, "Could not talk to activity manager.", e)
            }
            return applicationContext.packageName == pkg
        }
    }
}
Loading