强制使用桌面模式 - SECONDARY_HOME -android-15.0.0_r23

强制使用桌面模式 - SECONDARY_HOME -android-15.0.0_r23

  • [1. 设置层:UI 开关与全局数据库写入](#1. 设置层:UI 开关与全局数据库写入)
  • [2. Framework 层](#2. Framework 层)
    • [2.1 DisplayContent 中 isHomeSupported()](#2.1 DisplayContent 中 isHomeSupported())
    • [2.2 RootWindowContainer 在副屏启动SECONDARY_HOME桌面](#2.2 RootWindowContainer 在副屏启动SECONDARY_HOME桌面)
  • [3. SECONDARY_HOME的桌面Activity](#3. SECONDARY_HOME的桌面Activity)

1. 设置层:UI 开关与全局数据库写入

"开发者选项" > "强制使用桌面模式" 开关

packages/apps/Settings/res/xml/development_settings.xml

packages/apps/Settings/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java

  • UI 入口:在 Settings 应用 development_settings.xml 布局中 force_desktop_mode_on_external_displays
  • 数据库写入:开关状态被写入 Settings.Global 表,public static final String DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS = "force_desktop_mode_on_external_displays";
java 复制代码
    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        final boolean isEnabled = (Boolean) newValue;
        Settings.Global.putInt(mContext.getContentResolver(),
                DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
                isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
        // Update freeform window support on device.
        // DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT setting enables freeform support on device
        // where it's not present by default.
        Settings.Global.putInt(mContext.getContentResolver(),
                Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
                isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
        if (isEnabled && mFragment != null) {
            RebootConfirmationDialogFragment.show(
                    mFragment, R.string.reboot_dialog_enable_desktop_mode_on_secondary_display,
                    this);
        }
        return true;
    }

2. Framework 层

2.1 DisplayContent 中 isHomeSupported()

DisplayContentisHomeSupported()

  • mWmService.mDisplayWindowSettings.isHomeSupportedLocked(this) 其中settings.mShouldShowSystemDecors添加有display_settings.xml文件配置属性shouldShowSystemDecors="false"
  • isTrusted()LocalDisplayAdapter.javagetDisplayDeviceInfoLocked()获取mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
  • isSystemDecorationsSupported() 调用isSystemDecorationsSupported() - isPublicSecondaryDisplayWithDesktopModeForceEnabled()mWmService.mForceDesktopModeOnExternalDisplays对应mForceDesktopModeOnExternalDisplays = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;

frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

java 复制代码
    @Nullable ComponentName getCustomHomeComponent() {
        if (!isHomeSupported() || mDwpcHelper == null) {
            return null;
        }
        return mDwpcHelper.getCustomHomeComponent();
    }
    
    boolean isSystemDecorationsSupported() {
        if (mDisplayId == mWmService.mVr2dDisplayId) {
            // VR virtual display will be used to run and render 2D app within a VR experience.
            return false;
        }
        if (!isTrusted()) {
            // Do not show system decorations on untrusted virtual display.
            return false;
        }
        if (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
                || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
            // This display is configured to show system decorations.
            return true;
        }
        if (isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
            if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
                // System decorations should not be forced on a rear display due to security
                // policies.
                return (mDisplay.getFlags() & Display.FLAG_REAR) == 0;
            }
            // If the display is forced to desktop mode, treat it the same as it is configured to
            // show system decorations.
            return true;
        }
        return false;
    }

    /**
     * This is the development option to force enable desktop mode on all secondary public displays
     * that are not owned by a virtual device.
     * When this is enabled, it also force enable system decorations on those displays.
     *
     * If we need a per-display config to enable desktop mode for production, that config should
     * also check {@link #isSystemDecorationsSupported()} to avoid breaking any security policy.
     */
    boolean isPublicSecondaryDisplayWithDesktopModeForceEnabled() {
        if (!mWmService.mForceDesktopModeOnExternalDisplays || isDefaultDisplay || isPrivate()) {
            return false;
        }
        // Desktop mode is not supported on virtual devices.
        int deviceId = mRootWindowContainer.mTaskSupervisor.getDeviceIdForDisplayId(mDisplayId);
        return deviceId == Context.DEVICE_ID_DEFAULT;
    }

    /**
     * Checks if this display is configured and allowed to show home activity and wallpaper.
     *
     * <p>This is implied for displays that have {@link Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}
     * and can also be set via {@link VirtualDisplayConfig.Builder#setHomeSupported}.</p>
     */
    boolean isHomeSupported() {
        return (mWmService.mDisplayWindowSettings.isHomeSupportedLocked(this) && isTrusted())
                || isSystemDecorationsSupported();
    }

2.2 RootWindowContainer 在副屏启动SECONDARY_HOME桌面

startHomeOnTaskDisplayArea

  • shouldPlaceSecondaryHomeOnDisplayArea 最终会判断display.isHomeSupported()是否支持
  • resolveSecondaryHomeActivity 中调用resolveSecondaryHomeActivity获取getSecondaryHomeIntent包含Intent.CATEGORY_SECONDARY_HOME的桌面

frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

java 复制代码
    boolean startHomeOnTaskDisplayArea(int userId, String reason, TaskDisplayArea taskDisplayArea,
            boolean allowInstrumenting, boolean fromHomeKey) {
        // Fallback to top focused display area if the provided one is invalid.
        if (taskDisplayArea == null) {
            final Task rootTask = getTopDisplayFocusedRootTask();
            taskDisplayArea = rootTask != null ? rootTask.getDisplayArea()
                    : getDefaultTaskDisplayArea();
        }

        Intent homeIntent = null;
        ActivityInfo aInfo = null;
        if (taskDisplayArea == getDefaultTaskDisplayArea()
                || mWmService.shouldPlacePrimaryHomeOnDisplay(
                        taskDisplayArea.getDisplayId(), userId)) {
            homeIntent = mService.getHomeIntent();
            aInfo = resolveHomeActivity(userId, homeIntent);
        } else if (shouldPlaceSecondaryHomeOnDisplayArea(taskDisplayArea)) {
            Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, taskDisplayArea);
            aInfo = info.first;
            homeIntent = info.second;
        }

        if (aInfo == null || homeIntent == null) {
            return false;
        }

        if (!canStartHomeOnDisplayArea(aInfo, taskDisplayArea, allowInstrumenting)) {
            return false;
        }

        if (mService.mAmInternal.shouldDelayHomeLaunch(userId)) {
            Slog.d(TAG, "ThemeHomeDelay: Home launch was deferred with user " + userId);
            return false;
        }

        // Updates the home component of the intent.
        homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
        homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
        // Updates the extra information of the intent.
        if (fromHomeKey) {
            homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
        }
        homeIntent.putExtra(WindowManagerPolicy.EXTRA_START_REASON, reason);

        // Update the reason for ANR debugging to verify if the user activity is the one that
        // actually launched.
        final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId(
                aInfo.applicationInfo.uid) + ":" + taskDisplayArea.getDisplayId();
        mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason,
                taskDisplayArea);
        return true;
    }

    /**
     * This resolves the home activity info.
     *
     * @return the home activity info if any.
     */
    @VisibleForTesting
    ActivityInfo resolveHomeActivity(int userId, Intent homeIntent) {
        final int flags = ActivityManagerService.STOCK_PM_FLAGS;
        final ComponentName comp = homeIntent.getComponent();
        ActivityInfo aInfo = null;
        try {
            if (comp != null) {
                // Factory test.
                aInfo = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId);
            } else {
                final String resolvedType =
                        homeIntent.resolveTypeIfNeeded(mService.mContext.getContentResolver());
                final ResolveInfo info = mTaskSupervisor.resolveIntent(homeIntent, resolvedType,
                        userId, flags, Binder.getCallingUid(), Binder.getCallingPid());
                if (info != null) {
                    aInfo = info.activityInfo;
                }
            }
        } catch (RemoteException e) {
            // ignore
        }

        if (aInfo == null) {
            Slogf.wtf(TAG, new Exception(), "No home screen found for %s and user %d", homeIntent,
                    userId);
            return null;
        }

        aInfo = new ActivityInfo(aInfo);
        aInfo.applicationInfo = mService.getAppInfoForUser(aInfo.applicationInfo, userId);
        return aInfo;
    }

    @VisibleForTesting
    Pair<ActivityInfo, Intent> resolveSecondaryHomeActivity(int userId,
            @NonNull TaskDisplayArea taskDisplayArea) {
        if (taskDisplayArea == getDefaultTaskDisplayArea()) {
            throw new IllegalArgumentException(
                    "resolveSecondaryHomeActivity: Should not be default task container");
        }

        Intent homeIntent = mService.getHomeIntent();
        ActivityInfo aInfo = resolveHomeActivity(userId, homeIntent);
        boolean lookForSecondaryHomeActivityInPrimaryHomePackage = aInfo != null;

        if (android.companion.virtual.flags.Flags.vdmCustomHome()) {
            // Resolve the externally set home activity for this display, if any. If it is unset or
            // we fail to resolve it, fallback to the default secondary home activity.
            final ComponentName customHomeComponent =
                    taskDisplayArea.getDisplayContent() != null
                            ? taskDisplayArea.getDisplayContent().getCustomHomeComponent()
                            : null;
            if (customHomeComponent != null) {
                homeIntent.setComponent(customHomeComponent);
                ActivityInfo customHomeActivityInfo = resolveHomeActivity(userId, homeIntent);
                if (customHomeActivityInfo != null) {
                    aInfo = customHomeActivityInfo;
                    lookForSecondaryHomeActivityInPrimaryHomePackage = false;
                }
            }
        }

        if (lookForSecondaryHomeActivityInPrimaryHomePackage) {
            // Resolve activities in the same package as currently selected primary home activity.
            if (ResolverActivity.class.getName().equals(aInfo.name)) {
                // Always fallback to secondary home component if default home is not set.
                aInfo = null;
            } else {
                // Look for secondary home activities in the currently selected default home
                // package.
                homeIntent = mService.getSecondaryHomeIntent(aInfo.applicationInfo.packageName);
                final List<ResolveInfo> resolutions = resolveActivities(userId, homeIntent);
                final int size = resolutions.size();
                final String targetName = aInfo.name;
                aInfo = null;
                for (int i = 0; i < size; i++) {
                    ResolveInfo resolveInfo = resolutions.get(i);
                    // We need to traverse all resolutions to check if the currently selected
                    // default home activity is present.
                    if (resolveInfo.activityInfo.name.equals(targetName)) {
                        aInfo = resolveInfo.activityInfo;
                        break;
                    }
                }
                if (aInfo == null && size > 0) {
                    // First one is the best.
                    aInfo = resolutions.get(0).activityInfo;
                }
            }
        }

        if (aInfo != null) {
            if (!canStartHomeOnDisplayArea(aInfo, taskDisplayArea,
                    false /* allowInstrumenting */)) {
                aInfo = null;
            }
        }

        // Fallback to secondary home component.
        if (aInfo == null) {
            homeIntent = mService.getSecondaryHomeIntent(null);
            aInfo = resolveHomeActivity(userId, homeIntent);
        }
        return Pair.create(aInfo, homeIntent);
    }

    /**
     * Retrieve all activities that match the given intent.
     * The list should already ordered from best to worst matched.
     * {@link android.content.pm.PackageManager#queryIntentActivities}
     */
    @VisibleForTesting
    List<ResolveInfo> resolveActivities(int userId, Intent homeIntent) {
        List<ResolveInfo> resolutions;
        try {
            final String resolvedType =
                    homeIntent.resolveTypeIfNeeded(mService.mContext.getContentResolver());
            resolutions = AppGlobals.getPackageManager().queryIntentActivities(homeIntent,
                    resolvedType, ActivityManagerService.STOCK_PM_FLAGS, userId).getList();

        } catch (RemoteException e) {
            resolutions = new ArrayList<>();
        }
        return resolutions;
    }

    boolean resumeHomeActivity(ActivityRecord prev, String reason,
            TaskDisplayArea taskDisplayArea) {
        if (!mService.isBooting() && !mService.isBooted()) {
            // Not ready yet!
            return false;
        }

        if (taskDisplayArea == null) {
            taskDisplayArea = getDefaultTaskDisplayArea();
        }

        final ActivityRecord r = taskDisplayArea.getHomeActivity();
        final String myReason = reason + " resumeHomeActivity";

        // Only resume home activity if isn't finishing.
        if (r != null && !r.finishing) {
            r.moveFocusableActivityToTop(myReason);
            return resumeFocusedTasksTopActivities(r.getRootTask(), prev);
        }
        int userId = mWmService.getUserAssignedToDisplay(taskDisplayArea.getDisplayId());
        return startHomeOnTaskDisplayArea(userId, myReason, taskDisplayArea,
                false /* allowInstrumenting */, false /* fromHomeKey */);
    }

    /**
     * Check if the display is valid for primary home activity.
     *
     * @param displayId The target display ID
     * @return {@code true} if allowed to launch, {@code false} otherwise.
     */
    boolean shouldPlacePrimaryHomeOnDisplay(int displayId) {
        // No restrictions to default display, vr 2d display or main display for visible users.
        return displayId == DEFAULT_DISPLAY || (displayId != INVALID_DISPLAY
                && (displayId == mService.mVr2dDisplayId
                || mWmService.shouldPlacePrimaryHomeOnDisplay(displayId)));
    }

    /**
     * Check if the display area is valid for secondary home activity.
     *
     * @param taskDisplayArea The target display area.
     * @return {@code true} if allow to launch, {@code false} otherwise.
     */
    boolean shouldPlaceSecondaryHomeOnDisplayArea(TaskDisplayArea taskDisplayArea) {
        if (getDefaultTaskDisplayArea() == taskDisplayArea) {
            throw new IllegalArgumentException(
                    "shouldPlaceSecondaryHomeOnDisplay: Should not be on default task container");
        } else if (taskDisplayArea == null) {
            return false;
        }

        if (!taskDisplayArea.canHostHomeTask()) {
            // Can't launch home on a TaskDisplayArea that does not support root home task
            return false;
        }

        if (taskDisplayArea.getDisplayId() != DEFAULT_DISPLAY && !mService.mSupportsMultiDisplay) {
            // Can't launch home on secondary display if device does not support multi-display.
            return false;
        }

        final boolean deviceProvisioned = Settings.Global.getInt(
                mService.mContext.getContentResolver(),
                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
        if (!deviceProvisioned) {
            // Can't launch home on secondary display areas before device is provisioned.
            return false;
        }

        if (!StorageManager.isCeStorageUnlocked(mCurrentUser)) {
            // Can't launch home on secondary display areas if CE storage is still locked.
            return false;
        }

        final DisplayContent display = taskDisplayArea.getDisplayContent();
        if (display == null || display.isRemoved() || !display.isHomeSupported()) {
            // Can't launch home on display that doesn't support home.
            return false;
        }

        return true;
    }

    /**
     * Check if home activity start should be allowed on a {@link TaskDisplayArea}.
     *
     * @param homeInfo           {@code ActivityInfo} of the home activity that is going to be
     *                           launched.
     * @param taskDisplayArea    The target display area.
     * @param allowInstrumenting Whether launching home should be allowed if being instrumented.
     * @return {@code true} if allow to launch, {@code false} otherwise.
     */
    boolean canStartHomeOnDisplayArea(ActivityInfo homeInfo, TaskDisplayArea taskDisplayArea,
            boolean allowInstrumenting) {
        if (mService.mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
                && mService.mTopAction == null) {
            // We are running in factory test mode, but unable to find the factory test app, so
            // just sit around displaying the error message and don't try to start anything.
            return false;
        }

        final WindowProcessController app =
                mService.getProcessController(homeInfo.processName, homeInfo.applicationInfo.uid);
        if (!allowInstrumenting && app != null && app.isInstrumenting()) {
            // Don't do this if the home app is currently being instrumented.
            return false;
        }

        if (taskDisplayArea != null && !taskDisplayArea.canHostHomeTask()) {
            return false;
        }

        final int displayId = taskDisplayArea != null ? taskDisplayArea.getDisplayId()
                : INVALID_DISPLAY;
        if (shouldPlacePrimaryHomeOnDisplay(displayId)) {
            return true;
        }

        if (!shouldPlaceSecondaryHomeOnDisplayArea(taskDisplayArea)) {
            return false;
        }

        final boolean supportMultipleInstance = homeInfo.launchMode != LAUNCH_SINGLE_TASK
                && homeInfo.launchMode != LAUNCH_SINGLE_INSTANCE;
        if (!supportMultipleInstance) {
            // Can't launch home on secondary displays if it requested to be single instance.
            return false;
        }

        return true;
    }

frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

java 复制代码
    /**
     * Return the intent set with {@link Intent#CATEGORY_SECONDARY_HOME} to resolve secondary home
     * activities.
     *
     * @param preferredPackage Specify a preferred package name, otherwise use the package name
     *                         defined in config_secondaryHomePackage.
     * @return the intent set with {@link Intent#CATEGORY_SECONDARY_HOME}
     */
    Intent getSecondaryHomeIntent(String preferredPackage) {
        final Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
        final boolean useSystemProvidedLauncher = mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_useSystemProvidedLauncherForSecondary);
        if (preferredPackage == null || useSystemProvidedLauncher) {
            // Using the package name stored in config if no preferred package name or forced.
            final String secondaryHomePackage = mContext.getResources().getString(
                    com.android.internal.R.string.config_secondaryHomePackage);
            intent.setPackage(secondaryHomePackage);
        } else {
            intent.setPackage(preferredPackage);
        }
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
            intent.addCategory(Intent.CATEGORY_SECONDARY_HOME);
        }
        return intent;
    }

3. SECONDARY_HOME的桌面Activity

packages/apps/Launcher3/AndroidManifest-common.xml

xml 复制代码
        <!--
        Launcher activity for secondary display
        -->
        <activity
            android:name="com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher"
            android:theme="@style/AppTheme"
            android:launchMode="singleTop"
            android:exported="true"
            android:enabled="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.SECONDARY_HOME" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
相关推荐
_祝你今天愉快11 小时前
从驱动来分析服务的添加过程
android
Dovis(誓平步青云)14 小时前
折叠屏悬停看视频,上半屏和下半屏应该各做什么
android·java·服务器·开发语言·安全·音视频
alexhilton15 小时前
让架构边界变成可执行的测试
android·kotlin·android jetpack
hai_android16 小时前
SendChannel 与 ReceiveChannel 通信机制
android
数据治理自习室18 小时前
AI 应用评测体系(GraphRAG)
android·大数据·人工智能·kotlin
爱笑鱼19 小时前
Android 系统启动机制(五):system_server 是 init 启动的,还是 Zygote fork 出来的?
android
智购科技无人售货机工厂20 小时前
2026自动售货机防拆机物理安全设计:从安全螺丝到结构互锁的工程实践~YH
android·网络·驱动开发·python·单片机·安全·云原生
开开心心就好20 小时前
电子教鞭工具支持画框写字插图片功能齐全
android·开发语言·前端·javascript·人工智能·pdf·html
Dovis(誓平步青云)20 小时前
拍视频前先把镜头想清楚:做一个分镜取景辅助器
android·java·服务器·javascript·人工智能