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

Commit 8128127f authored by Chris Li's avatar Chris Li
Browse files

Reland WM Extensions for all devices

The revert is because there is a prebuilt app using an outdated
unreleased sidecar API (from 2022).

1. Guard SidecarProvider with flag as well.
2. Catch the AbstractMethodError for SidecarCallback access.

Bug: 306666082
Test: pass the failure test
Change-Id: Ic1535791d93d384f03b46e7ca9b0f2db0f84a01a
parent 139426f7
Loading
Loading
Loading
Loading
+57 −5
Original line number Diff line number Diff line
@@ -93,8 +93,12 @@ import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.ActivityTaskManager;
import android.app.ActivityThread;
import android.app.KeyguardManager;
import android.app.Presentation;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ComponentName;
@@ -1416,23 +1420,71 @@ public interface WindowManager extends ViewManager {
    public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";

    /**
     * Whether the device supports the WindowManager Extensions.
     * OEMs can enable this by having their device config to inherit window_extensions.mk, such as:
     * Whether the WindowManager Extensions - Activity Embedding feature should be guarded by
     * the app's target SDK on Android 15.
     *
     * WindowManager Extensions are only required for foldable and large screen before Android 15,
     * so we want to guard the Activity Embedding feature since it can have app compat impact on
     * devices with a compact size display.
     *
     * <p>If {@code true}, the feature is only enabled if the app's target SDK is Android 15 or
     * above.
     *
     * <p>If {@code false}, the feature is enabled for all apps.
     *
     * <p>The default value is {@code true}. OEMs can set to {@code false} by having their device
     * config to inherit window_extensions.mk. This is also required for large screen devices.
     * <pre>
     * $(call inherit-product, $(SRC_TARGET_DIR)/product/window_extensions.mk)
     * </pre>
     *
     * @hide
     */
    boolean ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15 = SystemProperties.getBoolean(
            "persist.wm.extensions.activity_embedding_guard_with_android_15", true);

    /**
     * For devices with {@link #ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15} as {@code true},
     * the Activity Embedding feature is enabled if the app's target SDK is Android 15+.
     *
     * @see #ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15
     * @hide
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
    long ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15 = 306666082L;

    /**
     * Whether the device contains the WindowManager Extensions shared library.
     * This is enabled for all devices through window_extensions_base.mk, but can be dropped if the
     * device doesn't support multi window.
     *
     * <p>Note: Large screen devices must also inherit window_extensions.mk to enable the Activity
     * Embedding feature by default for all apps.
     *
     * @see #ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15
     * @hide
     */
    boolean WINDOW_EXTENSIONS_ENABLED =
    boolean HAS_WINDOW_EXTENSIONS_ON_DEVICE =
            SystemProperties.getBoolean("persist.wm.extensions.enabled", false);

    /**
     * @see #WINDOW_EXTENSIONS_ENABLED
     * Whether the WindowManager Extensions are enabled.
     * If {@code false}, the WM Jetpack will report most of its features as disabled.
     * @see #HAS_WINDOW_EXTENSIONS_ON_DEVICE
     * @hide
     */
    @TestApi
    static boolean hasWindowExtensionsEnabled() {
        return WINDOW_EXTENSIONS_ENABLED;
        return HAS_WINDOW_EXTENSIONS_ON_DEVICE
                && ActivityTaskManager.supportsMultiWindow(ActivityThread.currentApplication())
                // Since enableWmExtensionsForAllFlag, HAS_WINDOW_EXTENSIONS_ON_DEVICE is now true
                // on all devices by default as a build file property.
                // Until finishing flag ramp up, only return true when
                // ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15 is false, which is set per device by
                // OEMs.
                && (Flags.enableWmExtensionsForAllFlag()
                || !ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15);
    }

    /**
+1 −1
Original line number Diff line number Diff line
@@ -400,7 +400,7 @@ public class ZygoteInit {
        // WindowManager Extensions is an optional shared library that is required for WindowManager
        // Jetpack to fully function. Since it is a widely used library, preload it to improve apps
        // startup performance.
        if (WindowManager.hasWindowExtensionsEnabled()) {
        if (WindowManager.HAS_WINDOW_EXTENSIONS_ON_DEVICE) {
            final String systemExtFrameworkPath =
                    new File(Environment.getSystemExtDirectory(), "framework").getPath();
            libs.add(new SharedLibraryInfo(
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.view;

import static com.android.window.flags.Flags.FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;

import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Tests for the {@link WindowManager}.
 *
 * Build/Install/Run:
 *  atest FrameworksCoreTests:WindowManagerTests
 */
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public class WindowManagerTests {

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Test
    public void testHasWindowExtensionsEnabled_flagDisabled() {
        mSetFlagsRule.disableFlags(FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG);

        // Before FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG, Extensions are always bundled with AE.
        assertEquals(isActivityEmbeddingEnableForAll(),
                WindowManager.hasWindowExtensionsEnabled());
    }

    @Test
    public void testHasWindowExtensionsEnabled_flagEnabled() {
        mSetFlagsRule.enableFlags(FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG);

        // Extensions should be enabled on all devices.
        assertTrue(WindowManager.hasWindowExtensionsEnabled());
    }

    @Test
    public void testActivityEmbeddingAvailability() {
        assumeTrue(isActivityEmbeddingEnableForAll());

        // AE can only be enabled when extensions is enabled.
        assertTrue(WindowManager.hasWindowExtensionsEnabled());
    }

    private static boolean isActivityEmbeddingEnableForAll() {
        return !WindowManager.ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15;
    }
}
+63 −26
Original line number Diff line number Diff line
@@ -16,15 +16,19 @@

package androidx.window.extensions;

import android.app.ActivityTaskManager;
import static android.view.WindowManager.ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15;
import static android.view.WindowManager.ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15;

import android.app.ActivityThread;
import android.app.Application;
import android.app.compat.CompatChanges;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.RawFoldingFeatureProducer;
import androidx.window.extensions.area.WindowAreaComponent;
@@ -38,25 +42,38 @@ import java.util.Objects;


/**
 * The reference implementation of {@link WindowExtensions} that implements the initial API version.
 * The reference implementation of {@link WindowExtensions} that implements the latest WindowManager
 * Extensions APIs.
 */
public class WindowExtensionsImpl implements WindowExtensions {
class WindowExtensionsImpl implements WindowExtensions {

    private static final String TAG = "WindowExtensionsImpl";

    /**
     * The min version of the WM Extensions that must be supported in the current platform version.
     */
    @VisibleForTesting
    static final int EXTENSIONS_VERSION_CURRENT_PLATFORM = 5;

    private final Object mLock = new Object();
    private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer;
    private volatile WindowLayoutComponentImpl mWindowLayoutComponent;
    private volatile SplitController mSplitController;
    private volatile WindowAreaComponent mWindowAreaComponent;

    public WindowExtensionsImpl() {
        Log.i(TAG, "Initializing Window Extensions.");
    private final int mVersion = EXTENSIONS_VERSION_CURRENT_PLATFORM;
    private final boolean mIsActivityEmbeddingEnabled;

    WindowExtensionsImpl() {
        mIsActivityEmbeddingEnabled = isActivityEmbeddingEnabled();
        Log.i(TAG, "Initializing Window Extensions, vendor API level=" + mVersion
                + ", activity embedding enabled=" + mIsActivityEmbeddingEnabled);
    }

    // TODO(b/241126279) Introduce constants to better version functionality
    @Override
    public int getVendorApiLevel() {
        return 5;
        return mVersion;
    }

    @NonNull
@@ -74,8 +91,8 @@ public class WindowExtensionsImpl implements WindowExtensions {
        if (mFoldingFeatureProducer == null) {
            synchronized (mLock) {
                if (mFoldingFeatureProducer == null) {
                    Context context = getApplication();
                    RawFoldingFeatureProducer foldingFeatureProducer =
                    final Context context = getApplication();
                    final RawFoldingFeatureProducer foldingFeatureProducer =
                            new RawFoldingFeatureProducer(context);
                    mFoldingFeatureProducer =
                            new DeviceStateManagerFoldingFeatureProducer(context,
@@ -91,8 +108,8 @@ public class WindowExtensionsImpl implements WindowExtensions {
        if (mWindowLayoutComponent == null) {
            synchronized (mLock) {
                if (mWindowLayoutComponent == null) {
                    Context context = getApplication();
                    DeviceStateManagerFoldingFeatureProducer producer =
                    final Context context = getApplication();
                    final DeviceStateManagerFoldingFeatureProducer producer =
                            getFoldingFeatureProducer();
                    mWindowLayoutComponent = new WindowLayoutComponentImpl(context, producer);
                }
@@ -102,29 +119,35 @@ public class WindowExtensionsImpl implements WindowExtensions {
    }

    /**
     * Returns a reference implementation of {@link WindowLayoutComponent} if available,
     * {@code null} otherwise. The implementation must match the API level reported in
     * {@link WindowExtensions#getWindowLayoutComponent()}.
     * Returns a reference implementation of the latest {@link WindowLayoutComponent}.
     *
     * The implementation must match the API level reported in
     * {@link WindowExtensions#getVendorApiLevel()}.
     *
     * @return {@link WindowLayoutComponent} OEM implementation
     */
    @NonNull
    @Override
    public WindowLayoutComponent getWindowLayoutComponent() {
        return getWindowLayoutComponentImpl();
    }

    /**
     * Returns a reference implementation of {@link ActivityEmbeddingComponent} if available,
     * {@code null} otherwise. The implementation must match the API level reported in
     * {@link WindowExtensions#getWindowLayoutComponent()}.
     * Returns a reference implementation of the latest {@link ActivityEmbeddingComponent} if the
     * device supports this feature, {@code null} otherwise.
     *
     * The implementation must match the API level reported in
     * {@link WindowExtensions#getVendorApiLevel()}.
     *
     * @return {@link ActivityEmbeddingComponent} OEM implementation.
     */
    @Nullable
    @Override
    public ActivityEmbeddingComponent getActivityEmbeddingComponent() {
        if (mSplitController == null) {
            if (!ActivityTaskManager.supportsMultiWindow(getApplication())) {
                // Disable AE for device that doesn't support multi window.
        if (!mIsActivityEmbeddingEnabled) {
            return null;
        }
        if (mSplitController == null) {
            synchronized (mLock) {
                if (mSplitController == null) {
                    mSplitController = new SplitController(
@@ -138,21 +161,35 @@ public class WindowExtensionsImpl implements WindowExtensions {
    }

    /**
     * Returns a reference implementation of {@link WindowAreaComponent} if available,
     * {@code null} otherwise. The implementation must match the API level reported in
     * {@link WindowExtensions#getWindowAreaComponent()}.
     * Returns a reference implementation of the latest {@link WindowAreaComponent}
     *
     * The implementation must match the API level reported in
     * {@link WindowExtensions#getVendorApiLevel()}.
     *
     * @return {@link WindowAreaComponent} OEM implementation.
     */
    @Nullable
    @Override
    public WindowAreaComponent getWindowAreaComponent() {
        if (mWindowAreaComponent == null) {
            synchronized (mLock) {
                if (mWindowAreaComponent == null) {
                    Context context = ActivityThread.currentApplication();
                    mWindowAreaComponent =
                            new WindowAreaComponentImpl(context);
                    final Context context = getApplication();
                    mWindowAreaComponent = new WindowAreaComponentImpl(context);
                }
            }
        }
        return mWindowAreaComponent;
    }

    @VisibleForTesting
    static boolean isActivityEmbeddingEnabled() {
        if (!ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15) {
            // Device enables it for all apps without targetSDK check.
            // This must be true for all large screen devices.
            return true;
        }
        // Use compat framework to guard the feature with targetSDK 15.
        return CompatChanges.isChangeEnabled(ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15);
    }
}
+46 −2
Original line number Diff line number Diff line
@@ -16,14 +16,20 @@

package androidx.window.extensions;

import android.annotation.NonNull;
import android.view.WindowManager;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.window.extensions.area.WindowAreaComponent;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.layout.WindowLayoutComponent;

/**
 * Provides the OEM implementation of {@link WindowExtensions}.
 */
public class WindowExtensionsProvider {

    private static final WindowExtensions sWindowExtensions = new WindowExtensionsImpl();
    private static volatile WindowExtensions sWindowExtensions;

    /**
     * Returns the OEM implementation of {@link WindowExtensions}. This method is implemented in
@@ -33,6 +39,44 @@ public class WindowExtensionsProvider {
     */
    @NonNull
    public static WindowExtensions getWindowExtensions() {
        if (sWindowExtensions == null) {
            synchronized (WindowExtensionsProvider.class) {
                if (sWindowExtensions == null) {
                    sWindowExtensions = WindowManager.hasWindowExtensionsEnabled()
                            ? new WindowExtensionsImpl()
                            : new DisabledWindowExtensions();
                }
            }
        }
        return sWindowExtensions;
    }

    /**
     * The stub version to return when the WindowManager Extensions is disabled
     * @see WindowManager#hasWindowExtensionsEnabled
     */
    private static class DisabledWindowExtensions implements WindowExtensions {
        @Override
        public int getVendorApiLevel() {
            return 0;
        }

        @Nullable
        @Override
        public WindowLayoutComponent getWindowLayoutComponent() {
            return null;
        }

        @Nullable
        @Override
        public ActivityEmbeddingComponent getActivityEmbeddingComponent() {
            return null;
        }

        @Nullable
        @Override
        public WindowAreaComponent getWindowAreaComponent() {
            return null;
        }
    }
}
Loading