diff options
17 files changed, 373 insertions, 267 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 34023a43ac60..f1fd8b7c035b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3616,6 +3616,11 @@ public final class ActivityThread extends ClientTransactionHandler } activity.mLaunchedFromBubble = r.mLaunchedFromBubble; activity.mCalled = false; + // Assigning the activity to the record before calling onCreate() allows + // ActivityThread#getActivity() lookup for the callbacks triggered from + // ActivityLifecycleCallbacks#onActivityCreated() or + // ActivityLifecycleCallback#onActivityPostCreated(). + r.activity = activity; if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { @@ -3626,7 +3631,6 @@ public final class ActivityThread extends ClientTransactionHandler "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onCreate()"); } - r.activity = activity; mLastReportedWindowingMode.put(activity.getActivityToken(), config.windowConfiguration.getWindowingMode()); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java deleted file mode 100644 index ce4e10364ba2..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.content.Context; - -import androidx.annotation.NonNull; -import androidx.window.extensions.embedding.ActivityEmbeddingComponent; -import androidx.window.extensions.organizer.EmbeddingExtensionImpl; - -/** - * 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. - */ -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); - } - - /** Provides a reference implementation of {@link ActivityEmbeddingComponent}. */ - public static ActivityEmbeddingComponent getActivityEmbeddingExtensionImpl( - @NonNull Context context) { - return new EmbeddingExtensionImpl(); - } - - /** - * The support library will use this method to check API version compatibility. - * @return API version string in MAJOR.MINOR.PATCH-description format. - */ - public static String getApiVersion() { - return "1.0.0-settings_sample"; - } -} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java deleted file mode 100644 index 6a53efee0e74..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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(); -} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java new file mode 100644 index 000000000000..990d7b6a0101 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -0,0 +1,91 @@ +/* + * 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.ActivityThread; +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.window.extensions.embedding.ActivityEmbeddingComponent; +import androidx.window.extensions.embedding.SplitController; +import androidx.window.extensions.layout.WindowLayoutComponent; +import androidx.window.extensions.layout.WindowLayoutComponentImpl; + +/** + * The reference implementation of {@link WindowExtensions} that implements the initial API version. + */ +public class WindowExtensionsImpl implements WindowExtensions { + + private final Object mLock = new Object(); + private volatile WindowLayoutComponent mWindowLayoutComponent; + private volatile SplitController mSplitController; + + @Override + public int getVendorApiLevel() { + return 1; + } + + @Override + public boolean isWindowLayoutComponentAvailable() { + return true; + } + + @Override + public WindowLayoutComponent getWindowLayoutComponent() { + if (mWindowLayoutComponent == null) { + synchronized (mLock) { + if (mWindowLayoutComponent == null) { + Context context = ActivityThread.currentApplication(); + mWindowLayoutComponent = new WindowLayoutComponentImpl(context); + } + } + } + return mWindowLayoutComponent; + } + + /** + * 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. + */ + @Override + public boolean isEmbeddingComponentAvailable() { + return true; + } + + /** + * 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.WindowExtensions}. An + * {@link UnsupportedOperationException} will be thrown if the device does not support + * Activity Embedding. Use + * {@link WindowExtensions#isEmbeddingComponentAvailable()} to determine if + * {@link ActivityEmbeddingComponent} is present. + * @return the OEM implementation of {@link ActivityEmbeddingComponent} + */ + @NonNull + public ActivityEmbeddingComponent getActivityEmbeddingComponent() { + if (mSplitController == null) { + synchronized (mLock) { + if (mSplitController == null) { + mSplitController = new SplitController(); + } + } + } + return mSplitController; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java new file mode 100644 index 000000000000..f9e1f077cffc --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java @@ -0,0 +1,38 @@ +/* + * 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.annotation.NonNull; + +/** + * Provides the OEM implementation of {@link WindowExtensions}. + */ +public class WindowExtensionsProvider { + + private static final WindowExtensions sWindowExtensions = new WindowExtensionsImpl(); + + /** + * Returns the OEM implementation of {@link WindowExtensions}. This method is implemented in + * the library provided on the device and overwrites one in the Jetpack library included in + * apps. + * @return the OEM implementation of {@link WindowExtensions} + */ + @NonNull + public static WindowExtensions getWindowExtensions() { + return sWindowExtensions; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 46c8ffe286bd..85ef270ac49d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.window.extensions.organizer; +package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -36,7 +36,6 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.window.extensions.embedding.SplitRule; import java.util.Map; import java.util.concurrent.Executor; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java index a41557d41e30..06e7d1457417 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -14,15 +14,11 @@ * limitations under the License. */ -package androidx.window.extensions.organizer; +package androidx.window.extensions.embedding; import android.annotation.NonNull; import android.app.Activity; -import androidx.window.extensions.embedding.SplitPairRule; -import androidx.window.extensions.embedding.SplitPlaceholderRule; -import androidx.window.extensions.embedding.SplitRule; - /** * Client-side descriptor of a split that holds two containers. */ diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index a783fcde4f52..e1c8b11a835c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.window.extensions.organizer; +package androidx.window.extensions.embedding; import android.annotation.NonNull; import android.annotation.Nullable; @@ -31,19 +31,10 @@ 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.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; import java.util.Set; @@ -53,7 +44,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, + ActivityEmbeddingComponent { private final SplitPresenter mPresenter; @@ -64,6 +56,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Callback to Jetpack to notify about changes to split states. private @NonNull Consumer<List<SplitInfo>> mEmbeddingCallback; + private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); public SplitController() { mPresenter = new SplitPresenter(new MainThreadExecutor(), this); @@ -77,6 +70,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 +97,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(); } @@ -119,8 +114,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen container.setInfo(taskFragmentAppearedInfo.getTaskFragmentInfo()); if (container.isFinished()) { mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); - updateCallbackIfNecessary(); } + updateCallbackIfNecessary(); } @Override @@ -139,8 +134,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final boolean shouldFinishDependent = !taskFragmentInfo.isTaskClearedForReuse(); mPresenter.cleanupContainer(container, shouldFinishDependent); - updateCallbackIfNecessary(); } + updateCallbackIfNecessary(); } @Override @@ -164,18 +159,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } + void onActivityCreated(@NonNull Activity launchedActivity) { + handleActivityCreated(launchedActivity); + updateCallbackIfNecessary(); + } + /** * Checks if the activity start should be routed to a particular container. It can create a new * container for the activity and a new split container if necessary. */ // TODO(b/190433398): Break down into smaller functions. - void onActivityCreated(@NonNull Activity launchedActivity) { + void handleActivityCreated(@NonNull Activity launchedActivity) { final List<EmbeddingRule> splitRules = getSplitRules(); final TaskFragmentContainer currentContainer = getContainerWithActivity( 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()); @@ -240,8 +240,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen mPresenter.createNewSplitContainer(activityBelow, launchedActivity, splitPairRule); - - updateCallbackIfNecessary(); } private void onActivityConfigurationChanged(@NonNull Activity activity) { @@ -501,7 +499,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen continue; } SplitPlaceholderRule placeholderRule = (SplitPlaceholderRule) rule; - if (placeholderRule.getActivityPredicate().test(activity)) { + if (placeholderRule.matchesActivity(activity)) { return placeholderRule; } } @@ -515,8 +513,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (mEmbeddingCallback == null) { return; } - // TODO(b/190433398): Check if something actually changed - mEmbeddingCallback.accept(getActiveSplitStates()); + if (!allActivitiesCreated()) { + return; + } + List<SplitInfo> currentSplitStates = getActiveSplitStates(); + if (mLastReportedSplitStates.equals(currentSplitStates)) { + return; + } + mLastReportedSplitStates.clear(); + mLastReportedSplitStates.addAll(currentSplitStates); + mEmbeddingCallback.accept(currentSplitStates); } /** @@ -525,20 +531,46 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private List<SplitInfo> getActiveSplitStates() { List<SplitInfo> splitStates = new ArrayList<>(); for (SplitContainer container : mSplitContainers) { - TaskFragment primaryContainer = - new TaskFragment( + if (container.getPrimaryContainer().isEmpty() + || container.getSecondaryContainer().isEmpty()) { + // Skipping containers that do not have any activities to report. + continue; + } + 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()); + secondaryContainer, + // Splits that are not showing side-by-side are reported as having 0 split + // ratio, since by definition in the API the primary container occupies no + // width of the split when covered by the secondary. + mPresenter.shouldShowSideBySide(container) + ? container.getSplitRule().getSplitRatio() + : 0.0f); splitStates.add(splitState); } return splitStates; } /** + * Checks if all activities that are registered with the containers have already appeared in + * the client. + */ + private boolean allActivitiesCreated() { + for (TaskFragmentContainer container : mContainers) { + if (container.getInfo() == null + || container.getInfo().getActivities().size() + != container.collectActivities().size()) { + return false; + } + } + return true; + } + + /** * Returns {@code true} if the container is expanded to occupy full task size. * Returns {@code false} if the container is included in an active split. */ @@ -567,8 +599,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen continue; } SplitPairRule pairRule = (SplitPairRule) rule; - if (pairRule.getActivityIntentPredicate().test( - new Pair(primaryActivity, secondaryActivityIntent))) { + if (pairRule.matchesActivityIntentPair(primaryActivity, secondaryActivityIntent)) { return pairRule; } } @@ -587,10 +618,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } SplitPairRule pairRule = (SplitPairRule) rule; final Intent intent = secondaryActivity.getIntent(); - if (pairRule.getActivityPairPredicate().test( - new Pair(primaryActivity, secondaryActivity)) - && (intent == null || pairRule.getActivityIntentPredicate().test( - new Pair(primaryActivity, intent)))) { + if (pairRule.matchesActivityPair(primaryActivity, secondaryActivity) + && (intent == null + || pairRule.matchesActivityIntentPair(primaryActivity, intent))) { return pairRule; } } @@ -611,7 +641,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 +654,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!activityRule.shouldAlwaysExpand()) { continue; } - if (activityRule.getActivityPredicate().test(activity)) { + if (activity != null && activityRule.matchesActivity(activity)) { + return true; + } else if (intent != null && activityRule.matchesIntent(intent)) { return true; } } @@ -678,11 +710,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** Executor that posts on the main application thread. */ private static class MainThreadExecutor implements Executor { - private final Handler handler = new Handler(Looper.getMainLooper()); + private final Handler mHandler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable r) { - handler.post(r); + mHandler.post(r); } } @@ -703,13 +735,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 diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index ac85ac8cbc34..25292b905fe8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -14,16 +14,19 @@ * limitations under the License. */ -package androidx.window.extensions.organizer; +package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; +import android.util.LayoutDirection; +import android.view.View; import android.view.WindowInsets; import android.view.WindowMetrics; import android.window.TaskFragmentCreationParams; @@ -32,8 +35,6 @@ import android.window.WindowContainerTransaction; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.window.extensions.embedding.SplitPairRule; -import androidx.window.extensions.embedding.SplitRule; import java.util.concurrent.Executor; @@ -42,13 +43,13 @@ import java.util.concurrent.Executor; * {@link SplitController}. */ class SplitPresenter extends JetpackTaskFragmentOrganizer { - private static final int POSITION_LEFT = 0; - private static final int POSITION_RIGHT = 1; + private static final int POSITION_START = 0; + private static final int POSITION_END = 1; private static final int POSITION_FILL = 2; @IntDef(value = { - POSITION_LEFT, - POSITION_RIGHT, + POSITION_START, + POSITION_END, POSITION_FILL, }) private @interface Position {} @@ -96,13 +97,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final WindowContainerTransaction wct = new WindowContainerTransaction(); final Rect parentBounds = getParentContainerBounds(primaryActivity); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, + isLtr(primaryActivity, rule)); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); // Create new empty task fragment - TaskFragmentContainer secondaryContainer = mController.newContainer(null); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule); + final TaskFragmentContainer secondaryContainer = mController.newContainer(null); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, + rule, isLtr(primaryActivity, rule)); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken(), secondaryRectBounds, WINDOWING_MODE_MULTI_WINDOW); @@ -135,11 +138,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final WindowContainerTransaction wct = new WindowContainerTransaction(); final Rect parentBounds = getParentContainerBounds(primaryActivity); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, + isLtr(primaryActivity, rule)); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, + isLtr(primaryActivity, rule)); final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct, secondaryActivity, secondaryRectBounds, primaryContainer); @@ -153,6 +158,20 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } /** + * Creates a new expanded container. + */ + TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) { + final TaskFragmentContainer newContainer = mController.newContainer(null); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + createTaskFragment(wct, newContainer.getTaskFragmentToken(), + launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_MULTI_WINDOW); + + applyTransaction(wct); + return newContainer; + } + + /** * Creates a new container or resizes an existing container for activity to the provided bounds. * @param activity The activity to be re-parented to the container if necessary. * @param containerToAvoid Re-parent from this container if an activity is already in it. @@ -197,8 +216,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @NonNull SplitRule rule) { final Rect parentBounds = getParentContainerBounds(launchingActivity); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, + isLtr(launchingActivity, rule)); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, + isLtr(launchingActivity, rule)); TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( launchingActivity.getActivityToken()); @@ -231,8 +252,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Getting the parent bounds using the updated container - it will have the recent value. final Rect parentBounds = getParentContainerBounds(updatedContainer); final SplitRule rule = splitContainer.getSplitRule(); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule); + final Activity activity = splitContainer.getPrimaryContainer().getTopNonFinishingActivity(); + if (activity == null) { + return; + } + final boolean isLtr = isLtr(activity, rule); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, + isLtr); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, + isLtr); // If the task fragments are not registered yet, the positions will be updated after they // are created again. @@ -283,36 +311,64 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // TODO(b/190433398): Supply correct insets. final WindowMetrics parentMetrics = new WindowMetrics(parentBounds, new WindowInsets(new Rect())); - return rule.getParentWindowMetricsPredicate().test(parentMetrics); + return rule.checkParentMetrics(parentMetrics); } @NonNull private Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds, - @NonNull SplitRule rule) { + @NonNull SplitRule rule, boolean isLtr) { if (!shouldShowSideBySide(parentBounds, rule)) { return new Rect(); } - float splitRatio = rule.getSplitRatio(); + final float splitRatio = rule.getSplitRatio(); + final float rtlSplitRatio = 1 - splitRatio; switch (position) { - case POSITION_LEFT: - return new Rect( - parentBounds.left, - parentBounds.top, - (int) (parentBounds.left + parentBounds.width() * splitRatio), - parentBounds.bottom); - case POSITION_RIGHT: - return new Rect( - (int) (parentBounds.left + parentBounds.width() * splitRatio), - parentBounds.top, - parentBounds.right, - parentBounds.bottom); + case POSITION_START: + return isLtr ? getLeftContainerBounds(parentBounds, splitRatio) + : getRightContainerBounds(parentBounds, rtlSplitRatio); + case POSITION_END: + return isLtr ? getRightContainerBounds(parentBounds, splitRatio) + : getLeftContainerBounds(parentBounds, rtlSplitRatio); case POSITION_FILL: return parentBounds; } return parentBounds; } + private Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) { + return new Rect( + parentBounds.left, + parentBounds.top, + (int) (parentBounds.left + parentBounds.width() * splitRatio), + parentBounds.bottom); + } + + private Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) { + return new Rect( + (int) (parentBounds.left + parentBounds.width() * splitRatio), + parentBounds.top, + parentBounds.right, + parentBounds.bottom); + } + + /** + * Checks if a split with the provided rule should be displays in left-to-right layout + * direction, either always or with the current configuration. + */ + private boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) { + switch (rule.getLayoutDirection()) { + case LayoutDirection.LOCALE: + return context.getResources().getConfiguration().getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR; + case LayoutDirection.RTL: + return false; + case LayoutDirection.LTR: + default: + return true; + } + } + @NonNull Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) { final Configuration parentConfig = mFragmentParentConfigs.get( diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java index 155c649d72a4..65bd9f330e37 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationAdapter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.window.extensions.organizer; +package androidx.window.extensions.embedding; import android.graphics.Rect; import android.view.Choreographer; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java index 663124349464..65797667687e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.window.extensions.organizer; +package androidx.window.extensions.embedding; import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE; import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index da3d116455ef..bb37fff297a7 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.window.extensions.organizer; +package androidx.window.extensions.embedding; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java index ddcb27ddb512..71295900cd06 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationSpec.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.window.extensions.organizer; +package androidx.window.extensions.embedding; import static android.view.RemoteAnimationTarget.MODE_CLOSING; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 8503b9f4e310..54e44a70ed40 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.window.extensions.organizer; +package androidx.window.extensions.embedding; import android.annotation.NonNull; import android.annotation.Nullable; @@ -212,7 +212,9 @@ class TaskFragmentContainer { @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) { // Finish own activities for (Activity activity : collectActivities()) { - activity.finish(); + if (!activity.isFinishing()) { + activity.finish(); + } } if (!shouldFinishDependent) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index a0d5b004ff1c..383d91da6af8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.window.extensions; +package androidx.window.extensions.layout; import static android.view.Display.DEFAULT_DISPLAY; @@ -36,19 +36,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 WindowLayoutComponentImpl implements 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 +64,7 @@ class SampleExtensionImpl extends StubExtension { private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer; private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer; - SampleExtensionImpl(Context context) { + public WindowLayoutComponentImpl(Context context) { mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context); mDevicePostureProducer = new PriorityDataProducer<>(List.of( mSettingsDevicePostureProducer, @@ -73,28 +81,68 @@ class SampleExtensionImpl extends StubExtension { mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } + /** + * 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 +163,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(); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/EmbeddingExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/EmbeddingExtensionImpl.java deleted file mode 100644 index 9a8961f1d460..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/EmbeddingExtensionImpl.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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); - } -} diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex 097febf9770a..42e829e3e5ab 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar |