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

Commit 7d93e0e5 authored by Andrii Kulian's avatar Andrii Kulian Committed by Diego Vela
Browse files

Update reference WM layout component interface

Moved to androidx.window.extensions.layout package.
Removed ExtensionProvider.
Updated method signatures.

Bug: 190433972
Test: Manual, using the sample app from AndroidX
Change-Id: I6c98ced7b447f231455723a8dc601a46653a48de
parent 11f14b1e
Loading
Loading
Loading
Loading
+0 −72
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 androidx.window.extensions;

import android.app.Activity;

import androidx.annotation.NonNull;

import java.util.HashSet;
import java.util.Set;

/**
 * Basic implementation of the {@link ExtensionInterface}. An OEM can choose to use it as the base
 * class for their implementation.
 */
abstract class StubExtension implements ExtensionInterface {

    private ExtensionCallback mExtensionCallback;
    private final Set<Activity> mWindowLayoutChangeListenerActivities = new HashSet<>();

    StubExtension() {
    }

    @Override
    public void setExtensionCallback(@NonNull ExtensionCallback extensionCallback) {
        this.mExtensionCallback = extensionCallback;
    }

    @Override
    public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) {
        this.mWindowLayoutChangeListenerActivities.add(activity);
        this.onListenersChanged();
    }

    @Override
    public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) {
        this.mWindowLayoutChangeListenerActivities.remove(activity);
        this.onListenersChanged();
    }

    void updateWindowLayout(@NonNull Activity activity,
            @NonNull ExtensionWindowLayoutInfo newLayout) {
        if (this.mExtensionCallback != null) {
            mExtensionCallback.onWindowLayoutChanged(activity, newLayout);
        }
    }

    @NonNull
    Set<Activity> getActivitiesListeningForLayoutChanges() {
        return mWindowLayoutChangeListenerActivities;
    }

    protected boolean hasListeners() {
        return !mWindowLayoutChangeListenerActivities.isEmpty();
    }

    protected abstract void onListenersChanged();
}
+65 −0
Original line number Diff line number Diff line
@@ -14,38 +14,52 @@
 * limitations under the License.
 */

package androidx.window.extensions;
package androidx.window.extensions.embedding;

import android.content.Context;

import androidx.annotation.NonNull;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.organizer.EmbeddingExtensionImpl;
import androidx.window.extensions.organizer.SplitController;

/**
 * Provider class that will instantiate the library implementation. It must be included in the
 * vendor library, and the vendor implementation must match the signature of this class.
 * Provider for the reference implementation of androidx.window.extensions.embedding OEM interface
 * for use with WindowManager Jetpack.
 */
public class ExtensionProvider {
    /**
     * Provides a simple implementation of {@link ExtensionInterface} that can be replaced by
     * an OEM by overriding this method.
     */
    public static ExtensionInterface getExtensionImpl(Context context) {
        return new SampleExtensionImpl(context);
public class ActivityEmbeddingComponentProvider {
    private static SplitController sInstance;

    private ActivityEmbeddingComponentProvider() {
    }

    /** Provides a reference implementation of {@link ActivityEmbeddingComponent}. */
    public static ActivityEmbeddingComponent getActivityEmbeddingExtensionImpl(
            @NonNull Context context) {
        return new EmbeddingExtensionImpl();
    /**
     * Returns {@code true} if {@link ActivityEmbeddingComponent} is present on the device,
     * {@code false} otherwise. If the component is not available the developer will receive a
     * single callback with empty data or default values where possible.
     */
    public static boolean isEmbeddingComponentAvailable() {
        return true;
    }

    /**
     * The support library will use this method to check API version compatibility.
     * @return API version string in MAJOR.MINOR.PATCH-description format.
     * Returns the OEM implementation of {@link ActivityEmbeddingComponent} if it is supported on
     * the device. The implementation must match the API level reported in
     * {@link androidx.window.extensions.WindowLibraryInfo}. An
     * {@link UnsupportedOperationException} will be thrown if the device does not support
     * Activity Embedding. Use
     * {@link ActivityEmbeddingComponentProvider#isEmbeddingComponentAvailable()} to determine if
     * {@link ActivityEmbeddingComponent} is present.
     * @return the OEM implementation of {@link ActivityEmbeddingComponent}
     */
    public static String getApiVersion() {
        return "1.0.0-settings_sample";
    @NonNull
    public static ActivityEmbeddingComponent getActivityEmbeddingComponent(
            @NonNull Context context) {
        if (sInstance == null) {
            synchronized (ActivityEmbeddingComponentProvider.class) {
                if (sInstance == null) {
                    sInstance = new SplitController();
                }
            }
        }
        return sInstance;
    }
}
+77 −14
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@
 * limitations under the License.
 */

package androidx.window.extensions;
package androidx.window.extensions.layout;

import static android.view.Display.DEFAULT_DISPLAY;

@@ -27,6 +27,7 @@ import android.graphics.Rect;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.window.common.DeviceStateManagerPostureProducer;
import androidx.window.common.DisplayFeature;
import androidx.window.common.ResourceConfigDisplayFeatureProducer;
@@ -36,19 +37,27 @@ import androidx.window.util.DataProducer;
import androidx.window.util.PriorityDataProducer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;

/**
 * Reference implementation of androidx.window.extensions OEM interface for use with
 * Reference implementation of androidx.window.extensions.layout OEM interface for use with
 * WindowManager Jetpack.
 *
 * NOTE: This version is a work in progress and under active development. It MUST NOT be used in
 * production builds since the interface can still change before reaching stable version.
 * Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead.
 */
class SampleExtensionImpl extends StubExtension {
public class WindowLayoutComponent {
    private static final String TAG = "SampleExtension";
    private static WindowLayoutComponent sInstance;

    private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
            new HashMap<>();

    private final SettingsDevicePostureProducer mSettingsDevicePostureProducer;
    private final DataProducer<Integer> mDevicePostureProducer;
@@ -56,7 +65,7 @@ class SampleExtensionImpl extends StubExtension {
    private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
    private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer;

    SampleExtensionImpl(Context context) {
    private WindowLayoutComponent(Context context) {
        mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context);
        mDevicePostureProducer = new PriorityDataProducer<>(List.of(
                mSettingsDevicePostureProducer,
@@ -73,28 +82,83 @@ class SampleExtensionImpl extends StubExtension {
        mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
    }

    /**
     * Returns a shared instance.
     */
    @Nullable
    public static WindowLayoutComponent getInstance(@NonNull Context context) {
        if (sInstance == null) {
            synchronized (WindowLayoutComponent.class) {
                if (sInstance == null) {
                    sInstance = new WindowLayoutComponent(context);
                }
            }
        }
        return sInstance;
    }

    /**
     * Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
     * @param activity hosting a {@link android.view.Window}
     * @param consumer interested in receiving updates to {@link WindowLayoutInfo}
     */
    public void addWindowLayoutInfoListener(@NonNull Activity activity,
            @NonNull Consumer<WindowLayoutInfo> consumer) {
        mWindowLayoutChangeListeners.put(activity, consumer);
        updateRegistrations();
    }

    /**
     * Removes a listener no longer interested in receiving updates.
     * @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
     */
    public void removeWindowLayoutInfoListener(
            @NonNull Consumer<WindowLayoutInfo> consumer) {
        mWindowLayoutChangeListeners.values().remove(consumer);
        updateRegistrations();
    }

    void updateWindowLayout(@NonNull Activity activity,
            @NonNull WindowLayoutInfo newLayout) {
        Consumer<WindowLayoutInfo> consumer = mWindowLayoutChangeListeners.get(activity);
        if (consumer != null) {
            consumer.accept(newLayout);
        }
    }

    @NonNull
    Set<Activity> getActivitiesListeningForLayoutChanges() {
        return mWindowLayoutChangeListeners.keySet();
    }

    protected boolean hasListeners() {
        return !mWindowLayoutChangeListeners.isEmpty();
    }

    private int getFeatureState(DisplayFeature feature) {
        Integer featureState = feature.getState();
        Optional<Integer> posture = mDevicePostureProducer.getData();
        int fallbackPosture = posture.orElse(ExtensionFoldingFeature.STATE_FLAT);
        int fallbackPosture = posture.orElse(FoldingFeature.STATE_FLAT);
        return featureState == null ? fallbackPosture : featureState;
    }

    private void onDisplayFeaturesChanged() {
        for (Activity activity : getActivitiesListeningForLayoutChanges()) {
            ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
            WindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
            updateWindowLayout(activity, newLayout);
        }
    }

    @NonNull
    private ExtensionWindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
        List<ExtensionDisplayFeature> displayFeatures = getDisplayFeatures(activity);
        return new ExtensionWindowLayoutInfo(displayFeatures);
    private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
        List<androidx.window.extensions.layout.DisplayFeature> displayFeatures =
                getDisplayFeatures(activity);
        return new WindowLayoutInfo(displayFeatures);
    }

    private List<ExtensionDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
        List<ExtensionDisplayFeature> features = new ArrayList<>();
    private List<androidx.window.extensions.layout.DisplayFeature> getDisplayFeatures(
            @NonNull Activity activity) {
        List<androidx.window.extensions.layout.DisplayFeature> features = new ArrayList<>();
        int displayId = activity.getDisplay().getDisplayId();
        if (displayId != DEFAULT_DISPLAY) {
            Log.w(TAG, "This sample doesn't support display features on secondary displays");
@@ -115,15 +179,14 @@ class SampleExtensionImpl extends StubExtension {
                rotateRectToDisplayRotation(displayId, featureRect);
                transformToWindowSpaceRect(activity, featureRect);

                features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(),
                features.add(new FoldingFeature(featureRect, baseFeature.getType(),
                        getFeatureState(baseFeature)));
            }
        }
        return features;
    }

    @Override
    protected void onListenersChanged() {
    private void updateRegistrations() {
        if (hasListeners()) {
            mSettingsDevicePostureProducer.registerObserversIfNeeded();
            mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
+0 −48
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 androidx.window.extensions.organizer;

import androidx.annotation.NonNull;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.embedding.EmbeddingRule;
import androidx.window.extensions.embedding.SplitInfo;

import java.util.List;
import java.util.Set;
import java.util.function.Consumer;

/**
 * Reference implementation of the activity embedding interface defined in WM Jetpack.
 */
public class EmbeddingExtensionImpl implements ActivityEmbeddingComponent {

    private final SplitController mSplitController;

    public EmbeddingExtensionImpl() {
        mSplitController = new SplitController();
    }

    @Override
    public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
        mSplitController.setEmbeddingRules(rules);
    }

    @Override
    public void setEmbeddingCallback(@NonNull Consumer<List<SplitInfo>> consumer) {
        mSplitController.setEmbeddingCallback(consumer);
    }
}
+29 −12
Original line number Diff line number Diff line
@@ -31,18 +31,17 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Pair;
import android.window.TaskFragmentAppearedInfo;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;

import androidx.window.extensions.embedding.ActivityRule;
import androidx.window.extensions.embedding.ActivityStack;
import androidx.window.extensions.embedding.EmbeddingRule;
import androidx.window.extensions.embedding.SplitInfo;
import androidx.window.extensions.embedding.SplitPairRule;
import androidx.window.extensions.embedding.SplitPlaceholderRule;
import androidx.window.extensions.embedding.SplitRule;
import androidx.window.extensions.embedding.TaskFragment;

import java.util.ArrayList;
import java.util.List;
@@ -53,7 +52,8 @@ import java.util.function.Consumer;
/**
 * Main controller class that manages split states and presentation.
 */
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback {
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
        androidx.window.extensions.embedding.ActivityEmbeddingComponent {

    private final SplitPresenter mPresenter;

@@ -77,6 +77,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    }

    /** Updates the embedding rules applied to future activity launches. */
    @Override
    public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
        mSplitRules.clear();
        mSplitRules.addAll(rules);
@@ -103,7 +104,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    /**
     * Registers the split organizer callback to notify about changes to active splits.
     */
    public void setEmbeddingCallback(@NonNull Consumer<List<SplitInfo>> callback) {
    @Override
    public void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> callback) {
        mEmbeddingCallback = callback;
        updateCallbackIfNecessary();
    }
@@ -175,7 +177,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                launchedActivity.getActivityToken(), launchedActivity);

        // Check if the activity is configured to always be expanded.
        if (shouldExpand(launchedActivity, splitRules)) {
        if (shouldExpand(launchedActivity, null, splitRules)) {
            if (shouldContainerBeExpanded(currentContainer)) {
                // Make sure that the existing container is expanded
                mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken());
@@ -525,11 +527,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    private List<SplitInfo> getActiveSplitStates() {
        List<SplitInfo> splitStates = new ArrayList<>();
        for (SplitContainer container : mSplitContainers) {
            TaskFragment primaryContainer =
                    new TaskFragment(
            ActivityStack primaryContainer =
                    new ActivityStack(
                            container.getPrimaryContainer().collectActivities());
            TaskFragment secondaryContainer =
                    new TaskFragment(
            ActivityStack secondaryContainer =
                    new ActivityStack(
                            container.getSecondaryContainer().collectActivities());
            SplitInfo splitState = new SplitInfo(primaryContainer,
                    secondaryContainer, container.getSplitRule().getSplitRatio());
@@ -611,7 +613,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * Returns {@code true} if an Activity with the provided component name should always be
     * expanded to occupy full task bounds. Such activity must not be put in a split.
     */
    private static boolean shouldExpand(@NonNull Activity activity,
    private static boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent,
            List<EmbeddingRule> splitRules) {
        if (splitRules == null) {
            return false;
@@ -624,7 +626,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            if (!activityRule.shouldAlwaysExpand()) {
                continue;
            }
            if (activityRule.getActivityPredicate().test(activity)) {
            if (activity != null && activityRule.getActivityPredicate().test(activity)) {
                return true;
            }
            if (intent != null && activityRule.getIntentPredicate().test(intent)) {
                return true;
            }
        }
@@ -703,13 +708,25 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            }
            final Activity launchingActivity = (Activity) who;

            if (!setLaunchingToSideContainer(launchingActivity, intent, options)) {
            if (shouldExpand(null, intent, getSplitRules())) {
                setLaunchingInExpandedContainer(launchingActivity, options);
            } else if (!setLaunchingToSideContainer(launchingActivity, intent, options)) {
                setLaunchingInSameContainer(launchingActivity, intent, options);
            }

            return super.onStartActivity(who, intent, options);
        }

        private void setLaunchingInExpandedContainer(Activity launchingActivity, Bundle options) {
            TaskFragmentContainer newContainer = mPresenter.createNewExpandedContainer(
                    launchingActivity);

            // Amend the request to let the WM know that the activity should be placed in the
            // dedicated container.
            options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                    newContainer.getTaskFragmentToken());
        }

        /**
         * Returns {@code true} if the activity that is going to be started via the
         * {@code intent} should be paired with the {@code launchingActivity} and is set to be
Loading