Update android_sdk extensions library Ran third_party/android_sdk/window_extensions/update_source.sh and fixed build errors. Change-Id: Id4366c1281207e97d45c690f24e606326a6a6964 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7720925 Commit-Queue: Sam Maier <smaier@google.com> Reviewed-by: Andrew Grieve <agrieve@chromium.org> Auto-Submit: Sam Maier <smaier@google.com> Commit-Queue: Andrew Grieve <agrieve@chromium.org> Cr-Commit-Position: refs/heads/main@{#1608602} NOKEYCHECK=True GitOrigin-RevId: 56d4a84e46dec7cc76abf24d114c139c15b2d769
diff --git a/window_extensions/BUILD.gn b/window_extensions/BUILD.gn index d9c40dd..c7501e9 100644 --- a/window_extensions/BUILD.gn +++ b/window_extensions/BUILD.gn
@@ -9,7 +9,10 @@ android_library("androidx_window_extensions_java") { visibility = [ ":*" ] sources = rebase_path(read_file("sources.txt", "list lines"), ".", "java") - deps = [ "//third_party/androidx:androidx_annotation_annotation_jvm_java" ] + deps = [ + "//third_party/android_deps:org_jspecify_jspecify_java", + "//third_party/androidx:androidx_annotation_annotation_jvm_java", + ] } # Use an intermediate target that omits the "_java" suffix in order to avoid
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/RequiresVendorApiLevel.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/RequiresVendorApiLevel.java new file mode 100644 index 0000000..72e844d --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/RequiresVendorApiLevel.java
@@ -0,0 +1,45 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions; + +import androidx.annotation.IntRange; +import androidx.annotation.RestrictTo; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** The annotation used to document the minimum supported vendor API level of the denoted target. */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD}) +@RestrictTo(RestrictTo.Scope.LIBRARY) +public @interface RequiresVendorApiLevel { + + /** The minimum required vendor API level of the denoted target. */ + @IntRange(from = 1) + int level(); + + /** + * The vendor API level that the denoted target started to deprecated, which defaults to {@link + * #VENDOR_API_LEVEL_NOT_SET}. + */ + int deprecatedSince() default VENDOR_API_LEVEL_NOT_SET; + + /** Indicates the denoted target is not deprecated. */ + int VENDOR_API_LEVEL_NOT_SET = -1; +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java index 74163e6..10c29fa 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
@@ -16,113 +16,29 @@ package androidx.window.extensions; -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.app.Activity; -import android.app.ActivityOptions; -import android.content.Context; -import android.os.IBinder; - -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; import androidx.window.extensions.area.WindowAreaComponent; -import androidx.window.extensions.core.util.function.Consumer; import androidx.window.extensions.embedding.ActivityEmbeddingComponent; -import androidx.window.extensions.embedding.ActivityStack; -import androidx.window.extensions.embedding.SplitAttributes; -import androidx.window.extensions.embedding.SplitInfo; import androidx.window.extensions.layout.WindowLayoutComponent; -import java.util.Set; +import org.jspecify.annotations.Nullable; /** * A class to provide instances of different WindowManager Jetpack extension components. An OEM must - * implement all the availability methods to state which WindowManager Jetpack extension - * can be used. If a component is not available then the check must return {@code false}. Trying to - * get a component that is not available will throw an {@link UnsupportedOperationException}. - * All components must support the API level returned in - * {@link WindowExtensions#getVendorApiLevel()}. + * implement all the availability methods to state which WindowManager Jetpack extension can be + * used. If a component is not available then the check must return {@code false}. Trying to get a + * component that is not available will throw an {@link UnsupportedOperationException}. All + * components must support the API level returned in {@link WindowExtensions#getVendorApiLevel()}. */ public interface WindowExtensions { - // TODO(b/241323716) Removed after we have annotation to check API level - /** - * An invalid {@link #getVendorApiLevel vendor API level} - */ - @RestrictTo(LIBRARY_GROUP) - int INVALID_VENDOR_API_LEVEL = -1; - - // TODO(b/241323716) Removed after we have annotation to check API level - /** - * A vendor API level constant. It helps to unify the format of documenting {@code @since} - * block. - * <p> - * The added APIs for Vendor API level 1 are: - * <ul> - * <li>{@link androidx.window.extensions.embedding.ActivityRule} APIs</li> - * <li>{@link androidx.window.extensions.embedding.SplitPairRule} APIs</li> - * <li>{@link androidx.window.extensions.embedding.SplitPlaceholderRule} APIs</li> - * <li>{@link androidx.window.extensions.embedding.SplitInfo} APIs</li> - * <li>{@link androidx.window.extensions.layout.DisplayFeature} APIs</li> - * <li>{@link androidx.window.extensions.layout.FoldingFeature} APIs</li> - * <li>{@link androidx.window.extensions.layout.WindowLayoutInfo} APIs</li> - * <li>{@link androidx.window.extensions.layout.WindowLayoutComponent} APIs</li> - * </ul> - * </p> - */ - @RestrictTo(LIBRARY_GROUP) - int VENDOR_API_LEVEL_1 = 1; - - // TODO(b/241323716) Removed after we have annotation to check API level - /** - * A vendor API level constant. It helps to unify the format of documenting {@code @since} - * block. - * The added APIs for Vendor API level 2 are: - * <ul> - * <li>{@link WindowAreaComponent#addRearDisplayStatusListener(Consumer)}</li> - * <li>{@link WindowAreaComponent#startRearDisplaySession(Activity, Consumer)}</li> - * <li>{@link androidx.window.extensions.embedding.SplitPlaceholderRule.Builder#setFinishPrimaryWithPlaceholder(int)}</li> - * <li>{@link androidx.window.extensions.embedding.SplitAttributes}</li> - * <li>{@link ActivityEmbeddingComponent#setSplitAttributesCalculator( - * androidx.window.extensions.core.util.function.Function)}</li> - * <li>{@link WindowLayoutComponent#addWindowLayoutInfoListener(Context, Consumer)}</li> - * </ul> - */ - @RestrictTo(LIBRARY_GROUP) - int VENDOR_API_LEVEL_2 = 2; - - // TODO(b/241323716) Removed after we have annotation to check API level - /** - * A vendor API level constant. It helps to unify the format of documenting {@code @since} - * block. - * <p> - * The added APIs for Vendor API level 3 are: - * <ul> - * <li>{@link ActivityStack#getToken()}</li> - * <li>{@link SplitInfo#getToken()}</li> - * <li>{@link ActivityEmbeddingComponent#setLaunchingActivityStack(ActivityOptions, - * IBinder)}</li> - * <li>{@link ActivityEmbeddingComponent#invalidateTopVisibleSplitAttributes()}</li> - * <li>{@link ActivityEmbeddingComponent#updateSplitAttributes(IBinder, SplitAttributes)} - * </li> - * <li>{@link ActivityEmbeddingComponent#finishActivityStacks(Set)}</li> - * <li>{@link WindowAreaComponent#addRearDisplayPresentationStatusListener(Consumer)}</li> - * <li>{@link WindowAreaComponent#startRearDisplayPresentationSession(Activity, Consumer)} - * </li> - * <li>{@link WindowAreaComponent#getRearDisplayMetrics()}</li> - * <li>{@link WindowAreaComponent#getRearDisplayPresentation()}</li> - * </ul> - * </p> - */ - @RestrictTo(LIBRARY_GROUP) - int VENDOR_API_LEVEL_3 = 3; /** * Returns the API level of the vendor library on the device. If the returned version is not * supported by the WindowManager library, then some functions may not be available or replaced * with stub implementations. * - * The expected use case is for the WindowManager library to determine which APIs are + * <p>The expected use case is for the WindowManager library to determine which APIs are * available and wrap the API so that app developers do not need to deal with the complexity. + * * @return the API level supported by the library. */ default int getVendorApiLevel() { @@ -133,30 +49,30 @@ * Returns the OEM implementation of {@link WindowLayoutComponent} if it is supported on the * device, {@code null} otherwise. The implementation must match the API level reported in * {@link WindowExtensions}. + * * @return the OEM implementation of {@link WindowLayoutComponent} */ - @Nullable - WindowLayoutComponent getWindowLayoutComponent(); + @Nullable WindowLayoutComponent getWindowLayoutComponent(); /** * Returns the OEM implementation of {@link ActivityEmbeddingComponent} if it is supported on * the device, {@code null} otherwise. The implementation must match the API level reported in * {@link WindowExtensions}. + * * @return the OEM implementation of {@link ActivityEmbeddingComponent} */ - @Nullable - default ActivityEmbeddingComponent getActivityEmbeddingComponent() { + default @Nullable ActivityEmbeddingComponent getActivityEmbeddingComponent() { return null; } /** - * Returns the OEM implementation of {@link WindowAreaComponent} if it is supported on - * the device, {@code null} otherwise. The implementation must match the API level reported in + * Returns the OEM implementation of {@link WindowAreaComponent} if it is supported on the + * device, {@code null} otherwise. The implementation must match the API level reported in * {@link WindowExtensions}. + * * @return the OEM implementation of {@link WindowAreaComponent} */ - @Nullable - default WindowAreaComponent getWindowAreaComponent() { + default @Nullable WindowAreaComponent getWindowAreaComponent() { return null; } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensionsProvider.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensionsProvider.java index 1f3aefd..e69a942 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensionsProvider.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensionsProvider.java
@@ -16,22 +16,20 @@ package androidx.window.extensions; -import androidx.annotation.NonNull; +import org.jspecify.annotations.NonNull; -/** - * Provides the OEM implementation of {@link WindowExtensions}. - */ +/** Provides the OEM implementation of {@link WindowExtensions}. */ public class WindowExtensionsProvider { private WindowExtensionsProvider() {} /** - * Returns the OEM implementation of {@link WindowExtensions}. This method must be - * implemented by the OEM also. + * Returns the OEM implementation of {@link WindowExtensions}. This method must be implemented + * by the OEM also. + * * @return the OEM implementation of {@link WindowExtensions} */ - @NonNull - public static WindowExtensions getWindowExtensions() { + public static @NonNull WindowExtensions getWindowExtensions() { throw new UnsupportedOperationException("Stub, replace with implementation"); } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaPresentation.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaPresentation.java index 0ce24b8..a24375b 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaPresentation.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaPresentation.java
@@ -18,27 +18,36 @@ import android.content.Context; import android.view.View; +import android.view.Window; -import androidx.annotation.NonNull; +import androidx.window.extensions.RequiresVendorApiLevel; + +import org.jspecify.annotations.NonNull; /** * An interface representing a container in an extension window area in which app content can be * shown. * - * Since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_3} * @see WindowAreaComponent#getRearDisplayPresentation() */ public interface ExtensionWindowAreaPresentation { /** - * Returns the {@link Context} for the window that is being used - * to display the additional content provided from the application. + * Returns the {@link Context} for the window that is being used to display the additional + * content provided from the application. */ - @NonNull - Context getPresentationContext(); + @RequiresVendorApiLevel(level = 3) + @NonNull Context getPresentationContext(); - /** - * Sets the {@link View} that the application wants to display in the extension window area. - */ + /** Sets the {@link View} that the application wants to display in the extension window area. */ + @RequiresVendorApiLevel(level = 3) void setPresentationView(@NonNull View view); + + /** Returns the {@link Window} for the rear display presentation area. */ + @RequiresVendorApiLevel(level = 4) + default @NonNull Window getWindow() { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaStatus.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaStatus.java index 0dcd47f..67b5009 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaStatus.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaStatus.java
@@ -18,12 +18,13 @@ import android.util.DisplayMetrics; -import androidx.annotation.NonNull; +import androidx.window.extensions.RequiresVendorApiLevel; + +import org.jspecify.annotations.NonNull; /** * Interface to provide information around the current status of a window area feature. * - * Since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_3} * @see WindowAreaComponent#addRearDisplayPresentationStatusListener */ public interface ExtensionWindowAreaStatus { @@ -32,6 +33,7 @@ * Returns the {@link androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus} * value that relates to the current status of a feature. */ + @RequiresVendorApiLevel(level = 3) @WindowAreaComponent.WindowAreaStatus int getWindowAreaStatus(); @@ -39,6 +41,6 @@ * Returns the {@link DisplayMetrics} that corresponds to the window area that a feature * interacts with. This is converted to size class information provided to developers. */ - @NonNull - DisplayMetrics getWindowAreaDisplayMetrics(); + @RequiresVendorApiLevel(level = 3) + @NonNull DisplayMetrics getWindowAreaDisplayMetrics(); }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/WindowAreaComponent.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/WindowAreaComponent.java index e5705ac..8d77345 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/WindowAreaComponent.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/WindowAreaComponent.java
@@ -20,12 +20,14 @@ import android.util.DisplayMetrics; import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; +import androidx.window.extensions.RequiresVendorApiLevel; import androidx.window.extensions.WindowExtensions; import androidx.window.extensions.core.util.function.Consumer; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,64 +37,45 @@ * The interface definition that will be used by the WindowManager library to get custom * OEM-provided behavior around moving windows between displays or display areas on a device. * - * Currently the only behavior supported is RearDisplay Mode, where the window - * is moved to the display that faces the same direction as the rear camera. + * <p>Currently the only behavior supported is RearDisplay Mode, where the window is moved to the + * display that faces the same direction as the rear camera. * * <p>This interface should be implemented by OEM and deployed to the target devices. + * * @see WindowExtensions#getWindowLayoutComponent() */ public interface WindowAreaComponent { /** - * WindowArea status constant to signify that the feature is - * unsupported on this device. Could be due to the device not supporting that - * specific feature. - * - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * WindowArea status constant to signify that the feature is unsupported on this device. Could + * be due to the device not supporting that specific feature. */ int STATUS_UNSUPPORTED = 0; /** - * WindowArea status constant to signify that the feature is - * currently unavailable but is supported on this device. This value could signify - * that the current device state does not support the specific feature or another - * process is currently enabled in that feature. - * - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * WindowArea status constant to signify that the feature is currently unavailable but is + * supported on this device. This value could signify that the current device state does not + * support the specific feature or another process is currently enabled in that feature. */ int STATUS_UNAVAILABLE = 1; /** - * WindowArea status constant to signify that the feature is - * available to be entered or enabled. - * - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * WindowArea status constant to signify that the feature is available to be entered or enabled. */ int STATUS_AVAILABLE = 2; - /** - * WindowArea status constant to signify that the feature is - * already enabled. - * - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} - */ + /** WindowArea status constant to signify that the feature is already enabled. */ int STATUS_ACTIVE = 3; @RestrictTo(RestrictTo.Scope.LIBRARY) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) - @IntDef({ - STATUS_UNSUPPORTED, - STATUS_UNAVAILABLE, - STATUS_AVAILABLE, - STATUS_ACTIVE - }) + @IntDef({STATUS_UNSUPPORTED, STATUS_UNAVAILABLE, STATUS_AVAILABLE, STATUS_ACTIVE}) @interface WindowAreaStatus {} /** - * Session state constant to represent there being no active session - * currently in progress. Used by the library to call the correct callbacks if - * a session is ended. + * Session state constant to represent there being no active session currently in progress. Used + * by the library to call the correct callbacks if a session is ended. */ int SESSION_STATE_INACTIVE = 0; @@ -105,173 +88,169 @@ int SESSION_STATE_ACTIVE = 1; /** - * Session state constant to represent that there is an - * active presentation session currently in progress, and the content provided by the - * application is visible. + * Session state constant to represent that there is an active presentation session currently in + * progress, and the content provided by the application is visible. */ int SESSION_STATE_CONTENT_VISIBLE = 2; @RestrictTo(RestrictTo.Scope.LIBRARY) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) - @IntDef({ - SESSION_STATE_ACTIVE, - SESSION_STATE_INACTIVE, - SESSION_STATE_CONTENT_VISIBLE - }) + @IntDef({SESSION_STATE_ACTIVE, SESSION_STATE_INACTIVE, SESSION_STATE_CONTENT_VISIBLE}) @interface WindowAreaSessionState {} /** - * Adds a listener interested in receiving updates on the RearDisplayStatus - * of the device. Because this is being called from the OEM provided - * extensions, the library will post the result of the listener on the executor - * provided by the developer. + * Adds a listener interested in receiving updates on the RearDisplayStatus of the device. + * Because this is being called from the OEM provided extensions, the library will post the + * result of the listener on the executor provided by the developer. * - * The listener provided will receive values that - * correspond to the [WindowAreaStatus] value that aligns with the current status - * of the rear display. + * <p>The listener provided will receive values that correspond to the [WindowAreaStatus] value + * that aligns with the current status of the rear display. + * * @param consumer interested in receiving updates to WindowAreaStatus. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} */ + @RequiresVendorApiLevel(level = 2) void addRearDisplayStatusListener(@NonNull Consumer<Integer> consumer); /** * Removes a listener no longer interested in receiving updates. + * * @param consumer no longer interested in receiving updates to WindowAreaStatus - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} */ + @RequiresVendorApiLevel(level = 2) void removeRearDisplayStatusListener(@NonNull Consumer<Integer> consumer); /** - * Creates and starts a rear display session and sends state updates to the - * consumer provided. This consumer will receive a constant represented by - * [WindowAreaSessionState] to represent the state of the current rear display - * session. We will translate to a more friendly interface in the library. + * Creates and starts a rear display session and sends state updates to the consumer provided. + * This consumer will receive a constant represented by [WindowAreaSessionState] to represent + * the state of the current rear display session. We will translate to a more friendly interface + * in the library. * - * Because this is being called from the OEM provided extensions, the library - * will post the result of the listener on the executor provided by the developer. + * <p>Because this is being called from the OEM provided extensions, the library will post the + * result of the listener on the executor provided by the developer. * - * @param activity to allow that the OEM implementation will use as a base - * context and to identify the source display area of the request. - * The reference to the activity instance must not be stored in the OEM - * implementation to prevent memory leaks. + * @param activity to allow that the OEM implementation will use as a base context and to + * identify the source display area of the request. The reference to the activity instance + * must not be stored in the OEM implementation to prevent memory leaks. * @param consumer to provide updates to the client on the status of the session - * @throws UnsupportedOperationException if this method is called when RearDisplay - * mode is not available. This could be to an incompatible device state or when - * another process is currently in this mode. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * @throws UnsupportedOperationException if this method is called when RearDisplay mode is not + * available. This could be to an incompatible device state or when another process is + * currently in this mode. */ + @RequiresVendorApiLevel(level = 2) @SuppressWarnings("ExecutorRegistration") // Jetpack will post it on the app-provided executor. - void startRearDisplaySession(@NonNull Activity activity, + void startRearDisplaySession( + @NonNull Activity activity, @NonNull Consumer<@WindowAreaSessionState Integer> consumer); + // TODO(b/264546746): Remove deprecated Window Extensions APIs after apps in g3 is updated to + // the latest library. /** - * Ends a RearDisplaySession and sends [STATE_INACTIVE] to the consumer - * provided in the {@code startRearDisplaySession} method. This method is only - * called through the {@code RearDisplaySession} provided to the developer. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * Ends a RearDisplaySession and sends [STATE_INACTIVE] to the consumer provided in the {@code + * startRearDisplaySession} method. This method is only called through the {@code + * RearDisplaySession} provided to the developer. */ + @RequiresVendorApiLevel(level = 2) void endRearDisplaySession(); /** - * Adds a listener interested in receiving updates on the rear display presentation status - * of the device. Because this is being called from the OEM provided - * extensions, the library will post the result of the listener on the executor - * provided by the developer. + * Adds a listener interested in receiving updates on the rear display presentation status of + * the device. Because this is being called from the OEM provided extensions, the library will + * post the result of the listener on the executor provided by the developer. * - * The listener provided will receive {@link ExtensionWindowAreaStatus} values that + * <p>The listener provided will receive {@link ExtensionWindowAreaStatus} values that * correspond to the current status of the feature. * * @param consumer interested in receiving updates to {@link ExtensionWindowAreaStatus}. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} */ + @RequiresVendorApiLevel(level = 3) default void addRearDisplayPresentationStatusListener( @NonNull Consumer<ExtensionWindowAreaStatus> consumer) { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); } /** * Removes a listener no longer interested in receiving updates. * * @param consumer no longer interested in receiving updates to WindowAreaStatus - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} */ + @RequiresVendorApiLevel(level = 3) default void removeRearDisplayPresentationStatusListener( @NonNull Consumer<ExtensionWindowAreaStatus> consumer) { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); } /** * Creates and starts a rear display presentation session and sends state updates to the - * consumer provided. This consumer will receive a constant represented by - * {@link WindowAreaSessionState} to represent the state of the current rear display - * session. We will translate to a more friendly interface in the library. + * consumer provided. This consumer will receive a constant represented by {@link + * WindowAreaSessionState} to represent the state of the current rear display session. We will + * translate to a more friendly interface in the library. * - * Because this is being called from the OEM provided extensions, the library - * will post the result of the listener on the executor provided by the developer. + * <p>Because this is being called from the OEM provided extensions, the library will post the + * result of the listener on the executor provided by the developer. * - * Rear display presentation mode refers to a feature where an {@link Activity} can present - * additional content on a device with a second display that is facing the same direction - * as the rear camera (i.e. the cover display on a fold-in style device). The calling - * {@link Activity} stays on the user-facing display. + * <p>Rear display presentation mode refers to a feature where an {@link Activity} can present + * additional content on a device with a second display that is facing the same direction as the + * rear camera (i.e. the cover display on a fold-in style device). The calling {@link Activity} + * stays on the user-facing display. * - * @param activity that the OEM implementation will use as a base - * context and to identify the source display area of the request. - * The reference to the activity instance must not be stored in the OEM - * implementation to prevent memory leaks. + * @param activity that the OEM implementation will use as a base context and to identify the + * source display area of the request. The reference to the activity instance must not be + * stored in the OEM implementation to prevent memory leaks. * @param consumer to provide updates to the client on the status of the session * @throws UnsupportedOperationException if this method is called when rear display presentation - * mode is not available. This could be to an incompatible device state or when - * another process is currently in this mode. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} + * mode is not available. This could be to an incompatible device state or when another + * process is currently in this mode. */ - default void startRearDisplayPresentationSession(@NonNull Activity activity, + @RequiresVendorApiLevel(level = 3) + default void startRearDisplayPresentationSession( + @NonNull Activity activity, @NonNull Consumer<@WindowAreaSessionState Integer> consumer) { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); } /** - * Ends the current rear display presentation session and provides updates to the - * callback provided. When this is ended, the presented content from the calling - * {@link Activity} will also be removed from the rear facing display. - * Because this is being called from the OEM provided extensions, the result of the listener - * will be posted on the executor provided by the developer at the initial call site. - * - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} + * Ends the current rear display presentation session and provides updates to the callback + * provided. When this is ended, the presented content from the calling {@link Activity} will + * also be removed from the rear facing display. Because this is being called from the OEM + * provided extensions, the result of the listener will be posted on the executor provided by + * the developer at the initial call site. */ + @RequiresVendorApiLevel(level = 3) default void endRearDisplayPresentationSession() { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); } /** - * Returns the {@link ExtensionWindowAreaPresentation} connected to the active - * rear display presentation session. If there is no session currently active, then it will - * return null. - * - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} + * Returns the {@link ExtensionWindowAreaPresentation} connected to the active rear display + * presentation session. If there is no session currently active, then it will return null. */ - @Nullable - default ExtensionWindowAreaPresentation getRearDisplayPresentation() { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + @RequiresVendorApiLevel(level = 3) + default @Nullable ExtensionWindowAreaPresentation getRearDisplayPresentation() { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); } /** * Returns the {@link android.util.DisplayMetrics} associated with the rear facing display. If - * there is no rear facing display available on the device, returns an empty - * {@link android.util.DisplayMetrics} object. - * - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} + * there is no rear facing display available on the device, returns an empty {@link + * android.util.DisplayMetrics} object. */ + @RequiresVendorApiLevel(level = 3) // TODO(b/273807238): Investigate how we can provide a listener to get runtime changes in // rear display metrics to better support other form-factors in the future. - @NonNull - default DisplayMetrics getRearDisplayMetrics() { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + default @NonNull DisplayMetrics getRearDisplayMetrics() { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingComponent.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingComponent.java index 121520c..f3b36e7 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingComponent.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingComponent.java
@@ -21,101 +21,153 @@ import android.os.IBinder; import android.view.WindowMetrics; -import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; +import androidx.window.extensions.RequiresVendorApiLevel; import androidx.window.extensions.WindowExtensions; import androidx.window.extensions.core.util.function.Consumer; import androidx.window.extensions.core.util.function.Function; +import androidx.window.extensions.util.SetCompat; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; /** * Extension component definition that is used by the WindowManager library to trigger custom * OEM-provided methods for organizing activities that isn't covered by platform APIs. * * <p>This interface should be implemented by OEM and deployed to the target devices. + * * @see androidx.window.extensions.WindowExtensions */ public interface ActivityEmbeddingComponent { - /** - * Updates the rules of embedding activities that are started in the client process. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_1} - */ + /** The vendor API level of the overlay feature APIs. */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + int OVERLAY_FEATURE_API_LEVEL = 8; + + /** Updates the rules of embedding activities that are started in the client process. */ + @RequiresVendorApiLevel(level = 1) void setEmbeddingRules(@NonNull Set<EmbeddingRule> splitRules); /** - * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #setSplitInfoCallback(Consumer)} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_1} + * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with vendor API level 2. + * Only used if {@link #setSplitInfoCallback(Consumer)} can't be called on vendor level 1. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated @SuppressWarnings("ExecutorRegistration") // Jetpack will post it on the app-provided executor. - void setSplitInfoCallback(@NonNull java.util.function.Consumer<List<SplitInfo>> consumer); + void setSplitInfoCallback(java.util.function.@NonNull Consumer<List<SplitInfo>> consumer); /** * Sets the callback that notifies WM Jetpack about changes in split states from the Extensions * Sidecar implementation. The listener should be registered for the lifetime of the process. - * There are no threading guarantees where the events are dispatched from. All messages are - * re-posted to the executors provided by developers. + * There are no threading guarantees where the events are dispatched from. * * @param consumer the callback to notify {@link SplitInfo} list changes - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} */ + @RequiresVendorApiLevel(level = 2) @SuppressWarnings("ExecutorRegistration") // Jetpack will post it on the app-provided executor. default void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> consumer) { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); } /** - * Clears the callback that was set in - * {@link ActivityEmbeddingComponent#setSplitInfoCallback(Consumer)}. - * Added in {@link WindowExtensions#getVendorApiLevel()} 2, calling an earlier version will - * throw {@link java.lang.NoSuchMethodError}. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * Clears the callback that was set in {@link + * ActivityEmbeddingComponent#setSplitInfoCallback(Consumer)}. Added in {@link + * WindowExtensions#getVendorApiLevel()} 2, calling an earlier version will throw {@link + * java.lang.NoSuchMethodError}. */ + @RequiresVendorApiLevel(level = 2) void clearSplitInfoCallback(); /** * Checks if an activity's' presentation is customized by its or any other process and only * occupies a portion of Task bounds. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_1} */ + @RequiresVendorApiLevel(level = 1) boolean isActivityEmbedded(@NonNull Activity activity); /** + * Pins the top-most {@link ActivityStack} to keep the stack of the Activities to be positioned + * on top. The rest of the activities in the Task will be split with the pinned {@link + * ActivityStack}. The pinned {@link ActivityStack} would also have isolated activity navigation + * in which only the activities that are started from the pinned {@link ActivityStack} can be + * added on top of the {@link ActivityStack}. + * + * <p>The pinned {@link ActivityStack} is unpinned whenever the parent Task bounds don't satisfy + * the dimensions and aspect ratio requirements {@link SplitRule#checkParentMetrics} to show two + * {@link ActivityStack}s. See {@link SplitPinRule.Builder#setSticky} if the same {@link + * ActivityStack} should be pinned again whenever the parent Task bounds satisfies the + * dimensions and aspect ratios requirements defined in the rule. + * + * @param taskId The id of the Task that top {@link ActivityStack} should be pinned. + * @param splitPinRule The SplitRule that specifies how the top {@link ActivityStack} should be + * split with others. + * @return Returns {@code true} if the top {@link ActivityStack} is successfully pinned. + * Otherwise, {@code false}. Few examples are: 1. There's no {@link ActivityStack}. 2. There + * is already an existing pinned {@link ActivityStack}. 3. There's no other {@link + * ActivityStack} to split with the top {@link ActivityStack}. + */ + @RequiresVendorApiLevel(level = 5) + default boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Unpins the pinned {@link ActivityStack}. The {@link ActivityStack} will still be the top-most + * {@link ActivityStack} right after unpinned, and the {@link ActivityStack} could be expanded + * or continue to be split with the next top {@link ActivityStack} if the current state matches + * any of the existing {@link SplitPairRule}. It is a no-op call if the task does not have a + * pinned {@link ActivityStack}. + * + * @param taskId The id of the Task that top {@link ActivityStack} should be unpinned. + */ + @RequiresVendorApiLevel(level = 5) + default void unpinTopActivityStack(int taskId) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** * Sets a function to compute the {@link SplitAttributes} for the {@link SplitRule} and current * window state provided in {@link SplitAttributesCalculatorParams}. - * <p> - * This method can be used to dynamically configure the split layout properties when new + * + * <p>This method can be used to dynamically configure the split layout properties when new * activities are launched or window properties change. - * <p> - * If the {@link SplitAttributes} calculator function is not set or is cleared by - * {@link #clearSplitAttributesCalculator()}, apps will update its split layout with - * registered {@link SplitRule} configurations: + * + * <p>If the {@link SplitAttributes} calculator function is not set or is cleared by {@link + * #clearSplitAttributesCalculator()}, apps will update its split layout with registered {@link + * SplitRule} configurations: + * * <ul> - * <li>Split with {@link SplitRule#getDefaultSplitAttributes()} if parent task - * container size constraints defined by - * {@link SplitRule#checkParentMetrics(WindowMetrics)} are satisfied</li> - * <li>Occupy the whole parent task bounds if the constraints are not satisfied. </li> + * <li>Split with {@link SplitRule#getDefaultSplitAttributes()} if parent task container size + * constraints defined by {@link SplitRule#checkParentMetrics(WindowMetrics)} are + * satisfied + * <li>Occupy the whole parent task bounds if the constraints are not satisfied. * </ul> - * <p> - * If the function is set, {@link SplitRule#getDefaultSplitAttributes()} and - * {@link SplitRule#checkParentMetrics(WindowMetrics)} will be passed to - * {@link SplitAttributesCalculatorParams} as - * {@link SplitAttributesCalculatorParams#getDefaultSplitAttributes()} and - * {@link SplitAttributesCalculatorParams#areDefaultConstraintsSatisfied()} instead, and the - * function will be invoked for every device and window state change regardless of the size - * constraints. Users can determine to follow the {@link SplitRule} behavior or customize - * the {@link SplitAttributes} with the {@link SplitAttributes} calculator function. + * + * <p>If the function is set, {@link SplitRule#getDefaultSplitAttributes()} and {@link + * SplitRule#checkParentMetrics(WindowMetrics)} will be passed to {@link + * SplitAttributesCalculatorParams} as {@link + * SplitAttributesCalculatorParams#getDefaultSplitAttributes()} and {@link + * SplitAttributesCalculatorParams#areDefaultConstraintsSatisfied()} instead, and the function + * will be invoked for every device and window state change regardless of the size constraints. + * Users can determine to follow the {@link SplitRule} behavior or customize the {@link + * SplitAttributes} with the {@link SplitAttributes} calculator function. * * @param calculator the callback to set. It will replace the previously set callback if it - * exists. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * exists. */ + @RequiresVendorApiLevel(level = 2) void setSplitAttributesCalculator( @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator); @@ -123,22 +175,31 @@ * Clears the previously callback set in {@link #setSplitAttributesCalculator(Function)}. * * @see #setSplitAttributesCalculator(Function) - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} */ + @RequiresVendorApiLevel(level = 2) void clearSplitAttributesCalculator(); /** - * Sets the launching {@link ActivityStack} to the given {@link ActivityOptions}. - * - * @param options The {@link ActivityOptions} to be updated. - * @param token The {@link ActivityStack#getToken()} to represent the {@link ActivityStack} - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} + * @deprecated Use {@link ActivityEmbeddingOptionsProperties#KEY_ACTIVITY_STACK_TOKEN} instead. */ - @NonNull - default ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options, - @NonNull IBinder token) { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + @Deprecated + @RequiresVendorApiLevel(level = 3, deprecatedSince = 5) + default @NonNull ActivityOptions setLaunchingActivityStack( + @NonNull ActivityOptions options, @NonNull IBinder token) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * @deprecated Use {@link #finishActivityStacksWithTokens(Set)} with instead. + */ + @Deprecated + @RequiresVendorApiLevel(level = 3, deprecatedSince = 5) + default void finishActivityStacks(@NonNull Set<IBinder> activityStackTokens) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); } /** @@ -147,12 +208,18 @@ * expanded to fill the parent task container. * * @param activityStackTokens The set of tokens of {@link ActivityStack}-s that is going to be - * finished. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} + * finished. */ - default void finishActivityStacks(@NonNull Set<IBinder> activityStackTokens) { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + @SuppressWarnings("deprecation") // Use finishActivityStacks(Set) as its core implementation. + @RequiresVendorApiLevel(level = 5) + default void finishActivityStacksWithTokens( + @NonNull Set<ActivityStack.Token> activityStackTokens) { + final Set<IBinder> binderSet = SetCompat.create(); + + for (ActivityStack.Token token : activityStackTokens) { + binderSet.add(token.getRawToken()); + } + finishActivityStacks(binderSet); } /** @@ -161,25 +228,254 @@ * when a change to the split presentation originates from the application state change rather * than driven by parent window changes or new activity starts. The call will be ignored if * there is no visible split. + * * @see #setSplitAttributesCalculator(Function) - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} */ + @RequiresVendorApiLevel(level = 3) default void invalidateTopVisibleSplitAttributes() { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); } /** - * Updates the {@link SplitAttributes} of a split pair. This is an alternative to using - * a split attributes calculator callback, applicable when apps only need to update the - * splits in a few cases but rely on the default split attributes otherwise. + * @deprecated Use {@link #updateSplitAttributes(SplitInfo.Token, SplitAttributes)} instead. + */ + @Deprecated + @RequiresVendorApiLevel(level = 3, deprecatedSince = 5) + default void updateSplitAttributes( + @NonNull IBinder splitInfoToken, @NonNull SplitAttributes splitAttributes) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Updates the {@link SplitAttributes} of a split pair. This is an alternative to using a split + * attributes calculator callback, applicable when apps only need to update the splits in a few + * cases but rely on the default split attributes otherwise. + * * @param splitInfoToken The identifier of the split pair to update. * @param splitAttributes The {@link SplitAttributes} to apply to the split pair. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} */ - default void updateSplitAttributes(@NonNull IBinder splitInfoToken, - @NonNull SplitAttributes splitAttributes) { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + @SuppressWarnings("deprecation") // Use finishActivityStacks(Set). + @RequiresVendorApiLevel(level = 5) + default void updateSplitAttributes( + SplitInfo.@NonNull Token splitInfoToken, @NonNull SplitAttributes splitAttributes) { + updateSplitAttributes(splitInfoToken.getRawToken(), splitAttributes); + } + + /** + * Returns the {@link ParentContainerInfo} by the {@link ActivityStack} token, or {@code null} + * if there's not such {@link ActivityStack} associated with the {@code token}. + * + * @param activityStackToken the token of an {@link ActivityStack}. + */ + @RequiresVendorApiLevel(level = OVERLAY_FEATURE_API_LEVEL) + default @Nullable ParentContainerInfo getParentContainerInfo( + ActivityStack.@NonNull Token activityStackToken) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Sets a function to compute the {@link ActivityStackAttributes} for the ActivityStack given + * for the current window and device state provided in {@link + * ActivityStackAttributesCalculatorParams} on the main thread. + * + * <p>This calculator function is only triggered if the {@link ActivityStack#getTag()} is + * specified. Similar to {@link #setSplitAttributesCalculator(Function)}, the calculator + * function could be triggered multiple times. It will be triggered whenever there's a launching + * standalone {@link ActivityStack} with {@link ActivityStack#getTag()} specified, or a parent + * window or device state update, such as device rotation, folding state change, or the host + * task goes to multi-window mode. + * + * @param calculator The calculator function to calculate {@link ActivityStackAttributes} based + * on {@link ActivityStackAttributesCalculatorParams}. + */ + @RequiresVendorApiLevel(level = OVERLAY_FEATURE_API_LEVEL) + default void setActivityStackAttributesCalculator( + @NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes> + calculator) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Clears the calculator function previously set by {@link + * #setActivityStackAttributesCalculator(Function)} + */ + @RequiresVendorApiLevel(level = OVERLAY_FEATURE_API_LEVEL) + default void clearActivityStackAttributesCalculator() { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Updates {@link ActivityStackAttributes} to an {@link ActivityStack} specified with {@code + * token} and applies the change directly. If there's no such an {@link ActivityStack}, this + * method is no-op. + * + * @param token The {@link ActivityStack} to update. + * @param activityStackAttributes The attributes to be applied + */ + @RequiresVendorApiLevel(level = OVERLAY_FEATURE_API_LEVEL) + default void updateActivityStackAttributes( + ActivityStack.@NonNull Token token, + @NonNull ActivityStackAttributes activityStackAttributes) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Gets the {@link ActivityStack}'s token by {@code tag}, or {@code null} if there's no {@link + * ActivityStack} associated with the {@code tag}. For example, the {@link ActivityStack} is + * dismissed before the is method is called. + * + * <p>The {@link ActivityStack} token can be obtained immediately after the {@link + * ActivityStack} is created. This method is usually used when Activity Embedding library wants + * to {@link #updateActivityStackAttributes} before receiving the {@link ActivityStack} record + * from the callback set by {@link #registerActivityStackCallback}. + * + * <p>For example, an app launches an overlay container and calls {@link + * #updateActivityStackAttributes} immediately right before the overlay {@link ActivityStack} is + * received from {@link #registerActivityStackCallback}. + * + * @param tag A unique identifier of an {@link ActivityStack} if set + * @return The {@link ActivityStack}'s token that the tag is associated with, or {@code null} if + * there's no such an {@link ActivityStack}. + */ + @RequiresVendorApiLevel(level = OVERLAY_FEATURE_API_LEVEL) + default ActivityStack.@Nullable Token getActivityStackToken(@NonNull String tag) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Registers a callback that notifies WindowManager Jetpack about changes in {@link + * ActivityStack}. + * + * <p>In most cases, {@link ActivityStack} are a part of {@link SplitInfo} as {@link + * SplitInfo#getPrimaryActivityStack() the primary ActivityStack} or {@link + * SplitInfo#getSecondaryActivityStack() the secondary ActivityStack} of a {@link SplitInfo}. + * + * <p>However, there are some cases that {@link ActivityStack} is standalone and usually + * expanded. Cases are: + * + * <ul> + * <li>A started {@link Activity} matches {@link ActivityRule} with {@link + * ActivityRule#shouldAlwaysExpand()} {@code true}. + * <li>The {@code ActivityStack} is an overlay {@code ActivityStack}. + * <li>The associated {@link ActivityStack activityStacks} of a {@code ActivityStack} are + * dismissed by {@link #finishActivityStacks(Set)}. + * <li>One {@link ActivityStack} of {@link SplitInfo}(Either {@link + * SplitInfo#getPrimaryActivityStack() the primary ActivityStack} or {@link + * SplitInfo#getSecondaryActivityStack() the secondary ActivityStack}) is empty and + * finished, while the other {@link ActivityStack} is not finished with the finishing + * {@link ActivityStack}. + * <p>An example is a pair of activities matches a {@link SplitPairRule}, and its {@link + * SplitPairRule#getFinishPrimaryWithSecondary()} is {@link SplitRule#FINISH_NEVER}. Then + * if the last activity of {@link SplitInfo#getSecondaryActivityStack() the secondary + * ActivityStack}) is finished, {@link SplitInfo#getPrimaryActivityStack() the primary + * ActivityStack} will still remain. + * </ul> + * + * @param executor the executor to dispatch {@link ActivityStack} list changes. + * @param callback the callback to notify {@link ActivityStack} list changes. + * @see ActivityEmbeddingComponent#finishActivityStacks(Set) + */ + @RequiresVendorApiLevel(level = 5) + default void registerActivityStackCallback( + @NonNull Executor executor, @NonNull Consumer<List<ActivityStack>> callback) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Removes the callback previously registered in {@link #registerActivityStackCallback}, or + * no-op if the callback hasn't been registered yet. + * + * @param callback The callback to remove, which should have been registered. + */ + @RequiresVendorApiLevel(level = 5) + default void unregisterActivityStackCallback(@NonNull Consumer<List<ActivityStack>> callback) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Sets a callback that notifies WindowManager Jetpack about changes for a given {@link + * Activity} to its {@link EmbeddedActivityWindowInfo}. + * + * <p>The callback will be invoked when the {@link EmbeddedActivityWindowInfo} is changed after + * the {@link Activity} is launched. Similar to {@link Activity#onConfigurationChanged}, the + * callback will only be invoked for visible {@link Activity}. + * + * @param executor the executor to dispatch {@link EmbeddedActivityWindowInfo} change. + * @param callback the callback to notify {@link EmbeddedActivityWindowInfo} change. + */ + @RequiresVendorApiLevel(level = 6) + default void setEmbeddedActivityWindowInfoCallback( + @NonNull Executor executor, @NonNull Consumer<EmbeddedActivityWindowInfo> callback) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Clears the callback previously set by {@link #setEmbeddedActivityWindowInfoCallback(Executor, + * Consumer)} + */ + @RequiresVendorApiLevel(level = 6) + default void clearEmbeddedActivityWindowInfoCallback() { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Returns the {@link EmbeddedActivityWindowInfo} of the given {@link Activity}, or {@code null} + * if the {@link Activity} is not attached. + * + * <p>This API can be used when {@link #setEmbeddedActivityWindowInfoCallback} is not set before + * the Activity is attached. + * + * @param activity the {@link Activity} to get {@link EmbeddedActivityWindowInfo} for. + */ + @RequiresVendorApiLevel(level = 6) + default @Nullable EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo( + @NonNull Activity activity) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Sets whether to auto save the embedding state to the system, which can be used to restore the + * app embedding state once the app process is restarted (if applicable). + * + * <p>The embedding state is not saved by default, in which case the embedding state and the + * embedded activities are removed once the app process is killed. + * + * <p>**Note** that the applications should set the {@link EmbeddingRule}s using {@link + * #setEmbeddingRules} when the application is initializing, such as configured in + * [android.app.Application.onCreate], in order to allow the library to restore the state + * properly. Otherwise, the state may not be restored and the activities may not be started and + * layout as expected. + * + * @param saveEmbeddingState whether to save the embedding state. + */ + @RequiresVendorApiLevel(level = 8) + default void setAutoSaveEmbeddingState(boolean saveEmbeddingState) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingOptionsProperties.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingOptionsProperties.java new file mode 100644 index 0000000..888df2b --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingOptionsProperties.java
@@ -0,0 +1,55 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; + +/** + * A class that contains activity embedding properties that puts to or retrieves from {@link + * android.app.ActivityOptions}. + */ +public class ActivityEmbeddingOptionsProperties { + + private ActivityEmbeddingOptionsProperties() {} + + /** + * The key of the unique identifier that put into {@link android.app.ActivityOptions}. + * + * <p>Type: {@link android.os.Bundle#putString(String, String) String} + * + * <p>An {@code OverlayCreateParams} property that represents the unique identifier of the + * overlay container. + */ + public static final String KEY_OVERLAY_TAG = "androidx.window.extensions.embedding.OverlayTag"; + + /** + * The key of {@link ActivityStack.Token#toBundle()} that put into {@link + * android.app.ActivityOptions}. + * + * <p>Type: {@link Bundle#putBundle} + * + * <p>Apps can launch an activity into the {@link ActivityStack} that associated with {@code + * token} by {@link android.app.Activity#startActivity(Intent, Bundle)}. + * + * @see androidx.window.extensions.embedding.ActivityStack.Token#toBundle() + * @see androidx.window.extensions.embedding.ActivityStack.Token#createFromBinder(IBinder) + */ + public static final String KEY_ACTIVITY_STACK_TOKEN = + "androidx.window.extensions.embedding.ActivityStackToken"; +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityRule.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityRule.java index c741647..fd7d062 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityRule.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityRule.java
@@ -16,31 +16,30 @@ package androidx.window.extensions.embedding; -import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import androidx.window.extensions.RequiresVendorApiLevel; import androidx.window.extensions.WindowExtensions; import androidx.window.extensions.core.util.function.Predicate; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + import java.util.Objects; -/** - * Split configuration rule for individual activities. - */ +/** Split configuration rule for individual activities. */ public class ActivityRule extends EmbeddingRule { - @NonNull - private final Predicate<Activity> mActivityPredicate; - @NonNull - private final Predicate<Intent> mIntentPredicate; + private final @NonNull Predicate<Activity> mActivityPredicate; + private final @NonNull Predicate<Intent> mIntentPredicate; private final boolean mShouldAlwaysExpand; - ActivityRule(@NonNull Predicate<Activity> activityPredicate, - @NonNull Predicate<Intent> intentPredicate, boolean shouldAlwaysExpand, + ActivityRule( + @NonNull Predicate<Activity> activityPredicate, + @NonNull Predicate<Intent> intentPredicate, + boolean shouldAlwaysExpand, @Nullable String tag) { super(tag); mActivityPredicate = activityPredicate; @@ -48,19 +47,13 @@ mShouldAlwaysExpand = shouldAlwaysExpand; } - /** - * Checks if the rule is applicable to the provided activity. - */ - @SuppressLint("ClassVerificationFailure") // Only called by Extensions implementation on device. + /** Checks if the rule is applicable to the provided activity. */ @RequiresApi(api = Build.VERSION_CODES.N) public boolean matchesActivity(@NonNull Activity activity) { return mActivityPredicate.test(activity); } - /** - * Checks if the rule is applicable to the provided activity intent. - */ - @SuppressLint("ClassVerificationFailure") // Only called by Extensions implementation on device. + /** Checks if the rule is applicable to the provided activity intent. */ @RequiresApi(api = Build.VERSION_CODES.N) public boolean matchesIntent(@NonNull Intent intent) { return mIntentPredicate.test(intent); @@ -74,28 +67,23 @@ return mShouldAlwaysExpand; } - /** - * Builder for {@link ActivityRule}. - */ + /** Builder for {@link ActivityRule}. */ public static final class Builder { - @NonNull - private final Predicate<Activity> mActivityPredicate; - @NonNull - private final Predicate<Intent> mIntentPredicate; + private final @NonNull Predicate<Activity> mActivityPredicate; + private final @NonNull Predicate<Intent> mIntentPredicate; private boolean mAlwaysExpand; - @Nullable - private String mTag; + private @Nullable String mTag; /** - * @deprecated Use {@link #Builder(Predicate, Predicate)} starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #Builder(Predicate, Predicate)} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. + * @deprecated Use {@link #Builder(Predicate, Predicate)} starting with {@link + * WindowExtensions#VENDOR_API_LEVEL_2}. Only used if {@link #Builder(Predicate, + * Predicate)} can't be called on {@link WindowExtensions#VENDOR_API_LEVEL_1}. */ @Deprecated @RequiresApi(Build.VERSION_CODES.N) - public Builder(@NonNull java.util.function.Predicate<Activity> activityPredicate, - @NonNull java.util.function.Predicate<Intent> intentPredicate) { + public Builder( + java.util.function.@NonNull Predicate<Activity> activityPredicate, + java.util.function.@NonNull Predicate<Intent> intentPredicate) { mActivityPredicate = activityPredicate::test; mIntentPredicate = intentPredicate::test; } @@ -104,37 +92,37 @@ * The {@link ActivityRule} Builder constructor * * @param activityPredicate the {@link Predicate} to verify if a given {@link Activity} - * matches the rule - * @param intentPredicate the {@link Predicate} to verify if a given {@link Intent} - * matches the rule - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * matches the rule + * @param intentPredicate the {@link Predicate} to verify if a given {@link Intent} matches + * the rule */ - public Builder(@NonNull Predicate<Activity> activityPredicate, + @RequiresVendorApiLevel(level = 2) + public Builder( + @NonNull Predicate<Activity> activityPredicate, @NonNull Predicate<Intent> intentPredicate) { mActivityPredicate = activityPredicate; mIntentPredicate = intentPredicate; } - /** @see ActivityRule#shouldAlwaysExpand() */ - @NonNull - public Builder setShouldAlwaysExpand(boolean alwaysExpand) { + /** + * @see ActivityRule#shouldAlwaysExpand() + */ + public @NonNull Builder setShouldAlwaysExpand(boolean alwaysExpand) { mAlwaysExpand = alwaysExpand; return this; } /** * @see ActivityRule#getTag() - * Since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2} */ - @NonNull - public Builder setTag(@NonNull String tag) { + @RequiresVendorApiLevel(level = 2) + public @NonNull Builder setTag(@NonNull String tag) { mTag = Objects.requireNonNull(tag); return this; } /** Builds a new instance of {@link ActivityRule}. */ - @NonNull - public ActivityRule build() { + public @NonNull ActivityRule build() { return new ActivityRule(mActivityPredicate, mIntentPredicate, mAlwaysExpand, mTag); } } @@ -159,10 +147,12 @@ return result; } - @NonNull @Override - public String toString() { - return "ActivityRule{mTag=" + getTag() - + "mShouldAlwaysExpand=" + mShouldAlwaysExpand + '}'; + public @NonNull String toString() { + return "ActivityRule{mTag=" + + getTag() + + ", mShouldAlwaysExpand=" + + mShouldAlwaysExpand + + '}'; } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStack.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStack.java index e568666..e21be7b 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStack.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStack.java
@@ -16,13 +16,17 @@ package androidx.window.extensions.embedding; +import static androidx.window.extensions.embedding.ActivityEmbeddingComponent.OVERLAY_FEATURE_API_LEVEL; + import android.app.Activity; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; -import androidx.annotation.NonNull; -import androidx.annotation.RestrictTo; -import androidx.window.extensions.WindowExtensions; +import androidx.window.extensions.RequiresVendorApiLevel; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -34,81 +38,75 @@ */ public class ActivityStack { - /** Only used for compatibility with the deprecated constructor. */ - private static final IBinder INVALID_ACTIVITY_STACK_TOKEN = new Binder(); - - @NonNull - private final List<Activity> mActivities; + private final @NonNull List<Activity> mActivities; private final boolean mIsEmpty; - @NonNull - private final IBinder mToken; + private final @NonNull Token mToken; + + private final @Nullable String mTag; /** * The {@code ActivityStack} constructor * - * @param activities {@link Activity Activities} in this application's process that - * belongs to this {@code ActivityStack} - * @param isEmpty Indicates whether there's any {@link Activity} running in this - * {@code ActivityStack} + * @param activities {@link Activity Activities} in this application's process that belongs to + * this {@code ActivityStack} + * @param isEmpty Indicates whether there's any {@link Activity} running in this {@code + * ActivityStack} * @param token The token to identify this {@code ActivityStack} - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} + * @param tag A unique identifier of {@link ActivityStack}. Only specifies for the overlay + * standalone {@link ActivityStack} currently. */ - ActivityStack(@NonNull List<Activity> activities, boolean isEmpty, @NonNull IBinder token) { + ActivityStack( + @NonNull List<Activity> activities, + boolean isEmpty, + @NonNull Token token, + @Nullable String tag) { Objects.requireNonNull(activities); Objects.requireNonNull(token); + mActivities = new ArrayList<>(activities); mIsEmpty = isEmpty; mToken = token; - } - - /** - * @deprecated Use the {@link WindowExtensions#VENDOR_API_LEVEL_3} version. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_1} - */ - @Deprecated - ActivityStack(@NonNull List<Activity> activities, boolean isEmpty) { - this(activities, isEmpty, INVALID_ACTIVITY_STACK_TOKEN); + mTag = tag; } /** * Returns {@link Activity Activities} in this application's process that belongs to this * ActivityStack. - * <p> - * Note that Activities that are running in other processes are not reported in the returned - * Activity list. They can be in any position in terms of ordering relative to the activities - * in the list. - * </p> + * + * <p>Note that Activities that are running in other processes are not reported in the returned + * Activity list. They can be in any position in terms of ordering relative to the activities in + * the list. */ - @NonNull - public List<Activity> getActivities() { + public @NonNull List<Activity> getActivities() { return new ArrayList<>(mActivities); } /** * Returns {@code true} if there's no {@link Activity} running in this ActivityStack. - * <p> - * Note that {@link #getActivities()} only report Activity in the process used to create this + * + * <p>Note that {@link #getActivities()} only report Activity in the process used to create this * ActivityStack. That said, if this ActivityStack only contains activities from another * process, {@link #getActivities()} will return empty list, while this method will return * {@code false}. - * </p> */ public boolean isEmpty() { return mIsEmpty; } - /** - * Returns a token uniquely identifying the container. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @NonNull - public IBinder getToken() { + /** Returns a token uniquely identifying the container. */ + @RequiresVendorApiLevel(level = 5) + public @NonNull Token getActivityStackToken() { return mToken; } + /** Returns the associated tag if specified. Otherwise, returns {@code null}. */ + @RequiresVendorApiLevel(level = OVERLAY_FEATURE_API_LEVEL) + public @Nullable String getTag() { + return mTag; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -116,7 +114,8 @@ ActivityStack that = (ActivityStack) o; return mActivities.equals(that.mActivities) && mIsEmpty == that.mIsEmpty - && mToken.equals(that.mToken); + && mToken.equals(that.mToken) + && Objects.equals(mTag, that.mTag); } @Override @@ -124,15 +123,99 @@ int result = (mIsEmpty ? 1 : 0); result = result * 31 + mActivities.hashCode(); result = result * 31 + mToken.hashCode(); + result = result * 31 + Objects.hashCode(mTag); + return result; } - @NonNull @Override - public String toString() { - return "ActivityStack{" + "mActivities=" + mActivities - + ", mIsEmpty=" + mIsEmpty - + ", mToken=" + mToken + public @NonNull String toString() { + return "ActivityStack{" + + "mActivities=" + + mActivities + + ", mIsEmpty=" + + mIsEmpty + + ", mToken=" + + mToken + + ", mTag=" + + mTag + '}'; } + + /** A unique identifier to represent an {@link ActivityStack}. */ + public static final class Token { + + /** An invalid token to provide compatibility value before vendor API level 5. */ + public static final @NonNull Token INVALID_ACTIVITY_STACK_TOKEN = new Token(new Binder()); + + private static final String KEY_ACTIVITY_STACK_RAW_TOKEN = + "androidx.window.extensions" + ".embedding.ActivityStack.Token"; + + private final IBinder mToken; + + Token(@NonNull IBinder token) { + mToken = token; + } + + /** + * Creates an {@link ActivityStack} token from binder. + * + * @param token the raw binder used by OEM Extensions implementation. + */ + @RequiresVendorApiLevel(level = 5) + public static @NonNull Token createFromBinder(@NonNull IBinder token) { + return new Token(token); + } + + /** + * Retrieves an {@link ActivityStack} token from {@link Bundle} if it's valid. + * + * @param bundle the {@link Bundle} to retrieve the {@link ActivityStack} token from. + * @throws IllegalArgumentException if the {@code bundle} isn't valid. + */ + @RequiresVendorApiLevel(level = 5) + public static @NonNull Token readFromBundle(@NonNull Bundle bundle) { + final IBinder token = bundle.getBinder(KEY_ACTIVITY_STACK_RAW_TOKEN); + + if (token == null) { + throw new IllegalArgumentException("Invalid bundle to create ActivityStack Token"); + } + return new Token(token); + } + + /** + * Converts the token to {@link Bundle}. + * + * <p>See {@link ActivityEmbeddingOptionsProperties#KEY_ACTIVITY_STACK_TOKEN} for sample + * usage. + */ + @RequiresVendorApiLevel(level = 5) + public @NonNull Bundle toBundle() { + final Bundle bundle = new Bundle(); + bundle.putBinder(KEY_ACTIVITY_STACK_RAW_TOKEN, mToken); + return bundle; + } + + @NonNull IBinder getRawToken() { + return mToken; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Token)) return false; + Token token = (Token) o; + return Objects.equals(mToken, token.mToken); + } + + @Override + public int hashCode() { + return Objects.hash(mToken); + } + + @Override + public @NonNull String toString() { + return "Token{" + "mToken=" + mToken + '}'; + } + } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStackAttributes.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStackAttributes.java new file mode 100644 index 0000000..0ea2bdc --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStackAttributes.java
@@ -0,0 +1,133 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_ACTIVITY_STACK; + +import android.graphics.Rect; + +import androidx.window.extensions.RequiresVendorApiLevel; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +/** Attributes used to update the layout and configuration of an {@link ActivityStack}. */ +public final class ActivityStackAttributes { + + private final @NonNull Rect mRelativeBounds; + + private final @NonNull WindowAttributes mWindowAttributes; + + private ActivityStackAttributes( + @NonNull Rect relativeBounds, @NonNull WindowAttributes windowAttributes) { + mRelativeBounds = relativeBounds; + mWindowAttributes = windowAttributes; + } + + /** + * Returns the requested bounds of an {@link ActivityStack} which relative to its parent + * container. + * + * <p>{@link Rect#isEmpty() Empty} bounds mean that this {@link ActivityStack} should fill its + * parent container bounds. + */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Rect getRelativeBounds() { + return mRelativeBounds; + } + + /** + * Returns the {@link WindowAttributes} which contains the configurations of the embedded + * Activity windows with this attributes. + */ + @RequiresVendorApiLevel(level = 6) + public @NonNull WindowAttributes getWindowAttributes() { + return mWindowAttributes; + } + + @Override + public int hashCode() { + return mRelativeBounds.hashCode() * 31 + mWindowAttributes.hashCode(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + if (!(obj instanceof ActivityStackAttributes)) return false; + final ActivityStackAttributes attrs = (ActivityStackAttributes) obj; + return mRelativeBounds.equals(attrs.mRelativeBounds) + && mWindowAttributes.equals(attrs.mWindowAttributes); + } + + @Override + public @NonNull String toString() { + return ActivityStackAttributes.class.getSimpleName() + + ": {" + + " relativeBounds=" + + mRelativeBounds + + ", windowAttributes=" + + mWindowAttributes + + "}"; + } + + /** The builder class of {@link ActivityStackAttributes}. */ + public static final class Builder { + + /** The {@link ActivityStackAttributes} builder constructor. */ + @RequiresVendorApiLevel(level = 6) + public Builder() {} + + private final @NonNull Rect mRelativeBounds = new Rect(); + + private @NonNull WindowAttributes mWindowAttributes = + new WindowAttributes(DIM_AREA_ON_ACTIVITY_STACK); + + /** + * Sets the requested relative bounds of the {@link ActivityStack}. If this value is not + * specified, {@link #getRelativeBounds()} defaults to {@link Rect#isEmpty() empty} bounds, + * which means to follow the parent container bounds. + * + * @param relativeBounds The requested relative bounds. + * @return This {@code Builder}. + */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Builder setRelativeBounds(@NonNull Rect relativeBounds) { + mRelativeBounds.set(relativeBounds); + return this; + } + + /** + * Sets the window attributes. If this value is not specified, the {@link + * WindowAttributes#getDimAreaBehavior()} will be only applied on the {@link ActivityStack} + * of the requested activity. + * + * @param attributes The {@link WindowAttributes} + * @return This {@code Builder}. + */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Builder setWindowAttributes(@NonNull WindowAttributes attributes) { + mWindowAttributes = attributes; + return this; + } + + /** Builds an {@link ActivityStackAttributes} instance. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull ActivityStackAttributes build() { + return new ActivityStackAttributes(mRelativeBounds, mWindowAttributes); + } + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStackAttributesCalculatorParams.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStackAttributesCalculatorParams.java new file mode 100644 index 0000000..538a9db --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStackAttributesCalculatorParams.java
@@ -0,0 +1,102 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import android.os.Bundle; + +import androidx.window.extensions.RequiresVendorApiLevel; +import androidx.window.extensions.core.util.function.Function; + +import org.jspecify.annotations.NonNull; + +/** + * The parameter container used in standalone {@link ActivityStack} calculator function to report + * {@link ParentContainerInfo} and associated {@link ActivityStack#getTag()} to calculate {@link + * ActivityStackAttributes} when there's a parent container information update or a standalone + * {@link ActivityStack} is going to be launched. + * + * @see ActivityEmbeddingComponent#setActivityStackAttributesCalculator(Function) + */ +public class ActivityStackAttributesCalculatorParams { + + private final @NonNull ParentContainerInfo mParentContainerInfo; + + private final @NonNull String mActivityStackTag; + + private final @NonNull Bundle mLaunchOptions; + + /** + * {@code ActivityStackAttributesCalculatorParams} constructor. + * + * @param parentContainerInfo The {@link ParentContainerInfo} of the standalone {@link + * ActivityStack} to apply the {@link ActivityStackAttributes}. + * @param activityStackTag The unique identifier of {@link ActivityStack} to apply the {@link + * ActivityStackAttributes}. + * @param launchOptions The options to launch the {@link ActivityStack}. + */ + ActivityStackAttributesCalculatorParams( + @NonNull ParentContainerInfo parentContainerInfo, + @NonNull String activityStackTag, + @NonNull Bundle launchOptions) { + mParentContainerInfo = parentContainerInfo; + mActivityStackTag = activityStackTag; + mLaunchOptions = launchOptions; + } + + /** Returns {@link ParentContainerInfo} of the standalone {@link ActivityStack} to calculate. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull ParentContainerInfo getParentContainerInfo() { + return mParentContainerInfo; + } + + /** Returns unique identifier of the standalone {@link ActivityStack} to calculate. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull String getActivityStackTag() { + return mActivityStackTag; + } + + /** + * Returns options that passed from WM Jetpack to WM Extensions library to launch an {@link + * ActivityStack}. {@link Bundle#isEmpty() empty} options mean there's no launch options. + * + * <p>For example, an {@link ActivityStack} launch options could be an {@link + * android.app.ActivityOptions} bundle that contains information to build an overlay {@link + * ActivityStack}. + * + * <p>The launch options will be used for initializing standalone {@link ActivityStack} with + * {@link #getActivityStackTag()} specified. The logic is owned by WM Jetpack, which is usually + * from the {@link android.app.ActivityOptions}, WM Extensions library must not touch the + * options. + */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Bundle getLaunchOptions() { + return mLaunchOptions; + } + + @Override + public @NonNull String toString() { + return ActivityStackAttributesCalculatorParams.class.getSimpleName() + + ":{" + + "parentContainerInfo=" + + mParentContainerInfo + + "activityStackTag=" + + mActivityStackTag + + "launchOptions=" + + mLaunchOptions + + "}"; + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/AnimationBackground.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/AnimationBackground.java new file mode 100644 index 0000000..7d6634e --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/AnimationBackground.java
@@ -0,0 +1,114 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import android.graphics.Color; + +import androidx.annotation.ColorInt; +import androidx.window.extensions.RequiresVendorApiLevel; + +import org.jspecify.annotations.NonNull; + +import java.util.Objects; + +/** + * A class to represent the background to show while animating embedding activity containers if the + * animation requires a background. + * + * @see SplitAttributes.Builder#setAnimationBackground + */ +public abstract class AnimationBackground { + + /** + * The special {@link AnimationBackground} object representing the default option. When used, + * the system will determine the color to use, such as using the current theme window background + * color. + */ + @RequiresVendorApiLevel(level = 5) + public static final @NonNull AnimationBackground ANIMATION_BACKGROUND_DEFAULT = + new DefaultBackground(); + + /** + * Creates a {@link ColorBackground} that wraps the given color. Only opaque background is + * supported. + * + * @param color the color to be stored. + * @throws IllegalArgumentException if the color is not opaque. + */ + @RequiresVendorApiLevel(level = 5) + public static @NonNull ColorBackground createColorBackground(@ColorInt int color) { + return new ColorBackground(color); + } + + private AnimationBackground() {} + + /** + * @see #ANIMATION_BACKGROUND_DEFAULT + */ + private static class DefaultBackground extends AnimationBackground { + @Override + public String toString() { + return DefaultBackground.class.getSimpleName(); + } + } + + /** + * An {@link AnimationBackground} to specify of using a developer-defined color as the animation + * background. Only opaque background is supported. + * + * @see #createColorBackground(int) + */ + @RequiresVendorApiLevel(level = 5) + public static class ColorBackground extends AnimationBackground { + + @ColorInt private final int mColor; + + private ColorBackground(@ColorInt int color) { + final int alpha = Color.alpha(color); + if (alpha != 255) { + throw new IllegalArgumentException( + "Color must be fully opaque, current alpha is " + alpha); + } + mColor = color; + } + + /** Returns the color to use as the animation background. */ + @RequiresVendorApiLevel(level = 5) + @ColorInt + public int getColor() { + return mColor; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ColorBackground)) return false; + final ColorBackground that = (ColorBackground) o; + return mColor == that.mColor; + } + + @Override + public int hashCode() { + return Objects.hash(mColor); + } + + @Override + public @NonNull String toString() { + return ColorBackground.class.getSimpleName() + " { color: " + mColor + " }"; + } + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/AnimationParams.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/AnimationParams.java new file mode 100644 index 0000000..730b84b --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/AnimationParams.java
@@ -0,0 +1,242 @@ +/* + * Copyright 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 androidx.window.extensions.embedding; + +import android.content.res.Resources; + +import androidx.annotation.AnimRes; +import androidx.window.extensions.RequiresVendorApiLevel; + +import org.jspecify.annotations.NonNull; + +import java.util.Objects; + +/** + * A class to represent the animation parameters to use while animating embedding activity + * containers. + * + * @see SplitAttributes.Builder#setAnimationParams + */ +public final class AnimationParams { + + /** + * The default value for animation resources ID, which means to use the system default + * animation. + */ + @RequiresVendorApiLevel(level = 7) + @SuppressWarnings("ResourceType") // Use as a hint to use the system default animation. + @AnimRes + public static final int DEFAULT_ANIMATION_RESOURCES_ID = 0xFFFFFFFF; + + private final @NonNull AnimationBackground mAnimationBackground; + + @AnimRes private final int mOpenAnimationResId; + + @AnimRes private final int mCloseAnimationResId; + + @AnimRes private final int mChangeAnimationResId; + + /** + * Creates an instance of this {@code AnimationParams}. + * + * @param animationBackground The {@link AnimationBackground} to use for the animation involving + * this {@code AnimationParams} object. + * @param openAnimationResId The animation resources ID from the "android" package to use for + * open transitions. + * @param closeAnimationResId The animation resources ID from the "android" package to use for + * close transitions. + * @param changeAnimationResId The animation resources ID from the "android" package to use for + * change (resize or move) transitions. + */ + private AnimationParams( + @NonNull AnimationBackground animationBackground, + @AnimRes int openAnimationResId, + @AnimRes int closeAnimationResId, + @AnimRes int changeAnimationResId) { + mAnimationBackground = animationBackground; + mOpenAnimationResId = openAnimationResId; + mCloseAnimationResId = closeAnimationResId; + mChangeAnimationResId = changeAnimationResId; + } + + /** Returns the {@link AnimationBackground} to use for the background during the animation. */ + @RequiresVendorApiLevel(level = 7) + public @NonNull AnimationBackground getAnimationBackground() { + return mAnimationBackground; + } + + /** + * Gets the open animation. + * + * @return the open animation transition resources ID from the "android" package. + */ + @RequiresVendorApiLevel(level = 7) + @AnimRes + public int getOpenAnimationResId() { + return mOpenAnimationResId; + } + + /** + * Gets the close animation. + * + * @return the close animation transition resources ID from the "android" package. + */ + @RequiresVendorApiLevel(level = 7) + @AnimRes + public int getCloseAnimationResId() { + return mCloseAnimationResId; + } + + /** + * Gets the change (resize or move) animation. + * + * @return the change (resize or move) animation transition resources ID from the "android" + * package. + */ + @RequiresVendorApiLevel(level = 7) + @AnimRes + public int getChangeAnimationResId() { + return mChangeAnimationResId; + } + + /** + * Builder for creating an instance of {@link AnimationParams}. + * + * <p>- The default animation background is to use the current theme window background color. - + * The default animation resources ID's for transitions is the system default. + */ + public static final class Builder { + private @NonNull AnimationBackground mAnimationBackground = + AnimationBackground.ANIMATION_BACKGROUND_DEFAULT; + + @AnimRes private int mOpenAnimationResId = DEFAULT_ANIMATION_RESOURCES_ID; + + @AnimRes private int mCloseAnimationResId = DEFAULT_ANIMATION_RESOURCES_ID; + + @AnimRes private int mChangeAnimationResId = DEFAULT_ANIMATION_RESOURCES_ID; + + /** Creates a new {@link AnimationParams.Builder} to create {@link AnimationParams}. */ + @RequiresVendorApiLevel(level = 7) + public Builder() {} + + /** + * Sets the {@link AnimationBackground} to use for the background during the animation. The + * default value is {@link AnimationBackground#ANIMATION_BACKGROUND_DEFAULT}, which means to + * use the current theme window background color. + * + * @param background An {@link AnimationBackground} to be used for the animation. + * @return this {@code Builder}. + */ + @RequiresVendorApiLevel(level = 7) + public AnimationParams.@NonNull Builder setAnimationBackground( + @NonNull AnimationBackground background) { + mAnimationBackground = background; + return this; + } + + /** + * Sets the open animation. Use {@link #DEFAULT_ANIMATION_RESOURCES_ID} for the system + * default animation. Use {@code 0} or {@link Resources#ID_NULL} for no animation. + * + * @param resId The resources ID to set from the "android" package. + * @return this {@code Builder}. + */ + @RequiresVendorApiLevel(level = 7) + public AnimationParams.@NonNull Builder setOpenAnimationResId(@AnimRes int resId) { + mOpenAnimationResId = resId; + return this; + } + + /** + * Sets the close animation. Use {@link #DEFAULT_ANIMATION_RESOURCES_ID} for the system + * default animation. Use {@code 0} or {@link Resources#ID_NULL} for no animation. + * + * @param resId The resources ID to set from the "android" package. + * @return this {@code Builder}. + */ + @RequiresVendorApiLevel(level = 7) + public AnimationParams.@NonNull Builder setCloseAnimationResId(@AnimRes int resId) { + mCloseAnimationResId = resId; + return this; + } + + /** + * Sets the change (resize or move) animation. Use {@link #DEFAULT_ANIMATION_RESOURCES_ID} + * for the system default animation. Use {@code 0} or {@link Resources#ID_NULL} for no + * animation. + * + * @param resId The resources ID to set from the "android" package. + * @return this {@code Builder}. + */ + @RequiresVendorApiLevel(level = 7) + public AnimationParams.@NonNull Builder setChangeAnimationResId(@AnimRes int resId) { + mChangeAnimationResId = resId; + return this; + } + + /** + * Builds a {@link AnimationParams} instance with the attributes specified by {@link + * #setAnimationBackground(AnimationBackground)}, {@link #setOpenAnimationResId(int)}, + * {@link #setCloseAnimationResId(int)}, and {@link #setChangeAnimationResId(int)}. + * + * @return the new {@code AnimationParams} instance. + */ + @RequiresVendorApiLevel(level = 7) + public @NonNull AnimationParams build() { + return new AnimationParams( + mAnimationBackground, + mOpenAnimationResId, + mCloseAnimationResId, + mChangeAnimationResId); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AnimationParams)) return false; + AnimationParams that = (AnimationParams) o; + return mAnimationBackground.equals(that.mAnimationBackground) + && mOpenAnimationResId == that.mOpenAnimationResId + && mCloseAnimationResId == that.mCloseAnimationResId + && mChangeAnimationResId == that.mChangeAnimationResId; + } + + @Override + public int hashCode() { + return Objects.hash( + mAnimationBackground, + mOpenAnimationResId, + mCloseAnimationResId, + mChangeAnimationResId); + } + + @Override + public @NonNull String toString() { + return AnimationParams.class.getSimpleName() + + "{" + + "animationBackground=" + + mAnimationBackground + + ", openAnimation=" + + mOpenAnimationResId + + ", closeAnimation=" + + mCloseAnimationResId + + ", changeAnimation=" + + mChangeAnimationResId + + "}"; + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/DividerAttributes.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/DividerAttributes.java new file mode 100644 index 0000000..91afa83 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/DividerAttributes.java
@@ -0,0 +1,530 @@ +/* + * Copyright 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 androidx.window.extensions.embedding; + +import android.graphics.Color; + +import androidx.annotation.ColorInt; +import androidx.annotation.Dimension; +import androidx.annotation.IntDef; +import androidx.window.extensions.RequiresVendorApiLevel; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * The attributes of the divider layout and behavior. + * + * @see SplitAttributes.Builder#setDividerAttributes(DividerAttributes) + */ +public final class DividerAttributes { + + /** A divider type that draws a static line between the primary and secondary containers. */ + public static final int DIVIDER_TYPE_FIXED = 1; + + /** + * A divider type that draws a line between the primary and secondary containers with a drag + * handle that the user can drag and resize the containers. + */ + public static final int DIVIDER_TYPE_DRAGGABLE = 2; + + @IntDef({DIVIDER_TYPE_FIXED, DIVIDER_TYPE_DRAGGABLE}) + @Retention(RetentionPolicy.SOURCE) + @interface DividerType {} + + /** + * A special value to indicate that the ratio is unset. which means the system will choose a + * default value based on the display size and form factor. + * + * @see #getPrimaryMinRatio() + * @see #getPrimaryMaxRatio() + */ + public static final float RATIO_SYSTEM_DEFAULT = -1.0f; + + /** + * A special value to indicate that the width is unset. which means the system will choose a + * default value based on the display size and form factor. + * + * @see #getWidthDp() + */ + public static final int WIDTH_SYSTEM_DEFAULT = -1; + + /** + * The default value for the veil color. When used, the activity window background color will be + * used. + * + * @see #getPrimaryVeilColor() + * @see #getSecondaryVeilColor() + */ + public static final int DIVIDER_VEIL_COLOR_DEFAULT = Color.TRANSPARENT; + + /** The {@link DividerType}. */ + private final @DividerType int mDividerType; + + /** + * The divider width in dp. It defaults to {@link #WIDTH_SYSTEM_DEFAULT}, which means the system + * will choose a default value based on the display size and form factor. + */ + private final @Dimension int mWidthDp; + + /** + * The min split ratio for the primary container. It defaults to {@link #RATIO_SYSTEM_DEFAULT}, + * the system will choose a default value based on the display size and form factor. Will only + * be used when the divider type is {@link #DIVIDER_TYPE_DRAGGABLE}. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code true}, the user is allowed to drag + * beyond this ratio, and when dragging is finished, the system will choose to either fully + * expand the secondary container or move the divider back to this ratio. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code false}, the user is not allowed to + * drag beyond this ratio. + * + * @see SplitAttributes.SplitType.RatioSplitType#getRatio() + */ + private final float mPrimaryMinRatio; + + /** + * The max split ratio for the primary container. It defaults to {@link #RATIO_SYSTEM_DEFAULT}, + * the system will choose a default value based on the display size and form factor. Will only + * be used when the divider type is {@link #DIVIDER_TYPE_DRAGGABLE}. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code true}, the user is allowed to drag + * beyond this ratio, and when dragging is finished, the system will choose to either fully + * expand the primary container or move the divider back to this ratio. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code false}, the user is not allowed to + * drag beyond this ratio. + * + * @see SplitAttributes.SplitType.RatioSplitType#getRatio() + */ + private final float mPrimaryMaxRatio; + + /** The color of the divider. */ + private final @ColorInt int mDividerColor; + + /** Whether it is allowed to expand a container to full screen by dragging the divider. */ + private final boolean mIsDraggingToFullscreenAllowed; + + /** The veil color of the primary container while dragging. */ + private final @ColorInt int mPrimaryVeilColor; + + /** The veil color of the secondary container while dragging. */ + private final @ColorInt int mSecondaryVeilColor; + + /** + * Constructor of {@link DividerAttributes}. + * + * @param dividerType the divider type. See {@link DividerType}. + * @param widthDp the width of the divider. + * @param primaryMinRatio the min split ratio for the primary container. + * @param primaryMaxRatio the max split ratio for the primary container. + * @param dividerColor the color of the divider. + * @param isDraggingToFullscreenAllowed whether it is allowed to expand a container to full + * screen by dragging the divider. + * @param primaryVeilColor the veil color of the primary container while dragging. If {@link + * #DIVIDER_VEIL_COLOR_DEFAULT}, activity window background color is used. + * @param secondaryVeilColor the veil color of the secondary container while dragging. If {@link + * #DIVIDER_VEIL_COLOR_DEFAULT}, activity window background color is used. + * @throws IllegalStateException if the provided values are invalid. + */ + private DividerAttributes( + @DividerType int dividerType, + @Dimension int widthDp, + float primaryMinRatio, + float primaryMaxRatio, + @ColorInt int dividerColor, + boolean isDraggingToFullscreenAllowed, + @ColorInt int primaryVeilColor, + @ColorInt int secondaryVeilColor) { + if (dividerType == DIVIDER_TYPE_FIXED + && (primaryMinRatio != RATIO_SYSTEM_DEFAULT + || primaryMaxRatio != RATIO_SYSTEM_DEFAULT)) { + throw new IllegalStateException( + "primaryMinRatio and primaryMaxRatio must be RATIO_SYSTEM_DEFAULT for " + + "DIVIDER_TYPE_FIXED."); + } + if (dividerType == DIVIDER_TYPE_FIXED + && (primaryVeilColor != DIVIDER_VEIL_COLOR_DEFAULT + || secondaryVeilColor != DIVIDER_VEIL_COLOR_DEFAULT)) { + throw new IllegalStateException( + "primaryVeilColor and secondaryVeilColor must be unset for" + + "DIVIDER_TYPE_FIXED."); + } + if (primaryMinRatio != RATIO_SYSTEM_DEFAULT + && primaryMaxRatio != RATIO_SYSTEM_DEFAULT + && primaryMinRatio > primaryMaxRatio) { + throw new IllegalStateException( + "primaryMinRatio must be less than or equal to primaryMaxRatio"); + } + mDividerType = dividerType; + mWidthDp = widthDp; + mPrimaryMinRatio = primaryMinRatio; + mPrimaryMaxRatio = primaryMaxRatio; + mDividerColor = dividerColor; + mIsDraggingToFullscreenAllowed = isDraggingToFullscreenAllowed; + mPrimaryVeilColor = primaryVeilColor; + mSecondaryVeilColor = secondaryVeilColor; + } + + /** + * Returns the divider type. + * + * @see #DIVIDER_TYPE_FIXED + * @see #DIVIDER_TYPE_DRAGGABLE + */ + @RequiresVendorApiLevel(level = 6) + public @DividerType int getDividerType() { + return mDividerType; + } + + /** + * Returns the width of the divider. It defaults to {@link #WIDTH_SYSTEM_DEFAULT}, which means + * the system will choose a default value based on the display size and form factor. + */ + @RequiresVendorApiLevel(level = 6) + public @Dimension int getWidthDp() { + return mWidthDp; + } + + /** + * Returns the min split ratio for the primary container the divider can be dragged to. It + * defaults to {@link #RATIO_SYSTEM_DEFAULT}, which means the system will choose a default value + * based on the display size and form factor. Will only be used when the divider type is {@link + * #DIVIDER_TYPE_DRAGGABLE}. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code true}, the user is allowed to drag + * beyond this ratio, and when dragging is finished, the system will choose to either fully + * expand the secondary container or move the divider back to this ratio. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code false}, the user is not allowed to + * drag beyond this ratio. + * + * @see SplitAttributes.SplitType.RatioSplitType#getRatio() + */ + @RequiresVendorApiLevel(level = 6) + public float getPrimaryMinRatio() { + return mPrimaryMinRatio; + } + + /** + * Returns the max split ratio for the primary container the divider can be dragged to. It + * defaults to {@link #RATIO_SYSTEM_DEFAULT}, which means the system will choose a default value + * based on the display size and form factor. Will only be used when the divider type is {@link + * #DIVIDER_TYPE_DRAGGABLE}. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code true}, the user is allowed to drag + * beyond this ratio, and when dragging is finished, the system will choose to either fully + * expand the primary container or move the divider back to this ratio. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code false}, the user is not allowed to + * drag beyond this ratio. + * + * @see SplitAttributes.SplitType.RatioSplitType#getRatio() + */ + @RequiresVendorApiLevel(level = 6) + public float getPrimaryMaxRatio() { + return mPrimaryMaxRatio; + } + + /** Returns the color of the divider. */ + @RequiresVendorApiLevel(level = 6) + public @ColorInt int getDividerColor() { + return mDividerColor; + } + + /** + * Returns whether it is allowed to expand a container to full screen by dragging the divider. + * Default is {@code true}. + */ + @RequiresVendorApiLevel(level = 7) + public boolean isDraggingToFullscreenAllowed() { + return mIsDraggingToFullscreenAllowed; + } + + /** + * Returns the veil color of the primary container. {@link #DIVIDER_VEIL_COLOR_DEFAULT} + * indicates that activity window background color should be used. + */ + @RequiresVendorApiLevel(level = 8) + public @ColorInt int getPrimaryVeilColor() { + return mPrimaryVeilColor; + } + + /** + * Returns the veil color of the secondary container. {@link #DIVIDER_VEIL_COLOR_DEFAULT} + * indicates that activity window background color should be used. + */ + @RequiresVendorApiLevel(level = 8) + public @ColorInt int getSecondaryVeilColor() { + return mSecondaryVeilColor; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + if (!(obj instanceof DividerAttributes)) return false; + final DividerAttributes other = (DividerAttributes) obj; + return mDividerType == other.mDividerType + && mWidthDp == other.mWidthDp + && mPrimaryMinRatio == other.mPrimaryMinRatio + && mPrimaryMaxRatio == other.mPrimaryMaxRatio + && mDividerColor == other.mDividerColor + && mIsDraggingToFullscreenAllowed == other.mIsDraggingToFullscreenAllowed + && mPrimaryVeilColor == other.mPrimaryVeilColor + && mSecondaryVeilColor == other.mSecondaryVeilColor; + } + + @Override + public int hashCode() { + return Objects.hash( + mDividerType, + mWidthDp, + mPrimaryMinRatio, + mPrimaryMaxRatio, + mIsDraggingToFullscreenAllowed, + mPrimaryVeilColor, + mSecondaryVeilColor); + } + + @Override + public @NonNull String toString() { + return DividerAttributes.class.getSimpleName() + + "{" + + "dividerType=" + + mDividerType + + ", width=" + + mWidthDp + + ", minPrimaryRatio=" + + mPrimaryMinRatio + + ", maxPrimaryRatio=" + + mPrimaryMaxRatio + + ", dividerColor=" + + mDividerColor + + ", isDraggingToFullscreenAllowed=" + + mIsDraggingToFullscreenAllowed + + ", mPrimaryVeilColor=" + + mPrimaryVeilColor + + ", mSecondaryVeilColor=" + + mSecondaryVeilColor + + "}"; + } + + /** The {@link DividerAttributes} builder. */ + public static final class Builder { + + private final @DividerType int mDividerType; + + private @Dimension int mWidthDp = WIDTH_SYSTEM_DEFAULT; + + private float mPrimaryMinRatio = RATIO_SYSTEM_DEFAULT; + + private float mPrimaryMaxRatio = RATIO_SYSTEM_DEFAULT; + + private @ColorInt int mDividerColor = Color.BLACK; + + private boolean mIsDraggingToFullscreenAllowed = false; + + private @ColorInt int mPrimaryVeilColor = DIVIDER_VEIL_COLOR_DEFAULT; + + private @ColorInt int mSecondaryVeilColor = DIVIDER_VEIL_COLOR_DEFAULT; + + /** + * The {@link DividerAttributes} builder constructor. + * + * @param dividerType the divider type, possible values are {@link #DIVIDER_TYPE_FIXED} and + * {@link #DIVIDER_TYPE_DRAGGABLE}. + */ + @RequiresVendorApiLevel(level = 6) + public Builder(@DividerType int dividerType) { + mDividerType = dividerType; + } + + /** + * The {@link DividerAttributes} builder constructor initialized by an existing {@link + * DividerAttributes}. + * + * @param original the original {@link DividerAttributes} to initialize the {@link Builder}. + */ + @RequiresVendorApiLevel(level = 6) + public Builder(@NonNull DividerAttributes original) { + Objects.requireNonNull(original); + mDividerType = original.mDividerType; + mWidthDp = original.mWidthDp; + mPrimaryMinRatio = original.mPrimaryMinRatio; + mPrimaryMaxRatio = original.mPrimaryMaxRatio; + mDividerColor = original.mDividerColor; + mIsDraggingToFullscreenAllowed = original.mIsDraggingToFullscreenAllowed; + mPrimaryVeilColor = original.mPrimaryVeilColor; + mSecondaryVeilColor = original.mSecondaryVeilColor; + } + + /** + * Sets the divider width. It defaults to {@link #WIDTH_SYSTEM_DEFAULT}, which means the + * system will choose a default value based on the display size and form factor. + * + * @throws IllegalArgumentException if the provided value is invalid. + */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Builder setWidthDp(@Dimension int widthDp) { + if (widthDp != WIDTH_SYSTEM_DEFAULT && widthDp < 0) { + throw new IllegalArgumentException( + "widthDp must be greater than or equal to 0 or WIDTH_SYSTEM_DEFAULT."); + } + mWidthDp = widthDp; + return this; + } + + /** + * Sets the min split ratio for the primary container. It defaults to {@link + * #RATIO_SYSTEM_DEFAULT}, which means the system will choose a default value based on the + * display size and form factor. Will only be used when the divider type is {@link + * #DIVIDER_TYPE_DRAGGABLE}. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code true}, the user is allowed to + * drag beyond this ratio, and when dragging is finished, the system will choose to either + * fully expand the secondary container or move the divider back to this ratio. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code false}, the user is not allowed + * to drag beyond this ratio. + * + * @param primaryMinRatio the min ratio for the primary container. Must be in range [0.0, + * 1.0) or {@link #RATIO_SYSTEM_DEFAULT}. + * @throws IllegalArgumentException if the provided value is invalid. + * @see SplitAttributes.SplitType.RatioSplitType#getRatio() + */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Builder setPrimaryMinRatio(float primaryMinRatio) { + if (primaryMinRatio != RATIO_SYSTEM_DEFAULT + && (primaryMinRatio >= 1.0 || primaryMinRatio < 0.0)) { + throw new IllegalArgumentException( + "primaryMinRatio must be in [0.0, 1.0) or RATIO_SYSTEM_DEFAULT."); + } + mPrimaryMinRatio = primaryMinRatio; + return this; + } + + /** + * Sets the max split ratio for the primary container. It defaults to {@link + * #RATIO_SYSTEM_DEFAULT}, which means the system will choose a default value based on the + * display size and form factor. Will only be used when the divider type is {@link + * #DIVIDER_TYPE_DRAGGABLE}. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code true}, the user is allowed to + * drag beyond this ratio, and when dragging is finished, the system will choose to either + * fully expand the primary container or move the divider back to this ratio. + * + * <p>If {@link #isDraggingToFullscreenAllowed()} is {@code false}, the user is not allowed + * to drag beyond this ratio. + * + * @param primaryMaxRatio the max ratio for the primary container. Must be in range (0.0, + * 1.0] or {@link #RATIO_SYSTEM_DEFAULT}. + * @throws IllegalArgumentException if the provided value is invalid. + * @see SplitAttributes.SplitType.RatioSplitType#getRatio() + */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Builder setPrimaryMaxRatio(float primaryMaxRatio) { + if (primaryMaxRatio != RATIO_SYSTEM_DEFAULT + && (primaryMaxRatio > 1.0 || primaryMaxRatio <= 0.0)) { + throw new IllegalArgumentException( + "primaryMaxRatio must be in (0.0, 1.0] or RATIO_SYSTEM_DEFAULT."); + } + mPrimaryMaxRatio = primaryMaxRatio; + return this; + } + + /** + * Sets the color of the divider. If not set, the default color {@link Color#BLACK} is used. + * Only the RGB components are used and the alpha value is ignored and always considered as + * fully opaque. + */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Builder setDividerColor(@ColorInt int dividerColor) { + mDividerColor = dividerColor; + return this; + } + + /** + * Sets whether it is allowed to expand a container to full screen by dragging the divider. + * Default is {@code true}. + */ + @RequiresVendorApiLevel(level = 7) + public @NonNull Builder setDraggingToFullscreenAllowed( + boolean isDraggingToFullscreenAllowed) { + mIsDraggingToFullscreenAllowed = isDraggingToFullscreenAllowed; + return this; + } + + /** + * Sets the veil color of the primary container. Solid color veils are used to cover + * activity content while dragging. + * + * <p>The default value is {@link #DIVIDER_VEIL_COLOR_DEFAULT}. + * + * @param color the veil color for the primary container. If the value equals to {@link + * #DIVIDER_VEIL_COLOR_DEFAULT}, activity window background color is used. If {@link + * Color#TRANSPARENT} is used, it is treated as {@link #DIVIDER_VEIL_COLOR_DEFAULT}. + * Only the RGB components are used and the alpha value is ignored and always considered + * as fully opaque. + */ + @RequiresVendorApiLevel(level = 8) + public @NonNull Builder setPrimaryVeilColor(@ColorInt int color) { + mPrimaryVeilColor = color; + return this; + } + + /** + * Sets the veil color of the secondary container. Solid color veils are used to cover + * activity content while dragging. + * + * <p>The default value is {@link #DIVIDER_VEIL_COLOR_DEFAULT}. + * + * @param color the veil color for the secondary container. If the value equals to {@link + * #DIVIDER_VEIL_COLOR_DEFAULT}, activity window background color is used. If {@link + * Color#TRANSPARENT} is used, it is treated as {@link #DIVIDER_VEIL_COLOR_DEFAULT}. + * Only the RGB components are used and the alpha value is ignored and always considered + * as fully opaque. + */ + @RequiresVendorApiLevel(level = 8) + public @NonNull Builder setSecondaryVeilColor(@ColorInt int color) { + mSecondaryVeilColor = color; + return this; + } + + /** + * Builds a {@link DividerAttributes} instance. + * + * @return a {@link DividerAttributes} instance. + * @throws IllegalArgumentException if the provided values are invalid. + */ + @RequiresVendorApiLevel(level = 6) + public @NonNull DividerAttributes build() { + return new DividerAttributes( + mDividerType, + mWidthDp, + mPrimaryMinRatio, + mPrimaryMaxRatio, + mDividerColor, + mIsDraggingToFullscreenAllowed, + mPrimaryVeilColor, + mSecondaryVeilColor); + } + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddedActivityWindowInfo.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddedActivityWindowInfo.java new file mode 100644 index 0000000..12b8bb1 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddedActivityWindowInfo.java
@@ -0,0 +1,115 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import android.app.Activity; +import android.graphics.Rect; + +import androidx.window.extensions.RequiresVendorApiLevel; + +import org.jspecify.annotations.NonNull; + +import java.util.Objects; + +/** + * Describes the embedded window related info of an activity. + * + * @see ActivityEmbeddingComponent#setEmbeddedActivityWindowInfoCallback + * @see ActivityEmbeddingComponent#getEmbeddedActivityWindowInfo + */ +public class EmbeddedActivityWindowInfo { + + private final @NonNull Activity mActivity; + private final boolean mIsEmbedded; + private final @NonNull Rect mTaskBounds; + private final @NonNull Rect mActivityStackBounds; + + EmbeddedActivityWindowInfo( + @NonNull Activity activity, + boolean isEmbedded, + @NonNull Rect taskBounds, + @NonNull Rect activityStackBounds) { + mActivity = Objects.requireNonNull(activity); + mIsEmbedded = isEmbedded; + mTaskBounds = Objects.requireNonNull(taskBounds); + mActivityStackBounds = Objects.requireNonNull(activityStackBounds); + } + + /** Returns the {@link Activity} this {@link EmbeddedActivityWindowInfo} is about. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Activity getActivity() { + return mActivity; + } + + /** + * Whether this activity is embedded, which means it is in an ActivityStack window that doesn't + * fill the Task. + */ + @RequiresVendorApiLevel(level = 6) + public boolean isEmbedded() { + return mIsEmbedded; + } + + /** Returns the bounds of the Task window in display space. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Rect getTaskBounds() { + return mTaskBounds; + } + + /** + * Returns the bounds of the ActivityStack window in display space. This can be referring to the + * bounds of the same window as {@link #getTaskBounds()} when the activity is not embedded. + */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Rect getActivityStackBounds() { + return mActivityStackBounds; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof EmbeddedActivityWindowInfo)) return false; + final EmbeddedActivityWindowInfo that = (EmbeddedActivityWindowInfo) o; + return mActivity.equals(that.mActivity) + && mIsEmbedded == that.mIsEmbedded + && mTaskBounds.equals(that.mTaskBounds) + && mActivityStackBounds.equals(that.mActivityStackBounds); + } + + @Override + public int hashCode() { + int result = mActivity.hashCode(); + result = result * 31 + (mIsEmbedded ? 1 : 0); + result = result * 31 + mTaskBounds.hashCode(); + result = result * 31 + mActivityStackBounds.hashCode(); + return result; + } + + @Override + public @NonNull String toString() { + return "EmbeddedActivityWindowInfo{" + + "activity=" + + mActivity + + ", isEmbedded=" + + mIsEmbedded + + ", taskBounds=" + + mTaskBounds + + ", activityStackBounds=" + + mActivityStackBounds + + "}"; + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddingRule.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddingRule.java index 9333bdc..ff379fe 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddingRule.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddingRule.java
@@ -16,9 +16,11 @@ package androidx.window.extensions.embedding; -import androidx.annotation.Nullable; +import androidx.window.extensions.RequiresVendorApiLevel; import androidx.window.extensions.core.util.function.Function; +import org.jspecify.annotations.Nullable; + import java.util.Objects; /** @@ -26,25 +28,21 @@ * updating from the core library. */ public abstract class EmbeddingRule { - @Nullable - private final String mTag; + private final @Nullable String mTag; EmbeddingRule(@Nullable String tag) { mTag = tag; } /** - * A unique string to identify this {@link EmbeddingRule}. - * The suggested usage is to set the tag in the corresponding rule builder to be able to - * differentiate between different rules in {@link SplitAttributes} calculator function. For - * example, it can be used to compute the {@link SplitAttributes} for the specific - * {@link SplitRule} in the {@link Function} set with + * A unique string to identify this {@link EmbeddingRule}. The suggested usage is to set the tag + * in the corresponding rule builder to be able to differentiate between different rules in + * {@link SplitAttributes} calculator function. For example, it can be used to compute the + * {@link SplitAttributes} for the specific {@link SplitRule} in the {@link Function} set with * {@link ActivityEmbeddingComponent#setSplitAttributesCalculator(Function)}. - * - * Since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2} */ - @Nullable - public String getTag() { + @RequiresVendorApiLevel(level = 2) + public @Nullable String getTag() { return mTag; }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ParentContainerInfo.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ParentContainerInfo.java new file mode 100644 index 0000000..1e4f686 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ParentContainerInfo.java
@@ -0,0 +1,105 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import android.content.res.Configuration; +import android.view.WindowMetrics; + +import androidx.window.extensions.RequiresVendorApiLevel; +import androidx.window.extensions.layout.WindowLayoutInfo; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +/** + * The parent container information of an {@link ActivityStack}. The data class is designed to + * provide information to calculate the presentation of an {@link ActivityStack}. + */ +@RequiresVendorApiLevel(level = 6) +public class ParentContainerInfo { + private final @NonNull WindowMetrics mWindowMetrics; + + private final @NonNull Configuration mConfiguration; + + private final @NonNull WindowLayoutInfo mWindowLayoutInfo; + + /** + * {@link ParentContainerInfo} constructor, which is used in Window Manager Extensions to + * provide information of a parent window container. + * + * @param windowMetrics The parent container's {@link WindowMetrics} + * @param configuration The parent container's {@link Configuration} + * @param windowLayoutInfo The parent container's {@link WindowLayoutInfo} + */ + ParentContainerInfo( + @NonNull WindowMetrics windowMetrics, + @NonNull Configuration configuration, + @NonNull WindowLayoutInfo windowLayoutInfo) { + mWindowMetrics = windowMetrics; + mConfiguration = configuration; + mWindowLayoutInfo = windowLayoutInfo; + } + + /** Returns the parent container's {@link WindowMetrics}. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull WindowMetrics getWindowMetrics() { + return mWindowMetrics; + } + + /** Returns the parent container's {@link Configuration}. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Configuration getConfiguration() { + return mConfiguration; + } + + /** Returns the parent container's {@link WindowLayoutInfo}. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull WindowLayoutInfo getWindowLayoutInfo() { + return mWindowLayoutInfo; + } + + @Override + public int hashCode() { + int result = mWindowMetrics.hashCode(); + result = 31 * result + mConfiguration.hashCode(); + result = 31 * result + mWindowLayoutInfo.hashCode(); + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + if (!(obj instanceof ParentContainerInfo)) return false; + final ParentContainerInfo parentContainerInfo = (ParentContainerInfo) obj; + return mWindowMetrics.equals(parentContainerInfo.mWindowMetrics) + && mConfiguration.equals(parentContainerInfo.mConfiguration) + && mWindowLayoutInfo.equals(parentContainerInfo.mWindowLayoutInfo); + } + + @Override + public @NonNull String toString() { + return ParentContainerInfo.class.getSimpleName() + + ": {" + + "windowMetrics=" + + WindowMetricsCompat.toString(mWindowMetrics) + + ", configuration=" + + mConfiguration + + ", windowLayoutInfo=" + + mWindowLayoutInfo + + "}"; + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributes.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributes.java index e00e910..708f37a 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributes.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributes.java
@@ -16,80 +16,64 @@ package androidx.window.extensions.embedding; -import static androidx.annotation.RestrictTo.Scope.LIBRARY; import static androidx.window.extensions.embedding.SplitAttributes.LayoutDirection.BOTTOM_TO_TOP; import static androidx.window.extensions.embedding.SplitAttributes.LayoutDirection.LEFT_TO_RIGHT; import static androidx.window.extensions.embedding.SplitAttributes.LayoutDirection.LOCALE; import static androidx.window.extensions.embedding.SplitAttributes.LayoutDirection.RIGHT_TO_LEFT; import static androidx.window.extensions.embedding.SplitAttributes.LayoutDirection.TOP_TO_BOTTOM; +import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK; import android.annotation.SuppressLint; -import android.graphics.Color; -import androidx.annotation.ColorInt; import androidx.annotation.FloatRange; import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; +import androidx.window.extensions.RequiresVendorApiLevel; import androidx.window.extensions.core.util.function.Function; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; /** - * Attributes that describe how the parent window (typically the activity task - * window) is split between the primary and secondary activity containers, - * including: + * Attributes that describe how the parent window (typically the activity task window) is split + * between the primary and secondary activity containers, including: + * * <ul> - * <li>Split type -- Categorizes the split and specifies the sizes of the - * primary and secondary activity containers relative to the parent - * bounds</li> - * <li>Layout direction -- Specifies whether the parent window is split - * vertically or horizontally and in which direction the primary and - * secondary containers are respectively positioned (left to right, - * right to left, top to bottom, and so forth)</li> - * <li>Animation background color -- The color of the background during - * animation of the split involving this {@code SplitAttributes} object - * if the animation requires a background</li> + * <li>Split type -- Categorizes the split and specifies the sizes of the primary and secondary + * activity containers relative to the parent bounds + * <li>Layout direction -- Specifies whether the parent window is split vertically or horizontally + * and in which direction the primary and secondary containers are respectively positioned + * (left to right, right to left, top to bottom, and so forth) + * <li>Animation background -- The background to show during animation of the split involving this + * {@code SplitAttributes} object if the animation requires a background * </ul> * * <p>Attributes can be configured by: + * * <ul> - * <li>Setting the default {@code SplitAttributes} using - * {@link SplitPairRule.Builder#setDefaultSplitAttributes} or - * {@link SplitPlaceholderRule.Builder#setDefaultSplitAttributes}.</li> - * <li>Using {@link ActivityEmbeddingComponent#setSplitAttributesCalculator(Function)} to set - * the callback to customize the {@code SplitAttributes} for a given device and window - * state.</li> + * <li>Setting the default {@code SplitAttributes} using {@link + * SplitPairRule.Builder#setDefaultSplitAttributes} or {@link + * SplitPlaceholderRule.Builder#setDefaultSplitAttributes}. + * <li>Using {@link ActivityEmbeddingComponent#setSplitAttributesCalculator(Function)} to set the + * callback to customize the {@code SplitAttributes} for a given device and window state. * </ul> * * @see SplitAttributes.SplitType * @see SplitAttributes.LayoutDirection - * Since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2} + * @see AnimationParams */ +@RequiresVendorApiLevel(level = 2) public class SplitAttributes { /** - * The default value for animation background color, which means to use the current theme window - * background color. - * - * Only opaque color is supported, so {@code 0} is used as the default. Any other non-opaque - * color will be treated as the default. - * - * @see Builder#setAnimationBackgroundColor(int) - */ - @ColorInt - @RestrictTo(LIBRARY) - public static final int DEFAULT_ANIMATION_BACKGROUND_COLOR = 0; - - /** - * The type of window split, which defines the proportion of the parent - * window occupied by the primary and secondary activity containers. + * The type of window split, which defines the proportion of the parent window occupied by the + * primary and secondary activity containers. */ public static class SplitType { - @NonNull - private final String mDescription; + private final @NonNull String mDescription; SplitType(@NonNull String description) { mDescription = description; @@ -112,15 +96,13 @@ return mDescription.equals(that.mDescription); } - @NonNull @Override - public String toString() { + public @NonNull String toString() { return mDescription; } @SuppressLint("Range") // The range is covered. - @NonNull - static SplitType createSplitTypeFromLegacySplitRatio( + static @NonNull SplitType createSplitTypeFromLegacySplitRatio( @FloatRange(from = 0.0, to = 1.0) float splitRatio) { // Treat 0.0 and 1.0 as ExpandContainerSplitType because it means the parent container // is filled with secondary or primary container. @@ -131,20 +113,20 @@ } /** - * A window split that's based on the ratio of the size of the primary - * container to the size of the parent window. + * A window split that's based on the ratio of the size of the primary container to the size + * of the parent window (excluding area unavailable for the containers such as the divider. + * See {@link DividerAttributes}). * - * <p>Values in the non-inclusive range (0.0, 1.0) define the size of - * the primary container relative to the size of the parent window: + * <p>Values in the non-inclusive range (0.0, 1.0) define the size of the primary container + * relative to the size of the parent window: + * * <ul> - * <li>0.5 -- Primary container occupies half of the parent - * window; secondary container, the other half</li> - * <li>Greater than 0.5 -- Primary container occupies a larger - * proportion of the parent window than the secondary - * container</li> - * <li>Less than 0.5 -- Primary container occupies a smaller - * proportion of the parent window than the secondary - * container</li> + * <li>0.5 -- Primary container occupies half of the parent window; secondary container, + * the other half + * <li>Greater than 0.5 -- Primary container occupies a larger proportion of the parent + * window than the secondary container + * <li>Less than 0.5 -- Primary container occupies a smaller proportion of the parent + * window than the secondary container * </ul> */ public static final class RatioSplitType extends SplitType { @@ -154,29 +136,30 @@ /** * Creates an instance of this {@code RatioSplitType}. * - * @param ratio The proportion of the parent window occupied by the - * primary container of the split. Can be a value in the - * non-inclusive range (0.0, 1.0). Use - * {@link SplitType.ExpandContainersSplitType} to create a split - * type that occupies the entire parent window. + * @param ratio The proportion of the parent window occupied by the primary container of + * the split (excluding area unavailable for the containers such as the divider. See + * {@link DividerAttributes}). Can be a value in the non-inclusive range (0.0, 1.0). + * Use {@link SplitType.ExpandContainersSplitType} to create a split type that + * occupies the entire parent window. */ public RatioSplitType( @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false) - float ratio) { + float ratio) { super("ratio:" + ratio); if (ratio <= 0.0f || ratio >= 1.0f) { - throw new IllegalArgumentException("Ratio must be in range (0.0, 1.0). " - + " Use SplitType.ExpandContainersSplitType() instead of 0 or 1."); + throw new IllegalArgumentException( + "Ratio must be in range (0.0, 1.0). Use" + + " SplitType.ExpandContainersSplitType() instead of 0 or 1."); } mRatio = ratio; } /** - * Gets the proportion of the parent window occupied by the primary - * activity container of the split. + * Gets the proportion of the parent window occupied by the primary activity container + * of the split (excluding area unavailable for the containers such as the divider. See + * {@link DividerAttributes}) . * - * @return The proportion of the split occupied by the primary - * container. + * @return The proportion of the split occupied by the primary container. */ @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false) public float getRatio() { @@ -184,58 +167,51 @@ } /** - * Creates a split type in which the primary and secondary - * containers occupy equal portions of the parent window. + * Creates a split type in which the primary and secondary containers occupy equal + * portions of the parent window. * - * Serves as the default {@link SplitType} if - * {@link SplitAttributes.Builder#setSplitType(SplitType)} is not - * specified. + * <p>Serves as the default {@link SplitType} if {@link + * SplitAttributes.Builder#setSplitType(SplitType)} is not specified. * - * @return A {@code RatioSplitType} in which the activity containers - * occupy equal portions of the parent window. + * @return A {@code RatioSplitType} in which the activity containers occupy equal + * portions of the parent window. */ - @NonNull - public static RatioSplitType splitEqually() { + public static @NonNull RatioSplitType splitEqually() { return new RatioSplitType(0.5f); } } /** - * A parent window split in which the split ratio conforms to the - * position of a hinge or separating fold in the device display. + * A parent window split in which the split ratio conforms to the position of a hinge or + * separating fold in the device display. * - * The split type is created only if: + * <p>The split type is created only if: + * * <ul> - * <li>The host task is not in multi-window mode (e.g., - * split-screen mode or picture-in-picture mode)</li> - * <li>The device has a hinge or separating fold reported by - * [androidx.window.layout.FoldingFeature.isSeparating]</li> - * <li>The hinge or separating fold orientation matches how the - * parent bounds are split: - * <ul> - * <li>The hinge or fold orientation is vertical, and - * the task bounds are also split vertically - * (containers are side by side)</li> - * <li>The hinge or fold orientation is horizontal, and - * the task bounds are also split horizontally - * (containers are top and bottom)</li> - * </ul> - * </li> + * <li>The host task is not in multi-window mode (e.g., split-screen mode or + * picture-in-picture mode) + * <li>The device has a hinge or separating fold reported by + * [androidx.window.layout.FoldingFeature.isSeparating] + * <li>The hinge or separating fold orientation matches how the parent bounds are split: + * <ul> + * <li>The hinge or fold orientation is vertical, and the task bounds are also split + * vertically (containers are side by side) + * <li>The hinge or fold orientation is horizontal, and the task bounds are also + * split horizontally (containers are top and bottom) + * </ul> * </ul> * - * Otherwise, the type falls back to the {@code SplitType} returned by - * {@link #getFallbackSplitType()}. + * Otherwise, the type falls back to the {@code SplitType} returned by {@link + * #getFallbackSplitType()}. */ public static final class HingeSplitType extends SplitType { - @NonNull - private final SplitType mFallbackSplitType; + private final @NonNull SplitType mFallbackSplitType; /** * Creates an instance of this {@code HingeSplitType}. * - * @param fallbackSplitType The split type to use if a split based - * on the device hinge or separating fold cannot be determined. - * Can be a {@link RatioSplitType} or + * @param fallbackSplitType The split type to use if a split based on the device hinge + * or separating fold cannot be determined. Can be a {@link RatioSplitType} or * {@link ExpandContainersSplitType}. */ public HingeSplitType(@NonNull SplitType fallbackSplitType) { @@ -244,132 +220,123 @@ } /** - * Returns the fallback {@link SplitType} if a split based on the - * device hinge or separating fold cannot be determined. + * Returns the fallback {@link SplitType} if a split based on the device hinge or + * separating fold cannot be determined. */ - @NonNull - public SplitType getFallbackSplitType() { + public @NonNull SplitType getFallbackSplitType() { return mFallbackSplitType; } } /** - * A window split in which the primary and secondary activity containers - * each occupy the entire parent window. + * A window split in which the primary and secondary activity containers each occupy the + * entire parent window. * - * The secondary container overlays the primary container. + * <p>The secondary container overlays the primary container. */ public static final class ExpandContainersSplitType extends SplitType { - /** - * Creates an instance of this {@code ExpandContainersSplitType}. - */ + /** Creates an instance of this {@code ExpandContainersSplitType}. */ public ExpandContainersSplitType() { super("expandContainers"); } } } - /** - * The layout direction of the primary and secondary activity containers. - */ + /** The layout direction of the primary and secondary activity containers. */ public static final class LayoutDirection { /** * Specifies that the parent bounds are split vertically (side to side). * - * Places the primary container in the left portion of the parent - * window, and the secondary container in the right portion. + * <p>Places the primary container in the left portion of the parent window, and the + * secondary container in the right portion. * - * A possible return value of {@link SplitType#getLayoutDirection()}. + * <p>A possible return value of {@link SplitType#getLayoutDirection()}. */ - // - // ------------------------- - // | | | - // | Primary | Secondary | - // | | | - // ------------------------- - // - // Must match {@link LayoutDirection#LTR} for backwards compatibility - // with prior versions of Extensions. + // + // ------------------------- + // | | | + // | Primary | Secondary | + // | | | + // ------------------------- + // + // Must match {@link LayoutDirection#LTR} for backwards compatibility + // with prior versions of Extensions. public static final int LEFT_TO_RIGHT = 0; /** - * Specifies that the parent bounds are split vertically (side to - * side). + * Specifies that the parent bounds are split vertically (side to side). * - * Places the primary container in the right portion of the parent - * window, and the secondary container in the left portion. + * <p>Places the primary container in the right portion of the parent window, and the + * secondary container in the left portion. * - * A possible return value of {@link SplitType#getLayoutDirection()}. + * <p>A possible return value of {@link SplitType#getLayoutDirection()}. */ - // ------------------------- - // | | | - // | Secondary | Primary | - // | | | - // ------------------------- - // - // Must match {@link LayoutDirection#RTL} for backwards compatibility - // with prior versions of Extensions. + // ------------------------- + // | | | + // | Secondary | Primary | + // | | | + // ------------------------- + // + // Must match {@link LayoutDirection#RTL} for backwards compatibility + // with prior versions of Extensions. public static final int RIGHT_TO_LEFT = 1; /** * Specifies that the parent bounds are split vertically (side to side). * - * The direction of the primary and secondary containers is deduced from - * the locale as either {@link #LEFT_TO_RIGHT} or - * {@link #RIGHT_TO_LEFT}. + * <p>The direction of the primary and secondary containers is deduced from the locale as + * either {@link #LEFT_TO_RIGHT} or {@link #RIGHT_TO_LEFT}. * - * A possible return value of {@link SplitType#getLayoutDirection()}. + * <p>A possible return value of {@link SplitType#getLayoutDirection()}. */ - // Must match {@link LayoutDirection#LOCALE} for backwards - // compatibility with prior versions of Extensions. + // Must match {@link LayoutDirection#LOCALE} for backwards + // compatibility with prior versions of Extensions. public static final int LOCALE = 3; /** - * Specifies that the parent bounds are split horizontally (top and - * bottom). + * Specifies that the parent bounds are split horizontally (top and bottom). * - * Places the primary container in the top portion of the parent window, - * and the secondary container in the bottom portion. + * <p>Places the primary container in the top portion of the parent window, and the + * secondary container in the bottom portion. * - * If the horizontal layout direction is not supported on the device, - * layout direction falls back to {@link #LOCALE}. + * <p>If the horizontal layout direction is not supported on the device, layout direction + * falls back to {@link #LOCALE}. * - * A possible return value of {@link SplitType#getLayoutDirection()}. + * <p>A possible return value of {@link SplitType#getLayoutDirection()}. */ - // ------------- - // | | - // | Primary | - // | | - // ------------- - // | | - // | Secondary | - // | | - // ------------- + // ------------- + // | | + // | Primary | + // | | + // ------------- + // | | + // | Secondary | + // | | + // ------------- public static final int TOP_TO_BOTTOM = 4; /** - * Specifies that the parent bounds are split horizontally (top and - * bottom). + * Specifies that the parent bounds are split horizontally (top and bottom). * - * Places the primary container in the bottom portion of the parent - * window, and the secondary container in the top portion. + * <p>Places the primary container in the bottom portion of the parent window, and the + * secondary container in the top portion. * - * If the horizontal layout direction is not supported on the device, - * layout direction falls back to {@link #LOCALE}. + * <p>If the horizontal layout direction is not supported on the device, layout direction + * falls back to {@link #LOCALE}. * - * A possible return value of {@link SplitType#getLayoutDirection()}. + * <p>A possible return value of {@link SplitType#getLayoutDirection()}. */ - // ------------- - // | | - // | Secondary | - // | | - // ------------- - // | | - // | Primary | - // | | - // ------------- + // ------------- + // | | + // | Secondary | + // | | + // ------------- + // | | + // | Primary | + // | | + // ------------- public static final int BOTTOM_TO_TOP = 5; private LayoutDirection() {} @@ -379,31 +346,40 @@ @Retention(RetentionPolicy.SOURCE) @interface ExtLayoutDirection {} - @ExtLayoutDirection - private final int mLayoutDirection; + @ExtLayoutDirection private final int mLayoutDirection; - private final SplitType mSplitType; + private final @NonNull SplitType mSplitType; - @ColorInt - private final int mAnimationBackgroundColor; + private final @NonNull AnimationParams mAnimationParams; + + private final @NonNull WindowAttributes mWindowAttributes; + + /** The attributes of a divider. If {@code null}, no divider is requested. */ + private final @Nullable DividerAttributes mDividerAttributes; /** * Creates an instance of this {@code SplitAttributes}. * - * @param splitType The type of split. See - * {@link SplitAttributes.SplitType}. - * @param layoutDirection The layout direction of the split, such as left to - * right or top to bottom. See {@link SplitAttributes.LayoutDirection}. - * @param animationBackgroundColor The {@link ColorInt} to use for the - * background color during animation of the split involving this - * {@code SplitAttributes} object if the animation requires a - * background. + * @param splitType The type of split. See {@link SplitAttributes.SplitType}. + * @param layoutDirection The layout direction of the split, such as left to right or top to + * bottom. See {@link SplitAttributes.LayoutDirection}. + * @param animationParams The {@link AnimationParams} to use for the during animation of the + * split involving this {@code SplitAttributes} object. + * @param attributes The {@link WindowAttributes} of the split, such as dim area behavior. + * @param dividerAttributes The {@link DividerAttributes}. If {@code null}, no divider is + * requested. */ - SplitAttributes(@NonNull SplitType splitType, @ExtLayoutDirection int layoutDirection, - @ColorInt int animationBackgroundColor) { + SplitAttributes( + @NonNull SplitType splitType, + @ExtLayoutDirection int layoutDirection, + @NonNull AnimationParams animationParams, + @NonNull WindowAttributes attributes, + @Nullable DividerAttributes dividerAttributes) { mSplitType = splitType; mLayoutDirection = layoutDirection; - mAnimationBackgroundColor = animationBackgroundColor; + mAnimationParams = animationParams; + mWindowAttributes = attributes; + mDividerAttributes = dividerAttributes; } /** @@ -421,53 +397,90 @@ * * @return The split type. */ - @NonNull - public SplitType getSplitType() { + public @NonNull SplitType getSplitType() { return mSplitType; } /** - * Gets the {@link ColorInt} to use for the background color during the - * animation of the split involving this {@code SplitAttributes} object. - * - * The default is {@link #DEFAULT_ANIMATION_BACKGROUND_COLOR}, which means - * to use the current theme window background color. - * - * @return The animation background {@code ColorInt}. + * @deprecated Use {@link #getAnimationParams()} starting with vendor API level 7. Only used if + * {@link #getAnimationParams()} can't be called on vendor API level 5 and 6. */ - @ColorInt - @RestrictTo(LIBRARY) - public int getAnimationBackgroundColor() { - return mAnimationBackgroundColor; + @RequiresVendorApiLevel(level = 5, deprecatedSince = 7) + @Deprecated + @SuppressWarnings("Deprecation") + public @NonNull AnimationBackground getAnimationBackground() { + return mAnimationParams.getAnimationBackground(); + } + + /** + * Returns the {@link AnimationParams} to use during the animation of the split involving this + * {@code SplitAttributes} object. + */ + @RequiresVendorApiLevel(level = 7) + public @NonNull AnimationParams getAnimationParams() { + return mAnimationParams; + } + + /** + * Returns the {@link WindowAttributes} which contains the configurations of the embedded + * Activity windows in this SplitAttributes. + */ + @RequiresVendorApiLevel(level = 5) + public @NonNull WindowAttributes getWindowAttributes() { + return mWindowAttributes; + } + + /** Returns the {@link DividerAttributes}. If {@code null}, no divider is requested. */ + @RequiresVendorApiLevel(level = 6) + public @Nullable DividerAttributes getDividerAttributes() { + return mDividerAttributes; } /** * Builder for creating an instance of {@link SplitAttributes}. * - * - The default split type is an equal split between primary and secondary containers. - * - The default layout direction is based on locale. - * - The default animation background color is to use the current theme window background color. + * <p>- The default split type is an equal split between primary and secondary containers. - The + * default layout direction is based on locale. - The default animation background is to use the + * current theme window background color. */ public static final class Builder { - @NonNull - private SplitType mSplitType = new SplitType.RatioSplitType(0.5f); - @ExtLayoutDirection - private int mLayoutDirection = LOCALE; + private @NonNull SplitType mSplitType = new SplitType.RatioSplitType(0.5f); + @ExtLayoutDirection private int mLayoutDirection = LOCALE; - @ColorInt - private int mAnimationBackgroundColor = 0; + private @NonNull AnimationParams mAnimationParams = new AnimationParams.Builder().build(); + + private @NonNull WindowAttributes mWindowAttributes = + new WindowAttributes(DIM_AREA_ON_TASK); + + private @Nullable DividerAttributes mDividerAttributes; + + /** Creates a new {@link Builder} to create {@link SplitAttributes}. */ + public Builder() {} + + /** + * Creates a {@link Builder} with values cloned from the original {@link SplitAttributes}. + * + * @param original the original {@link SplitAttributes} to initialize the {@link Builder}. + */ + @RequiresVendorApiLevel(level = 6) + public Builder(@NonNull SplitAttributes original) { + mSplitType = original.mSplitType; + mLayoutDirection = original.mLayoutDirection; + mAnimationParams = original.mAnimationParams; + mWindowAttributes = original.mWindowAttributes; + mDividerAttributes = original.mDividerAttributes; + } /** * Sets the split type attribute. * - * The default is an equal split between primary and secondary - * containers (see {@link SplitType.RatioSplitType#splitEqually()}). + * <p>The default is an equal split between primary and secondary containers (see {@link + * SplitType.RatioSplitType#splitEqually()}). * * @param splitType The split type attribute. * @return This {@code Builder}. */ - @NonNull - public Builder setSplitType(@NonNull SplitType splitType) { + public @NonNull Builder setSplitType(@NonNull SplitType splitType) { mSplitType = splitType; return this; } @@ -475,101 +488,133 @@ /** * Sets the split layout direction attribute. * - * The default is based on locale. + * <p>The default is based on locale. * - * Must be one of: + * <p>Must be one of: + * * <ul> - * <li>{@link LayoutDirection#LOCALE}</li> - * <li>{@link LayoutDirection#LEFT_TO_RIGHT}</li> - * <li>{@link LayoutDirection#RIGHT_TO_LEFT}</li> - * <li>{@link LayoutDirection#TOP_TO_BOTTOM}</li> - * <li>{@link LayoutDirection#BOTTOM_TO_TOP}</li> + * <li>{@link LayoutDirection#LOCALE} + * <li>{@link LayoutDirection#LEFT_TO_RIGHT} + * <li>{@link LayoutDirection#RIGHT_TO_LEFT} + * <li>{@link LayoutDirection#TOP_TO_BOTTOM} + * <li>{@link LayoutDirection#BOTTOM_TO_TOP} * </ul> * * @param layoutDirection The layout direction attribute. * @return This {@code Builder}. */ @SuppressLint("WrongConstant") // To be compat with android.util.LayoutDirection APIs - @NonNull - public Builder setLayoutDirection(@ExtLayoutDirection int layoutDirection) { + public @NonNull Builder setLayoutDirection(@ExtLayoutDirection int layoutDirection) { mLayoutDirection = layoutDirection; return this; } /** - * Sets the {@link ColorInt} to use for the background during the - * animation of the split involving this {@code SplitAttributes} object - * if the animation requires a background. - * - * Only opaque color is supported. - * - * The default value is {@link #DEFAULT_ANIMATION_BACKGROUND_COLOR}, which - * means to use the current theme window background color. Any non-opaque - * animation color will be treated as - * {@link #DEFAULT_ANIMATION_BACKGROUND_COLOR}. - * - * @param color A packed color int of the form {@code AARRGGBB} for the - * animation background color. - * @return This {@code Builder}. + * @deprecated Use {@link #setAnimationParams(AnimationParams)} starting with vendor API + * level 7. Only used if {@link #setAnimationParams(AnimationParams)} can't be called on + * vendor API level 5 and 6. */ - @NonNull - @RestrictTo(LIBRARY) - public Builder setAnimationBackgroundColor(@ColorInt int color) { - // Any non-opaque color will be treated as the default. - mAnimationBackgroundColor = Color.alpha(color) != 255 - ? DEFAULT_ANIMATION_BACKGROUND_COLOR - : color; + @RequiresVendorApiLevel(level = 5, deprecatedSince = 7) + @Deprecated + @SuppressWarnings("Deprecation") + public @NonNull Builder setAnimationBackground(@NonNull AnimationBackground background) { + mAnimationParams = + new AnimationParams.Builder().setAnimationBackground(background).build(); return this; } /** - * Builds a {@link SplitAttributes} instance with the attributes - * specified by {@link #setSplitType}, {@link #setLayoutDirection}, and - * {@link #setAnimationBackgroundColor}. + * Sets the {@link AnimationParams} to use during the animation of the split involving this + * {@code SplitAttributes} object. + * + * @param params The {@link AnimationParams} to be used for the animation of the split. + * @return This {@code Builder}. + */ + @RequiresVendorApiLevel(level = 7) + public @NonNull Builder setAnimationParams(@NonNull AnimationParams params) { + mAnimationParams = params; + return this; + } + + /** + * Sets the window attributes. If this value is not specified, the {@link + * WindowAttributes#getDimAreaBehavior()} will be only applied on the {@link ActivityStack} + * of the requested activity. + * + * @param attributes The {@link WindowAttributes} + * @return This {@code Builder}. + */ + @RequiresVendorApiLevel(level = 5) + public @NonNull Builder setWindowAttributes(@NonNull WindowAttributes attributes) { + mWindowAttributes = attributes; + return this; + } + + /** Sets the {@link DividerAttributes}. If {@code null}, no divider is requested. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Builder setDividerAttributes( + @Nullable DividerAttributes dividerAttributes) { + mDividerAttributes = dividerAttributes; + return this; + } + + /** + * Builds a {@link SplitAttributes} instance with the attributes specified by {@link + * #setSplitType}, {@link #setLayoutDirection}, and {@link #setAnimationParams}. * * @return The new {@code SplitAttributes} instance. */ - @NonNull - public SplitAttributes build() { - return new SplitAttributes(mSplitType, mLayoutDirection, mAnimationBackgroundColor); + public @NonNull SplitAttributes build() { + return new SplitAttributes( + mSplitType, + mLayoutDirection, + mAnimationParams, + mWindowAttributes, + mDividerAttributes); } } @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SplitAttributes)) return false; + SplitAttributes that = (SplitAttributes) o; + return mLayoutDirection == that.mLayoutDirection + && mSplitType.equals(that.mSplitType) + && Objects.equals(mAnimationParams, that.mAnimationParams) + && mWindowAttributes.equals(that.mWindowAttributes) + && Objects.equals(mDividerAttributes, that.mDividerAttributes); + } + + @Override public int hashCode() { - int result = mSplitType.hashCode(); - result = result * 31 + mLayoutDirection; - result = result * 31 + mAnimationBackgroundColor; - return result; + return Objects.hash( + mLayoutDirection, + mSplitType, + mAnimationParams, + mWindowAttributes, + mDividerAttributes); } @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof SplitAttributes)) { - return false; - } - final SplitAttributes otherAttributes = (SplitAttributes) other; - return mLayoutDirection == otherAttributes.mLayoutDirection - && mSplitType.equals(otherAttributes.mSplitType) - && mAnimationBackgroundColor == otherAttributes.mAnimationBackgroundColor; - } - - @NonNull - @Override - public String toString() { - return SplitAttributes.class.getSimpleName() + "{" - + "layoutDir=" + layoutDirectionToString() - + ", ratio=" + mSplitType - + ", animationBgColor=" + Integer.toHexString(mAnimationBackgroundColor) + public @NonNull String toString() { + return SplitAttributes.class.getSimpleName() + + "{" + + "layoutDir=" + + layoutDirectionToString() + + ", splitType=" + + mSplitType + + ", animationParams=" + + mAnimationParams + + ", windowAttributes=" + + mWindowAttributes + + ", dividerAttributes=" + + mDividerAttributes + "}"; } - @NonNull - private String layoutDirectionToString() { - switch(mLayoutDirection) { + private @NonNull String layoutDirectionToString() { + switch (mLayoutDirection) { case LEFT_TO_RIGHT: return "LEFT_TO_RIGHT"; case RIGHT_TO_LEFT:
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributesCalculatorParams.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributesCalculatorParams.java index 80f98fe..ae214d8 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributesCalculatorParams.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributesCalculatorParams.java
@@ -17,87 +17,73 @@ package androidx.window.extensions.embedding; import android.content.res.Configuration; -import android.os.Build; import android.view.WindowMetrics; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; +import androidx.window.extensions.RequiresVendorApiLevel; import androidx.window.extensions.layout.WindowLayoutInfo; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + /** - * The parameter container used to report the current device and window state in - * {@link ActivityEmbeddingComponent#setSplitAttributesCalculator( - * androidx.window.extensions.core.util.function.Function)} and references the corresponding - * {@link SplitRule} by {@link #getSplitRuleTag()} if {@link SplitRule#getTag()} is specified. + * The parameter container used to report the current device and window state in {@link + * ActivityEmbeddingComponent#setSplitAttributesCalculator( + * androidx.window.extensions.core.util.function.Function)} and references the corresponding {@link + * SplitRule} by {@link #getSplitRuleTag()} if {@link SplitRule#getTag()} is specified. * * @see ActivityEmbeddingComponent#clearSplitAttributesCalculator() - * Since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2} */ +@RequiresVendorApiLevel(level = 2) public class SplitAttributesCalculatorParams { - @NonNull - private final WindowMetrics mParentWindowMetrics; - @NonNull - private final Configuration mParentConfiguration; - @NonNull - private final WindowLayoutInfo mParentWindowLayoutInfo; - @NonNull - private final SplitAttributes mDefaultSplitAttributes; + private final @NonNull WindowMetrics mParentWindowMetrics; + private final @NonNull Configuration mParentConfiguration; + private final @NonNull WindowLayoutInfo mParentWindowLayoutInfo; + private final @NonNull SplitAttributes mDefaultSplitAttributes; private final boolean mAreDefaultConstraintsSatisfied; - @Nullable - private final String mSplitRuleTag; + private final @Nullable String mSplitRuleTag; /** Returns the parent container's {@link WindowMetrics} */ - @NonNull - public WindowMetrics getParentWindowMetrics() { + public @NonNull WindowMetrics getParentWindowMetrics() { return mParentWindowMetrics; } /** Returns the parent container's {@link Configuration} */ - @NonNull - public Configuration getParentConfiguration() { + public @NonNull Configuration getParentConfiguration() { return new Configuration(mParentConfiguration); } /** - * Returns the {@link SplitRule#getDefaultSplitAttributes()}. It could be from - * {@link SplitRule} Builder APIs - * ({@link SplitPairRule.Builder#setDefaultSplitAttributes(SplitAttributes)} or - * {@link SplitPlaceholderRule.Builder#setDefaultSplitAttributes(SplitAttributes)}) or from - * the {@code splitRatio} and {@code splitLayoutDirection} attributes from static rule - * definitions. + * Returns the {@link SplitRule#getDefaultSplitAttributes()}. It could be from {@link SplitRule} + * Builder APIs ({@link SplitPairRule.Builder#setDefaultSplitAttributes(SplitAttributes)} or + * {@link SplitPlaceholderRule.Builder#setDefaultSplitAttributes(SplitAttributes)}) or from the + * {@code splitRatio} and {@code splitLayoutDirection} attributes from static rule definitions. */ - @NonNull - public SplitAttributes getDefaultSplitAttributes() { + public @NonNull SplitAttributes getDefaultSplitAttributes() { return mDefaultSplitAttributes; } /** * Returns whether the {@link #getParentWindowMetrics()} satisfies the dimensions and aspect - * ratios requirements specified in the {@link androidx.window.embedding.SplitRule}, which - * are: - * - {@link androidx.window.embedding.SplitRule#minWidthDp} - * - {@link androidx.window.embedding.SplitRule#minHeightDp} - * - {@link androidx.window.embedding.SplitRule#minSmallestWidthDp} - * - {@link androidx.window.embedding.SplitRule#maxAspectRatioInPortrait} - * - {@link androidx.window.embedding.SplitRule#maxAspectRatioInLandscape} + * ratios requirements specified in the {@link androidx.window.embedding.SplitRule}, which are: + * - {@link androidx.window.embedding.SplitRule#minWidthDp} - {@link + * androidx.window.embedding.SplitRule#minHeightDp} - {@link + * androidx.window.embedding.SplitRule#minSmallestWidthDp} - {@link + * androidx.window.embedding.SplitRule#maxAspectRatioInPortrait} - {@link + * androidx.window.embedding.SplitRule#maxAspectRatioInLandscape} */ public boolean areDefaultConstraintsSatisfied() { return mAreDefaultConstraintsSatisfied; } /** Returns the parent container's {@link WindowLayoutInfo} */ - @NonNull - public WindowLayoutInfo getParentWindowLayoutInfo() { + public @NonNull WindowLayoutInfo getParentWindowLayoutInfo() { return mParentWindowLayoutInfo; } /** - * Returns {@link SplitRule#getTag()} to apply the {@link SplitAttributes} result if it was - * set. + * Returns {@link SplitRule#getTag()} to apply the {@link SplitAttributes} result if it was set. */ - @Nullable - public String getSplitRuleTag() { + public @Nullable String getSplitRuleTag() { return mSplitRuleTag; } @@ -107,8 +93,7 @@ @NonNull WindowLayoutInfo parentWindowLayoutInfo, @NonNull SplitAttributes defaultSplitAttributes, boolean areDefaultConstraintsSatisfied, - @Nullable String splitRuleTag - ) { + @Nullable String splitRuleTag) { mParentWindowMetrics = parentWindowMetrics; mParentConfiguration = parentConfiguration; mParentWindowLayoutInfo = parentWindowLayoutInfo; @@ -117,33 +102,22 @@ mSplitRuleTag = splitRuleTag; } - @NonNull @Override - public String toString() { - return getClass().getSimpleName() + ":{" - + "windowMetrics=" + windowMetricsToString(mParentWindowMetrics) - + ", configuration=" + mParentConfiguration - + ", windowLayoutInfo=" + mParentWindowLayoutInfo - + ", defaultSplitAttributes=" + mDefaultSplitAttributes - + ", areDefaultConstraintsSatisfied=" + mAreDefaultConstraintsSatisfied - + ", tag=" + mSplitRuleTag + "}"; - } - - private static String windowMetricsToString(@NonNull WindowMetrics windowMetrics) { - // TODO(b/187712731): Use WindowMetrics#toString after it's implemented in U. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - return Api30Impl.windowMetricsToString(windowMetrics); - } - throw new UnsupportedOperationException("WindowMetrics didn't exist in R."); - } - - @RequiresApi(30) - private static final class Api30Impl { - static String windowMetricsToString(@NonNull WindowMetrics windowMetrics) { - return WindowMetrics.class.getSimpleName() + ":{" - + "bounds=" + windowMetrics.getBounds() - + ", windowInsets=" + windowMetrics.getWindowInsets() - + "}"; - } + public @NonNull String toString() { + return getClass().getSimpleName() + + ":{" + + "windowMetrics=" + + WindowMetricsCompat.toString(mParentWindowMetrics) + + ", configuration=" + + mParentConfiguration + + ", windowLayoutInfo=" + + mParentWindowLayoutInfo + + ", defaultSplitAttributes=" + + mDefaultSplitAttributes + + ", areDefaultConstraintsSatisfied=" + + mAreDefaultConstraintsSatisfied + + ", tag=" + + mSplitRuleTag + + "}"; } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitInfo.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitInfo.java index bac42a4..c603fe9 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitInfo.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitInfo.java
@@ -16,30 +16,23 @@ package androidx.window.extensions.embedding; -import android.os.Binder; import android.os.IBinder; -import androidx.annotation.NonNull; -import androidx.window.extensions.WindowExtensions; +import androidx.window.extensions.RequiresVendorApiLevel; import androidx.window.extensions.embedding.SplitAttributes.SplitType; +import org.jspecify.annotations.NonNull; + import java.util.Objects; /** Describes a split of two containers with activities. */ public class SplitInfo { - /** Only used for compatibility with the deprecated constructor. */ - private static final IBinder INVALID_SPLIT_INFO_TOKEN = new Binder(); + private final @NonNull ActivityStack mPrimaryActivityStack; + private final @NonNull ActivityStack mSecondaryActivityStack; + private final @NonNull SplitAttributes mSplitAttributes; - @NonNull - private final ActivityStack mPrimaryActivityStack; - @NonNull - private final ActivityStack mSecondaryActivityStack; - @NonNull - private final SplitAttributes mSplitAttributes; - - @NonNull - private final IBinder mToken; + private final @NonNull Token mToken; /** * The {@code SplitInfo} constructor @@ -48,12 +41,12 @@ * @param secondaryActivityStack The secondary {@link ActivityStack} * @param splitAttributes The current {@link SplitAttributes} of this split pair * @param token The token to identify this split pair - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} */ - SplitInfo(@NonNull ActivityStack primaryActivityStack, + SplitInfo( + @NonNull ActivityStack primaryActivityStack, @NonNull ActivityStack secondaryActivityStack, @NonNull SplitAttributes splitAttributes, - @NonNull IBinder token) { + @NonNull Token token) { Objects.requireNonNull(primaryActivityStack); Objects.requireNonNull(secondaryActivityStack); Objects.requireNonNull(splitAttributes); @@ -64,33 +57,19 @@ mToken = token; } - /** - * @deprecated Use the {@link WindowExtensions#VENDOR_API_LEVEL_3} version. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_1} - */ - @Deprecated - SplitInfo(@NonNull ActivityStack primaryActivityStack, - @NonNull ActivityStack secondaryActivityStack, - @NonNull SplitAttributes splitAttributes) { - this(primaryActivityStack, secondaryActivityStack, splitAttributes, - INVALID_SPLIT_INFO_TOKEN); - } - - @NonNull - public ActivityStack getPrimaryActivityStack() { + public @NonNull ActivityStack getPrimaryActivityStack() { return mPrimaryActivityStack; } - @NonNull - public ActivityStack getSecondaryActivityStack() { + public @NonNull ActivityStack getSecondaryActivityStack() { return mSecondaryActivityStack; } /** - * @deprecated Use {@link #getSplitAttributes()} starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if {@link #getSplitAttributes()} - * can't be called on {@link WindowExtensions#VENDOR_API_LEVEL_1}. + * @deprecated Use {@link #getSplitAttributes()} starting with vendor API level 2. Only used if + * {@link #getSplitAttributes()} can't be called on vendor API level 1. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated public float getSplitRatio() { final SplitType splitType = mSplitAttributes.getSplitType(); @@ -101,21 +80,24 @@ } } - /** - * Returns the {@link SplitAttributes} of this split. - * Since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2} - */ - @NonNull - public SplitAttributes getSplitAttributes() { + /** Returns the {@link SplitAttributes} of this split. */ + @RequiresVendorApiLevel(level = 2) + public @NonNull SplitAttributes getSplitAttributes() { return mSplitAttributes; } /** - * Returns a token uniquely identifying the container. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} + * @deprecated Use {@link #getSplitInfoToken()} instead. */ - @NonNull - public IBinder getToken() { + @Deprecated + @RequiresVendorApiLevel(level = 3, deprecatedSince = 5) + public @NonNull IBinder getToken() { + return mToken.getRawToken(); + } + + /** Returns a token uniquely identifying the split. */ + @RequiresVendorApiLevel(level = 5) + public @NonNull Token getSplitInfoToken() { return mToken; } @@ -124,9 +106,10 @@ if (this == o) return true; if (!(o instanceof SplitInfo)) return false; SplitInfo that = (SplitInfo) o; - return mSplitAttributes.equals(that.mSplitAttributes) && mPrimaryActivityStack.equals( - that.mPrimaryActivityStack) && mSecondaryActivityStack.equals( - that.mSecondaryActivityStack) && mToken.equals(that.mToken); + return mSplitAttributes.equals(that.mSplitAttributes) + && mPrimaryActivityStack.equals(that.mPrimaryActivityStack) + && mSecondaryActivityStack.equals(that.mSecondaryActivityStack) + && mToken.equals(that.mToken); } @Override @@ -138,14 +121,59 @@ return result; } - @NonNull @Override - public String toString() { + public @NonNull String toString() { return "SplitInfo{" - + "mPrimaryActivityStack=" + mPrimaryActivityStack - + ", mSecondaryActivityStack=" + mSecondaryActivityStack - + ", mSplitAttributes=" + mSplitAttributes - + ", mToken=" + mToken + + "mPrimaryActivityStack=" + + mPrimaryActivityStack + + ", mSecondaryActivityStack=" + + mSecondaryActivityStack + + ", mSplitAttributes=" + + mSplitAttributes + + ", mToken=" + + mToken + '}'; } + + /** A unique identifier to represent the split. */ + public static final class Token { + + private final @NonNull IBinder mToken; + + Token(@NonNull IBinder token) { + mToken = token; + } + + @NonNull IBinder getRawToken() { + return mToken; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Token)) return false; + Token token = (Token) o; + return Objects.equals(mToken, token.mToken); + } + + @Override + public int hashCode() { + return Objects.hash(mToken); + } + + @Override + public @NonNull String toString() { + return "Token{" + "mToken=" + mToken + '}'; + } + + /** + * Creates a split token from binder. + * + * @param token the raw binder used by OEM Extensions implementation. + */ + @RequiresVendorApiLevel(level = 5) + public static @NonNull Token createFromBinder(@NonNull IBinder token) { + return new Token(token); + } + } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPairRule.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPairRule.java index ada0637..ac78064 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPairRule.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPairRule.java
@@ -18,7 +18,6 @@ import static androidx.window.extensions.embedding.SplitAttributes.SplitType.createSplitTypeFromLegacySplitRatio; -import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Build; @@ -26,31 +25,28 @@ import android.view.WindowMetrics; import androidx.annotation.FloatRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; -import androidx.window.extensions.WindowExtensions; +import androidx.window.extensions.RequiresVendorApiLevel; import androidx.window.extensions.core.util.function.Predicate; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + import java.util.Objects; -/** - * Split configuration rules for activity pairs. - */ +/** Split configuration rules for activity pairs. */ public class SplitPairRule extends SplitRule { - @NonNull - private final Predicate<Pair<Activity, Activity>> mActivityPairPredicate; - @NonNull - private final Predicate<Pair<Activity, Intent>> mActivityIntentPredicate; - @SplitFinishBehavior - private final int mFinishPrimaryWithSecondary; - @SplitFinishBehavior - private final int mFinishSecondaryWithPrimary; + private final @NonNull Predicate<Pair<Activity, Activity>> mActivityPairPredicate; + private final @NonNull Predicate<Pair<Activity, Intent>> mActivityIntentPredicate; + @SplitFinishBehavior private final int mFinishPrimaryWithSecondary; + @SplitFinishBehavior private final int mFinishSecondaryWithPrimary; private final boolean mClearTop; - SplitPairRule(@NonNull SplitAttributes defaultSplitAttributes, + SplitPairRule( + @NonNull SplitAttributes defaultSplitAttributes, @SplitFinishBehavior int finishPrimaryWithSecondary, - @SplitFinishBehavior int finishSecondaryWithPrimary, boolean clearTop, + @SplitFinishBehavior int finishSecondaryWithPrimary, + boolean clearTop, @NonNull Predicate<Pair<Activity, Activity>> activityPairPredicate, @NonNull Predicate<Pair<Activity, Intent>> activityIntentPredicate, @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate, @@ -63,13 +59,10 @@ mClearTop = clearTop; } - /** - * Checks if the rule is applicable to the provided activities. - */ - @SuppressLint("ClassVerificationFailure") // Only called by Extensions implementation on device. + /** Checks if the rule is applicable to the provided activities. */ @RequiresApi(api = Build.VERSION_CODES.N) - public boolean matchesActivityPair(@NonNull Activity primaryActivity, - @NonNull Activity secondaryActivity) { + public boolean matchesActivityPair( + @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { return mActivityPairPredicate.test(new Pair<>(primaryActivity, secondaryActivity)); } @@ -77,10 +70,9 @@ * Checks if the rule is applicable to the provided primary activity and secondary activity * intent. */ - @SuppressLint("ClassVerificationFailure") // Only called by Extensions implementation on device. @RequiresApi(api = Build.VERSION_CODES.N) - public boolean matchesActivityIntentPair(@NonNull Activity primaryActivity, - @NonNull Intent secondaryActivityIntent) { + public boolean matchesActivityIntentPair( + @NonNull Activity primaryActivity, @NonNull Intent secondaryActivityIntent) { return mActivityIntentPredicate.test(new Pair<>(primaryActivity, secondaryActivityIntent)); } @@ -103,53 +95,46 @@ } /** - * If there is an existing split with the same primary container, indicates whether the - * existing secondary container and all activities in it should be destroyed. Otherwise the new - * secondary will appear on top. Defaults to "true". + * If there is an existing split with the same primary container, indicates whether the existing + * secondary container and all activities in it should be destroyed. Otherwise the new secondary + * will appear on top. Defaults to "true". */ public boolean shouldClearTop() { return mClearTop; } - /** - * Builder for {@link SplitPairRule}. - */ + /** Builder for {@link SplitPairRule}. */ public static final class Builder { - @NonNull - private final Predicate<Pair<Activity, Activity>> mActivityPairPredicate; - @NonNull - private final Predicate<Pair<Activity, Intent>> mActivityIntentPredicate; - @NonNull - private final Predicate<WindowMetrics> mParentWindowMetricsPredicate; + private final @NonNull Predicate<Pair<Activity, Activity>> mActivityPairPredicate; + private final @NonNull Predicate<Pair<Activity, Intent>> mActivityIntentPredicate; + private final @NonNull Predicate<WindowMetrics> mParentWindowMetricsPredicate; + // Keep for backward compatibility @FloatRange(from = 0.0, to = 1.0) private float mSplitRatio; + // Keep for backward compatibility - @SplitAttributes.ExtLayoutDirection - private int mLayoutDirection; + @SplitAttributes.ExtLayoutDirection private int mLayoutDirection; private SplitAttributes mDefaultSplitAttributes; private boolean mClearTop; - @SplitFinishBehavior - private int mFinishPrimaryWithSecondary; - @SplitFinishBehavior - private int mFinishSecondaryWithPrimary; - @Nullable - private String mTag; + @SplitFinishBehavior private int mFinishPrimaryWithSecondary; + @SplitFinishBehavior private int mFinishSecondaryWithPrimary; + private @Nullable String mTag; /** - * @deprecated Use {@link #Builder(Predicate, Predicate, Predicate)} starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #Builder(Predicate, Predicate, Predicate)} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. + * @deprecated Use {@link #Builder(Predicate, Predicate, Predicate)} starting with vendor + * API level 2. Only used if {@link #Builder(Predicate, Predicate, Predicate)} can 't be + * called on vendor API level 1. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated @RequiresApi(Build.VERSION_CODES.N) - public Builder(@NonNull java.util.function.Predicate<Pair<Activity, Activity>> + public Builder( + java.util.function.@NonNull Predicate<Pair<Activity, Activity>> activityPairPredicate, - @NonNull java.util.function.Predicate<Pair<Activity, Intent>> + java.util.function.@NonNull Predicate<Pair<Activity, Intent>> activityIntentPredicate, - @NonNull java.util.function.Predicate<WindowMetrics> - parentWindowMetricsPredicate) { + java.util.function.@NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate) { mActivityPairPredicate = activityPairPredicate::test; mActivityIntentPredicate = activityIntentPredicate::test; mParentWindowMetricsPredicate = parentWindowMetricsPredicate::test; @@ -159,15 +144,16 @@ * The {@link SplitPairRule} builder constructor * * @param activityPairPredicate the {@link Predicate} to verify if an {@link Activity} pair - * matches this rule + * matches this rule * @param activityIntentPredicate the {@link Predicate} to verify if an ({@link Activity}, - * {@link Intent}) pair matches this rule + * {@link Intent}) pair matches this rule * @param parentWindowMetricsPredicate the {@link Predicate} to verify if the matched split - * pair is allowed to show adjacent to each other with the - * given parent {@link WindowMetrics} - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * pair is allowed to show adjacent to each other with the given parent {@link + * WindowMetrics} */ - public Builder(@NonNull Predicate<Pair<Activity, Activity>> activityPairPredicate, + @RequiresVendorApiLevel(level = 2) + public Builder( + @NonNull Predicate<Pair<Activity, Activity>> activityPairPredicate, @NonNull Predicate<Pair<Activity, Intent>> activityIntentPredicate, @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate) { mActivityPairPredicate = activityPairPredicate; @@ -176,108 +162,116 @@ } /** - * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #setDefaultSplitAttributes(SplitAttributes)} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. {@code splitRatio} will be translated to - * {@link SplitAttributes.SplitType.ExpandContainersSplitType} for value {@code 0.0} and - * {@code 1.0}, and {@link SplitAttributes.SplitType.RatioSplitType} for value with range - * (0.0, 1.0). + * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with vendor + * API level 2. Only used if {@link #setDefaultSplitAttributes(SplitAttributes)} can't + * be called on vendor API level 1. {@code splitRatio} will be translated to {@link + * SplitAttributes.SplitType.ExpandContainersSplitType} for value {@code 0.0} and {@code + * 1.0}, and {@link SplitAttributes.SplitType.RatioSplitType} for value with range (0.0, + * 1.0). */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated - @NonNull - public Builder setSplitRatio(@FloatRange(from = 0.0, to = 1.0) float splitRatio) { + public @NonNull Builder setSplitRatio(@FloatRange(from = 0.0, to = 1.0) float splitRatio) { mSplitRatio = splitRatio; return this; } /** - * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #setDefaultSplitAttributes(SplitAttributes)} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. + * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with vendor + * API level 2. Only used if {@link #setDefaultSplitAttributes(SplitAttributes)} can't + * be called on vendor API level 1. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated - @NonNull - public Builder setLayoutDirection(@SplitAttributes.ExtLayoutDirection int layoutDirection) { + public @NonNull Builder setLayoutDirection( + @SplitAttributes.ExtLayoutDirection int layoutDirection) { mLayoutDirection = layoutDirection; return this; } /** - * See {@link SplitPairRule#getDefaultSplitAttributes()} for reference. - * Overrides values if set in {@link #setSplitRatio(float)} and - * {@link #setLayoutDirection(int)} - * - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * See {@link SplitPairRule#getDefaultSplitAttributes()} for reference. Overrides values if + * set in {@link #setSplitRatio(float)} and {@link #setLayoutDirection(int)} */ - @NonNull - public Builder setDefaultSplitAttributes(@NonNull SplitAttributes attrs) { + @RequiresVendorApiLevel(level = 2) + public @NonNull Builder setDefaultSplitAttributes(@NonNull SplitAttributes attrs) { mDefaultSplitAttributes = attrs; return this; } - /** @deprecated To be removed with next developer preview. */ + /** + * @deprecated To be removed with next developer preview. + */ @Deprecated - @NonNull - public Builder setShouldFinishPrimaryWithSecondary( + public @NonNull Builder setShouldFinishPrimaryWithSecondary( boolean finishPrimaryWithSecondary) { return this; } - /** @deprecated To be removed with next developer preview. */ + /** + * @deprecated To be removed with next developer preview. + */ @Deprecated - @NonNull - public Builder setShouldFinishSecondaryWithPrimary(boolean finishSecondaryWithPrimary) { + public @NonNull Builder setShouldFinishSecondaryWithPrimary( + boolean finishSecondaryWithPrimary) { return this; } - /** @see SplitPairRule#getFinishPrimaryWithSecondary() */ - @NonNull - public Builder setFinishPrimaryWithSecondary(@SplitFinishBehavior int finishBehavior) { + /** + * @see SplitPairRule#getFinishPrimaryWithSecondary() + */ + public @NonNull Builder setFinishPrimaryWithSecondary( + @SplitFinishBehavior int finishBehavior) { mFinishPrimaryWithSecondary = finishBehavior; return this; } - /** @see SplitPairRule#getFinishSecondaryWithPrimary() */ - @NonNull - public Builder setFinishSecondaryWithPrimary(@SplitFinishBehavior int finishBehavior) { + /** + * @see SplitPairRule#getFinishSecondaryWithPrimary() + */ + public @NonNull Builder setFinishSecondaryWithPrimary( + @SplitFinishBehavior int finishBehavior) { mFinishSecondaryWithPrimary = finishBehavior; return this; } - /** @see SplitPairRule#shouldClearTop() */ - @NonNull - public Builder setShouldClearTop(boolean shouldClearTop) { + /** + * @see SplitPairRule#shouldClearTop() + */ + public @NonNull Builder setShouldClearTop(boolean shouldClearTop) { mClearTop = shouldClearTop; return this; } /** * @see SplitPairRule#getTag() - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} */ - @NonNull - public Builder setTag(@NonNull String tag) { + @RequiresVendorApiLevel(level = 2) + public @NonNull Builder setTag(@NonNull String tag) { mTag = Objects.requireNonNull(tag); return this; } /** Builds a new instance of {@link SplitPairRule}. */ - @NonNull - public SplitPairRule build() { + public @NonNull SplitPairRule build() { // To provide compatibility with prior version of WM Jetpack library, where // #setDefaultAttributes hasn't yet been supported and thus would not be set. - mDefaultSplitAttributes = (mDefaultSplitAttributes != null) - ? mDefaultSplitAttributes - : new SplitAttributes.Builder() - .setSplitType(createSplitTypeFromLegacySplitRatio(mSplitRatio)) - .setLayoutDirection(mLayoutDirection) - .build(); - return new SplitPairRule(mDefaultSplitAttributes, - mFinishPrimaryWithSecondary, mFinishSecondaryWithPrimary, - mClearTop, mActivityPairPredicate, mActivityIntentPredicate, - mParentWindowMetricsPredicate, mTag); + mDefaultSplitAttributes = + (mDefaultSplitAttributes != null) + ? mDefaultSplitAttributes + : new SplitAttributes.Builder() + .setSplitType(createSplitTypeFromLegacySplitRatio(mSplitRatio)) + .setLayoutDirection(mLayoutDirection) + .build(); + return new SplitPairRule( + mDefaultSplitAttributes, + mFinishPrimaryWithSecondary, + mFinishSecondaryWithPrimary, + mClearTop, + mActivityPairPredicate, + mActivityIntentPredicate, + mParentWindowMetricsPredicate, + mTag); } } @@ -305,15 +299,19 @@ return result; } - @NonNull @Override - public String toString() { + public @NonNull String toString() { return "SplitPairRule{" - + "mTag=" + getTag() - + ", mDefaultSplitAttributes=" + getDefaultSplitAttributes() - + ", mFinishPrimaryWithSecondary=" + mFinishPrimaryWithSecondary - + ", mFinishSecondaryWithPrimary=" + mFinishSecondaryWithPrimary - + ", mClearTop=" + mClearTop + + "mTag=" + + getTag() + + ", mDefaultSplitAttributes=" + + getDefaultSplitAttributes() + + ", mFinishPrimaryWithSecondary=" + + mFinishPrimaryWithSecondary + + ", mFinishSecondaryWithPrimary=" + + mFinishSecondaryWithPrimary + + ", mClearTop=" + + mClearTop + '}'; } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPinRule.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPinRule.java new file mode 100644 index 0000000..63f254b --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPinRule.java
@@ -0,0 +1,139 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import android.view.WindowMetrics; + +import androidx.window.extensions.RequiresVendorApiLevel; +import androidx.window.extensions.core.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.util.Objects; + +/** + * Split configuration rules for keeping an {@link ActivityStack} in the split in a pin state to + * provide an isolated Activity navigation from the split. A pin state here is referring the {@link + * ActivityStack} to be fixed on top. + * + * @see ActivityEmbeddingComponent#pinTopActivityStack + */ +@RequiresVendorApiLevel(level = 5) +public class SplitPinRule extends SplitRule { + /** + * Whether the rule should be applied whenever the parent Task satisfied the parent window + * metrics predicate. See {@link ActivityEmbeddingComponent#pinTopActivityStack}. + */ + private final boolean mIsSticky; + + SplitPinRule( + @NonNull SplitAttributes defaultSplitAttributes, + @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate, + boolean isSticky, + @Nullable String tag) { + super(parentWindowMetricsPredicate, defaultSplitAttributes, tag); + mIsSticky = isSticky; + } + + /** + * Whether the rule is sticky. This configuration rule can only be applied once when possible. + * That is, the rule will be abandoned whenever the pinned {@link ActivityStack} no longer able + * to be split with another {@link ActivityStack} once the configuration of the parent Task is + * changed. Sets the rule to be sticky if the rule should be permanent until the {@link + * ActivityStack} explicitly unpin. + * + * @see ActivityEmbeddingComponent#pinTopActivityStack + */ + public boolean isSticky() { + return mIsSticky; + } + + /** Builder for {@link SplitPinRule}. */ + public static final class Builder { + private final @NonNull SplitAttributes mDefaultSplitAttributes; + private final @NonNull Predicate<WindowMetrics> mParentWindowMetricsPredicate; + private boolean mIsSticky; + private @Nullable String mTag; + + /** + * The {@link SplitPinRule} builder constructor. + * + * @param defaultSplitAttributes the default {@link SplitAttributes} to apply + * @param parentWindowMetricsPredicate the {@link Predicate} to verify if the pinned {@link + * ActivityStack} and the one behind are allowed to show adjacent to each other with the + * given parent {@link WindowMetrics} + */ + public Builder( + @NonNull SplitAttributes defaultSplitAttributes, + @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate) { + mDefaultSplitAttributes = defaultSplitAttributes; + mParentWindowMetricsPredicate = parentWindowMetricsPredicate; + } + + /** + * Sets the rule to be sticky. + * + * @see SplitPinRule#isSticky() + */ + public @NonNull Builder setSticky(boolean isSticky) { + mIsSticky = isSticky; + return this; + } + + /** + * @see SplitPinRule#getTag() + */ + public @NonNull Builder setTag(@NonNull String tag) { + mTag = Objects.requireNonNull(tag); + return this; + } + + /** Builds a new instance of {@link SplitPinRule}. */ + public @NonNull SplitPinRule build() { + return new SplitPinRule( + mDefaultSplitAttributes, mParentWindowMetricsPredicate, mIsSticky, mTag); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SplitPinRule)) return false; + SplitPinRule that = (SplitPinRule) o; + return super.equals(o) && mIsSticky == that.mIsSticky; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (mIsSticky ? 1 : 0); + return result; + } + + @Override + public @NonNull String toString() { + return "SplitPinRule{" + + "mTag=" + + getTag() + + ", mDefaultSplitAttributes=" + + getDefaultSplitAttributes() + + ", mIsSticky=" + + mIsSticky + + '}'; + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPlaceholderRule.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPlaceholderRule.java index 38b5451..ddb9cb2 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPlaceholderRule.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPlaceholderRule.java
@@ -18,7 +18,6 @@ import static androidx.window.extensions.embedding.SplitAttributes.SplitType.createSplitTypeFromLegacySplitRatio; -import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Build; @@ -26,44 +25,39 @@ import androidx.annotation.FloatRange; import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; -import androidx.window.extensions.WindowExtensions; +import androidx.window.extensions.RequiresVendorApiLevel; import androidx.window.extensions.core.util.function.Predicate; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** - * Split configuration rules for split placeholders - activities used to occupy additional - * available space on the side before the user selects content to show. + * Split configuration rules for split placeholders - activities used to occupy additional available + * space on the side before the user selects content to show. */ public class SplitPlaceholderRule extends SplitRule { - @NonNull - private final Predicate<Activity> mActivityPredicate; - @NonNull - private final Predicate<Intent> mIntentPredicate; - @NonNull - private final Intent mPlaceholderIntent; + private final @NonNull Predicate<Activity> mActivityPredicate; + private final @NonNull Predicate<Intent> mIntentPredicate; + private final @NonNull Intent mPlaceholderIntent; private final boolean mIsSticky; /** - * Determines what happens with the primary container when the placeholder activity is - * finished in one of the containers in a split. + * Determines what happens with the primary container when the placeholder activity is finished + * in one of the containers in a split. */ - @IntDef({ - FINISH_ALWAYS, - FINISH_ADJACENT - }) + @IntDef({FINISH_ALWAYS, FINISH_ADJACENT}) @Retention(RetentionPolicy.SOURCE) - @interface SplitPlaceholderFinishBehavior{} + @interface SplitPlaceholderFinishBehavior {} - @SplitPlaceholderFinishBehavior - private final int mFinishPrimaryWithPlaceholder; + @SplitPlaceholderFinishBehavior private final int mFinishPrimaryWithPlaceholder; - SplitPlaceholderRule(@NonNull Intent placeholderIntent, + SplitPlaceholderRule( + @NonNull Intent placeholderIntent, @NonNull SplitAttributes defaultSplitAttributes, boolean isSticky, @SplitPlaceholderFinishBehavior int finishPrimaryWithPlaceholder, @@ -79,19 +73,13 @@ mPlaceholderIntent = placeholderIntent; } - /** - * Checks if the rule is applicable to the provided activity. - */ - @SuppressLint("ClassVerificationFailure") // Only called by Extensions implementation on device. + /** Checks if the rule is applicable to the provided activity. */ @RequiresApi(api = Build.VERSION_CODES.N) public boolean matchesActivity(@NonNull Activity activity) { return mActivityPredicate.test(activity); } - /** - * Checks if the rule is applicable to the provided activity intent. - */ - @SuppressLint("ClassVerificationFailure") // Only called by Extensions implementation on device. + /** Checks if the rule is applicable to the provided activity intent. */ @RequiresApi(api = Build.VERSION_CODES.N) public boolean matchesIntent(@NonNull Intent intent) { return mIntentPredicate.test(intent); @@ -100,8 +88,7 @@ /** * An {@link Intent} used by Extensions Sidecar to launch the placeholder when the space allows. */ - @NonNull - public Intent getPlaceholderIntent() { + public @NonNull Intent getPlaceholderIntent() { return mPlaceholderIntent; } @@ -114,11 +101,11 @@ } /** - * @deprecated Use {@link #getFinishPrimaryWithPlaceholder()} instead starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #getFinishPrimaryWithPlaceholder()} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. + * @deprecated Use {@link #getFinishPrimaryWithPlaceholder()} instead starting with vendor API + * level 2. Only used if {@link #getFinishPrimaryWithPlaceholder()} can't be called on + * vendor API level 1. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated @SplitPlaceholderFinishBehavior public int getFinishPrimaryWithSecondary() { @@ -128,52 +115,44 @@ /** * Determines what happens with the primary container when all activities are finished in the * associated secondary/placeholder container. - * - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} */ - // TODO(b/238905747): Add api guard for extensions. + @RequiresVendorApiLevel(level = 2) @SplitPlaceholderFinishBehavior public int getFinishPrimaryWithPlaceholder() { return mFinishPrimaryWithPlaceholder; } - /** - * Builder for {@link SplitPlaceholderRule}. - */ + /** Builder for {@link SplitPlaceholderRule}. */ public static final class Builder { - @NonNull - private final Predicate<Activity> mActivityPredicate; - @NonNull - private final Predicate<Intent> mIntentPredicate; - @NonNull - private final Predicate<WindowMetrics> mParentWindowMetricsPredicate; - @NonNull - private final Intent mPlaceholderIntent; + private final @NonNull Predicate<Activity> mActivityPredicate; + private final @NonNull Predicate<Intent> mIntentPredicate; + private final @NonNull Predicate<WindowMetrics> mParentWindowMetricsPredicate; + private final @NonNull Intent mPlaceholderIntent; + // Keep for backward compatibility @FloatRange(from = 0.0, to = 1.0) private float mSplitRatio; + // Keep for backward compatibility - @SplitAttributes.ExtLayoutDirection - private int mLayoutDirection; + @SplitAttributes.ExtLayoutDirection private int mLayoutDirection; private SplitAttributes mDefaultSplitAttributes; private boolean mIsSticky = false; - @SplitPlaceholderFinishBehavior - private int mFinishPrimaryWithPlaceholder = FINISH_ALWAYS; - @Nullable - private String mTag; + @SplitPlaceholderFinishBehavior private int mFinishPrimaryWithPlaceholder = FINISH_ALWAYS; + private @Nullable String mTag; /** * @deprecated Use {@link #Builder(Intent, Predicate, Predicate, Predicate)} starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #Builder(Intent, Predicate, Predicate, Predicate)} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. + * vendor API level 2. Only used if {@link #Builder(Intent, Predicate, Predicate, + * Predicate)} can't be called on vendor API level 1. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated @RequiresApi(Build.VERSION_CODES.N) - public Builder(@NonNull Intent placeholderIntent, - @NonNull java.util.function.Predicate<Activity> activityPredicate, - @NonNull java.util.function.Predicate<Intent> intentPredicate, - @NonNull java.util.function.Predicate<WindowMetrics> parentWindowMetricsPredicate) { + public Builder( + @NonNull Intent placeholderIntent, + java.util.function.@NonNull Predicate<Activity> activityPredicate, + java.util.function.@NonNull Predicate<Intent> intentPredicate, + java.util.function.@NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate) { mActivityPredicate = activityPredicate::test; mIntentPredicate = intentPredicate::test; mPlaceholderIntent = placeholderIntent; @@ -182,19 +161,19 @@ /** * The {@link SplitPlaceholderRule} Builder constructor - * @param placeholderIntent the placeholder activity to launch if - * {@link SplitPlaceholderRule#checkParentMetrics(WindowMetrics)} - * is satisfied + * + * @param placeholderIntent the placeholder activity to launch if {@link + * SplitPlaceholderRule#checkParentMetrics(WindowMetrics)} is satisfied * @param activityPredicate the {@link Predicate} to verify if a given {@link Activity} - * matches the rule - * @param intentPredicate the {@link Predicate} to verify if a given {@link Intent} - * matches the rule + * matches the rule + * @param intentPredicate the {@link Predicate} to verify if a given {@link Intent} matches + * the rule * @param parentWindowMetricsPredicate the {@link Predicate} to verify if the placeholder - * {@link Activity} should be launched with the given - * {@link WindowMetrics} - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * {@link Activity} should be launched with the given {@link WindowMetrics} */ - public Builder(@NonNull Intent placeholderIntent, + @RequiresVendorApiLevel(level = 2) + public Builder( + @NonNull Intent placeholderIntent, @NonNull Predicate<Activity> activityPredicate, @NonNull Predicate<Intent> intentPredicate, @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate) { @@ -205,61 +184,58 @@ } /** - * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #setDefaultSplitAttributes(SplitAttributes)} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. {@code splitRatio} will be translated to - * @link SplitAttributes.SplitType.ExpandContainersSplitType} for value - * {@code 0.0} and {@code 1.0}, and {@link SplitAttributes.SplitType.RatioSplitType} for - * value with range (0.0, 1.0). + * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with vendor + * API level 2. Only used if {@link #setDefaultSplitAttributes(SplitAttributes)} can't + * be called on vendor API level 1. {@code splitRatio} will be translated to + * @link SplitAttributes.SplitType.ExpandContainersSplitType} for value {@code 0.0} and + * {@code 1.0}, and {@link SplitAttributes.SplitType.RatioSplitType} for value with + * range (0.0, 1.0). */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated - @NonNull - public Builder setSplitRatio(@FloatRange(from = 0.0, to = 1.0) float splitRatio) { + public @NonNull Builder setSplitRatio(@FloatRange(from = 0.0, to = 1.0) float splitRatio) { mSplitRatio = splitRatio; return this; } /** - * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #setDefaultSplitAttributes(SplitAttributes)} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. + * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with vendor + * API level 2. Only used if {@link #setDefaultSplitAttributes(SplitAttributes)} can't + * be called on vendor API level 1. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated - @NonNull - public Builder setLayoutDirection(@SplitAttributes.ExtLayoutDirection int layoutDirection) { + public @NonNull Builder setLayoutDirection( + @SplitAttributes.ExtLayoutDirection int layoutDirection) { mLayoutDirection = layoutDirection; return this; } /** - * See {@link SplitPlaceholderRule#getDefaultSplitAttributes()} for reference. - * Overrides values if set in {@link #setSplitRatio(float)} and - * {@link #setLayoutDirection(int)} - * - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * See {@link SplitPlaceholderRule#getDefaultSplitAttributes()} for reference. Overrides + * values if set in {@link #setSplitRatio(float)} and {@link #setLayoutDirection(int)} */ - @NonNull - public Builder setDefaultSplitAttributes(@NonNull SplitAttributes attrs) { + @RequiresVendorApiLevel(level = 2) + public @NonNull Builder setDefaultSplitAttributes(@NonNull SplitAttributes attrs) { mDefaultSplitAttributes = attrs; return this; } - /** @see SplitPlaceholderRule#isSticky() */ - @NonNull - public Builder setSticky(boolean sticky) { + /** + * @see SplitPlaceholderRule#isSticky() + */ + public @NonNull Builder setSticky(boolean sticky) { mIsSticky = sticky; return this; } /** * @deprecated Use SplitPlaceholderRule#setFinishPrimaryWithPlaceholder(int)} starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. + * vendor API level 2. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated - @NonNull - public Builder setFinishPrimaryWithSecondary( + public @NonNull Builder setFinishPrimaryWithSecondary( @SplitPlaceholderFinishBehavior int finishBehavior) { if (finishBehavior == FINISH_NEVER) { finishBehavior = FINISH_ALWAYS; @@ -269,11 +245,9 @@ /** * @see SplitPlaceholderRule#getFinishPrimaryWithPlaceholder() - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} */ - // TODO(b/238905747): Add api guard for extensions. - @NonNull - public Builder setFinishPrimaryWithPlaceholder( + @RequiresVendorApiLevel(level = 2) + public @NonNull Builder setFinishPrimaryWithPlaceholder( @SplitPlaceholderFinishBehavior int finishBehavior) { mFinishPrimaryWithPlaceholder = finishBehavior; return this; @@ -281,28 +255,33 @@ /** * @see SplitPlaceholderRule#getTag() - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} */ - @NonNull - public Builder setTag(@NonNull String tag) { + @RequiresVendorApiLevel(level = 2) + public @NonNull Builder setTag(@NonNull String tag) { mTag = Objects.requireNonNull(tag); return this; } /** Builds a new instance of {@link SplitPlaceholderRule}. */ - @NonNull - public SplitPlaceholderRule build() { + public @NonNull SplitPlaceholderRule build() { // To provide compatibility with prior version of WM Jetpack library, where // #setDefaultAttributes hasn't yet been supported and thus would not be set. - mDefaultSplitAttributes = (mDefaultSplitAttributes != null) - ? mDefaultSplitAttributes - : new SplitAttributes.Builder() - .setSplitType(createSplitTypeFromLegacySplitRatio(mSplitRatio)) - .setLayoutDirection(mLayoutDirection) - .build(); - return new SplitPlaceholderRule(mPlaceholderIntent, mDefaultSplitAttributes, mIsSticky, - mFinishPrimaryWithPlaceholder, mActivityPredicate, - mIntentPredicate, mParentWindowMetricsPredicate, mTag); + mDefaultSplitAttributes = + (mDefaultSplitAttributes != null) + ? mDefaultSplitAttributes + : new SplitAttributes.Builder() + .setSplitType(createSplitTypeFromLegacySplitRatio(mSplitRatio)) + .setLayoutDirection(mLayoutDirection) + .build(); + return new SplitPlaceholderRule( + mPlaceholderIntent, + mDefaultSplitAttributes, + mIsSticky, + mFinishPrimaryWithPlaceholder, + mActivityPredicate, + mIntentPredicate, + mParentWindowMetricsPredicate, + mTag); } } @@ -332,15 +311,19 @@ return result; } - @NonNull @Override - public String toString() { + public @NonNull String toString() { return "SplitPlaceholderRule{" - + "mTag=" + getTag() - + ", mDefaultSplitAttributes=" + getDefaultSplitAttributes() - + ", mActivityPredicate=" + mActivityPredicate - + ", mIsSticky=" + mIsSticky - + ", mFinishPrimaryWithPlaceholder=" + mFinishPrimaryWithPlaceholder + + "mTag=" + + getTag() + + ", mDefaultSplitAttributes=" + + getDefaultSplitAttributes() + + ", mActivityPredicate=" + + mActivityPredicate + + ", mIsSticky=" + + mIsSticky + + ", mFinishPrimaryWithPlaceholder=" + + mFinishPrimaryWithPlaceholder + '}'; } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitRule.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitRule.java index dd02b14..a072359 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitRule.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitRule.java
@@ -16,76 +16,76 @@ package androidx.window.extensions.embedding; -import android.annotation.SuppressLint; import android.os.Build; import android.view.WindowMetrics; import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; -import androidx.window.extensions.WindowExtensions; +import androidx.window.extensions.RequiresVendorApiLevel; import androidx.window.extensions.core.util.function.Predicate; import androidx.window.extensions.embedding.SplitAttributes.SplitType; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** * Split configuration rules for activities that are launched to side in a split. Define when an - * activity that was launched in a side container from another activity should be shown - * adjacent or on top of it, as well as the visual properties of the split. Can be applied to - * new activities started from the same process automatically by the embedding implementation on - * the device. + * activity that was launched in a side container from another activity should be shown adjacent or + * on top of it, as well as the visual properties of the split. Can be applied to new activities + * started from the same process automatically by the embedding implementation on the device. */ public abstract class SplitRule extends EmbeddingRule { - @NonNull - private final Predicate<WindowMetrics> mParentWindowMetricsPredicate; + private final @NonNull Predicate<WindowMetrics> mParentWindowMetricsPredicate; - @NonNull - private final SplitAttributes mDefaultSplitAttributes; + private final @NonNull SplitAttributes mDefaultSplitAttributes; /** * Never finish the associated container. + * * @see SplitFinishBehavior */ public static final int FINISH_NEVER = 0; + /** * Always finish the associated container independent of the current presentation mode. + * * @see SplitFinishBehavior */ public static final int FINISH_ALWAYS = 1; + /** * Only finish the associated container when displayed adjacent to the one being finished. Does * not finish the associated one when containers are stacked on top of each other. + * * @see SplitFinishBehavior */ public static final int FINISH_ADJACENT = 2; /** - * Determines what happens with the associated container when all activities are finished in - * one of the containers in a split. - * <p> - * For example, given that {@link SplitPairRule#getFinishPrimaryWithSecondary()} is - * {@link #FINISH_ADJACENT} and secondary container finishes. The primary associated - * container is finished if it's shown adjacent to the secondary container. The primary - * associated container is not finished if it occupies entire task bounds.</p> + * Determines what happens with the associated container when all activities are finished in one + * of the containers in a split. + * + * <p>For example, given that {@link SplitPairRule#getFinishPrimaryWithSecondary()} is {@link + * #FINISH_ADJACENT} and secondary container finishes. The primary associated container is + * finished if it's shown adjacent to the secondary container. The primary associated container + * is not finished if it occupies entire task bounds. * * @see SplitPairRule#getFinishPrimaryWithSecondary() * @see SplitPairRule#getFinishSecondaryWithPrimary() * @see SplitPlaceholderRule#getFinishPrimaryWithSecondary() */ - @IntDef({ - FINISH_NEVER, - FINISH_ALWAYS, - FINISH_ADJACENT - }) + @IntDef({FINISH_NEVER, FINISH_ALWAYS, FINISH_ADJACENT}) @Retention(RetentionPolicy.SOURCE) @interface SplitFinishBehavior {} - SplitRule(@NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate, - @NonNull SplitAttributes defaultSplitAttributes, @Nullable String tag) { + SplitRule( + @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate, + @NonNull SplitAttributes defaultSplitAttributes, + @Nullable String tag) { super(tag); mParentWindowMetricsPredicate = parentWindowMetricsPredicate; mDefaultSplitAttributes = defaultSplitAttributes; @@ -93,28 +93,27 @@ /** * Checks whether the parent window satisfied the dimensions and aspect ratios requirements - * specified in the {@link androidx.window.embedding.SplitRule}, which are - * {@link androidx.window.embedding.SplitRule#minWidthDp}, - * {@link androidx.window.embedding.SplitRule#minHeightDp}, - * {@link androidx.window.embedding.SplitRule#minSmallestWidthDp}, - * {@link androidx.window.embedding.SplitRule#maxAspectRatioInPortrait} and - * {@link androidx.window.embedding.SplitRule#maxAspectRatioInLandscape}. + * specified in the {@link androidx.window.embedding.SplitRule}, which are {@link + * androidx.window.embedding.SplitRule#minWidthDp}, {@link + * androidx.window.embedding.SplitRule#minHeightDp}, {@link + * androidx.window.embedding.SplitRule#minSmallestWidthDp}, {@link + * androidx.window.embedding.SplitRule#maxAspectRatioInPortrait} and {@link + * androidx.window.embedding.SplitRule#maxAspectRatioInLandscape}. * * @param parentMetrics the {@link WindowMetrics} of the parent window. * @return whether the parent window satisfied the {@link SplitRule} requirements. */ - @SuppressLint("ClassVerificationFailure") // Only called by Extensions implementation on device. @RequiresApi(api = Build.VERSION_CODES.N) public boolean checkParentMetrics(@NonNull WindowMetrics parentMetrics) { return mParentWindowMetricsPredicate.test(parentMetrics); } /** - * @deprecated Use {@link #getDefaultSplitAttributes()} instead starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #getDefaultSplitAttributes()} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. + * @deprecated Use {@link #getDefaultSplitAttributes()} instead starting with vendor API level + * 2. Only used if {@link #getDefaultSplitAttributes()} can't be called on vendor API level + * 1. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated public float getSplitRatio() { final SplitType splitType = mDefaultSplitAttributes.getSplitType(); @@ -126,11 +125,11 @@ } /** - * @deprecated Use {@link #getDefaultSplitAttributes()} instead starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #getDefaultSplitAttributes()} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. + * @deprecated Use {@link #getDefaultSplitAttributes()} instead starting with vendor API level + * 2. Only used if {@link #getDefaultSplitAttributes()} can't be called on vendor API level + * 1. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated @SplitAttributes.ExtLayoutDirection public int getLayoutDirection() { @@ -138,13 +137,11 @@ } /** - * Returns the default {@link SplitAttributes} which is applied if - * {@link #checkParentMetrics(WindowMetrics)} is {@code true}. - * - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + * Returns the default {@link SplitAttributes} which is applied if {@link + * #checkParentMetrics(WindowMetrics)} is {@code true}. */ - @NonNull - public SplitAttributes getDefaultSplitAttributes() { + @RequiresVendorApiLevel(level = 2) + public @NonNull SplitAttributes getDefaultSplitAttributes() { return mDefaultSplitAttributes; } @@ -166,12 +163,13 @@ return result; } - @NonNull @Override - public String toString() { + public @NonNull String toString() { return "SplitRule{" - + "mTag=" + getTag() - + ", mDefaultSplitAttributes=" + mDefaultSplitAttributes + + "mTag=" + + getTag() + + ", mDefaultSplitAttributes=" + + mDefaultSplitAttributes + '}'; } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/WindowAttributes.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/WindowAttributes.java new file mode 100644 index 0000000..9275c9a --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/WindowAttributes.java
@@ -0,0 +1,90 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import androidx.annotation.IntDef; +import androidx.window.extensions.RequiresVendorApiLevel; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** The attributes of the embedded Activity Window. */ +public final class WindowAttributes { + + /** + * The dim effect is applying on the {@link ActivityStack} of the Activity window when needed. + * If the {@link ActivityStack} is not expanded to fill the parent container, the dim effect is + * applying only on the {@link ActivityStack} of the requested Activity. + */ + public static final int DIM_AREA_ON_ACTIVITY_STACK = 1; + + /** + * The dim effect is applying on the area of the whole Task when needed. If the embedded + * transparent activity is split and displayed side-by-side with another activity, the dim + * effect is applying on the Task, which across over the two {@link ActivityStack}s. + */ + public static final int DIM_AREA_ON_TASK = 2; + + @IntDef({DIM_AREA_ON_ACTIVITY_STACK, DIM_AREA_ON_TASK}) + @Retention(RetentionPolicy.SOURCE) + @interface DimAreaBehavior {} + + @DimAreaBehavior private final int mDimAreaBehavior; + + /** + * The {@link WindowAttributes} constructor. + * + * @param dimAreaBehavior the type of area that the dim layer is applying. + */ + @RequiresVendorApiLevel(level = 5) + public WindowAttributes(@DimAreaBehavior int dimAreaBehavior) { + mDimAreaBehavior = dimAreaBehavior; + } + + /** + * Returns the {@link DimAreaBehavior} to use when dim behind the Activity window is needed. + * + * @return The dim area behavior. + */ + @DimAreaBehavior + @RequiresVendorApiLevel(level = 5) + public int getDimAreaBehavior() { + return mDimAreaBehavior; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + if (obj == null || !(obj instanceof WindowAttributes)) return false; + final WindowAttributes other = (WindowAttributes) obj; + return mDimAreaBehavior == other.getDimAreaBehavior(); + } + + @Override + public int hashCode() { + return Objects.hash(mDimAreaBehavior); + } + + @Override + public @NonNull String toString() { + return "dimAreaBehavior=" + mDimAreaBehavior; + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/WindowMetricsCompat.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/WindowMetricsCompat.java new file mode 100644 index 0000000..2d84874 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/WindowMetricsCompat.java
@@ -0,0 +1,53 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import android.os.Build; +import android.view.WindowMetrics; + +import androidx.annotation.RequiresApi; + +import org.jspecify.annotations.NonNull; + +/** A helper class to access {@link WindowMetrics#toString()} with compatibility. */ +class WindowMetricsCompat { + private WindowMetricsCompat() {} + + static @NonNull String toString(@NonNull WindowMetrics windowMetrics) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + // WindowMetrics#toString is implemented in U. + return windowMetrics.toString(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return Api30Impl.toString(windowMetrics); + } + // Should be safe since ActivityEmbedding is not enabled before R. + throw new UnsupportedOperationException("WindowMetrics didn't exist in R."); + } + + @RequiresApi(30) + private static final class Api30Impl { + static @NonNull String toString(@NonNull WindowMetrics windowMetrics) { + return WindowMetrics.class.getSimpleName() + + ":{" + + "bounds=" + + windowMetrics.getBounds() + + ", windowInsets=" + + windowMetrics.getWindowInsets() + + "}"; + } + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/DisplayFeature.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/DisplayFeature.java index 32b0fa1..a00639d 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/DisplayFeature.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/DisplayFeature.java
@@ -18,19 +18,16 @@ import android.graphics.Rect; -import androidx.annotation.NonNull; +import org.jspecify.annotations.NonNull; -/** - * Description of a physical feature on the display. - */ +/** Description of a physical feature on the display. */ public interface DisplayFeature { /** - * The bounding rectangle of the feature within the application window - * in the window coordinate space. + * The bounding rectangle of the feature within the application window in the window coordinate + * space. * * @return bounds of display feature. */ - @NonNull - Rect getBounds(); + @NonNull Rect getBounds(); }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/DisplayFoldFeature.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/DisplayFoldFeature.java new file mode 100644 index 0000000..71be524 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/DisplayFoldFeature.java
@@ -0,0 +1,195 @@ +/* + * Copyright 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 androidx.window.extensions.layout; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Build; + +import androidx.annotation.IntDef; +import androidx.annotation.RestrictTo; +import androidx.window.extensions.RequiresVendorApiLevel; +import androidx.window.extensions.core.util.function.Consumer; +import androidx.window.extensions.util.SetUtilApi23; + +import org.jspecify.annotations.NonNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Represents a fold on a display that may intersect a window. The presence of a fold does not imply + * that it intersects the window an {@link android.app.Activity} is running in. For example, on a + * device that can fold like a book and has an outer screen, the fold should be reported regardless + * of the folding state, or which screen is on to indicate that there may be a fold when the user + * opens the device. + * + * @see WindowLayoutComponent#addWindowLayoutInfoListener(Context, Consumer) to listen to features + * that affect the window. + */ +public final class DisplayFoldFeature { + + /** + * The type of fold is unknown. This is here for compatibility reasons if a new type is added, + * and cannot be reported to an incompatible application. + */ + public static final int TYPE_UNKNOWN = 0; + + /** The type of fold is a physical hinge separating two display panels. */ + public static final int TYPE_HINGE = 1; + + /** The type of fold is a screen that folds from 0-180. */ + public static final int TYPE_SCREEN_FOLD_IN = 2; + + @RestrictTo(RestrictTo.Scope.LIBRARY) + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {TYPE_UNKNOWN, TYPE_HINGE, TYPE_SCREEN_FOLD_IN}) + public @interface FoldType {} + + /** The fold supports the half opened state. */ + public static final int FOLD_PROPERTY_SUPPORTS_HALF_OPENED = 1; + + @Target(ElementType.TYPE_USE) + @RestrictTo(RestrictTo.Scope.LIBRARY) + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {FOLD_PROPERTY_SUPPORTS_HALF_OPENED}) + public @interface FoldProperty {} + + @FoldType private final int mType; + + private final Set<@FoldProperty Integer> mProperties; + + /** + * Creates an instance of [FoldDisplayFeature]. + * + * @param type the type of fold, either [FoldDisplayFeature.TYPE_HINGE] or + * [FoldDisplayFeature.TYPE_FOLDABLE_SCREEN] + */ + DisplayFoldFeature(@FoldType int type, @NonNull Set<@FoldProperty Integer> properties) { + mType = type; + if (Build.VERSION.SDK_INT >= 23) { + mProperties = SetUtilApi23.createSet(); + } else { + mProperties = new HashSet<>(); + } + mProperties.addAll(properties); + } + + /** Returns the type of fold that is either a hinge or a fold. */ + @RequiresVendorApiLevel(level = 6) + @FoldType + public int getType() { + return mType; + } + + /** Returns {@code true} if the fold has the given property, {@code false} otherwise. */ + @RequiresVendorApiLevel(level = 6) + public boolean hasProperty(@FoldProperty int property) { + return mProperties.contains(property); + } + + /** Returns {@code true} if the fold has all the given properties, {@code false} otherwise. */ + @RequiresVendorApiLevel(level = 6) + public boolean hasProperties(@FoldProperty int @NonNull ... properties) { + for (int i = 0; i < properties.length; i++) { + if (!mProperties.contains(properties[i])) { + return false; + } + } + return true; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DisplayFoldFeature that = (DisplayFoldFeature) o; + return mType == that.mType && Objects.equals(mProperties, that.mProperties); + } + + @Override + public int hashCode() { + return Objects.hash(mType, mProperties); + } + + @Override + public @NonNull String toString() { + return "ScreenFoldDisplayFeature{mType=" + mType + ", mProperties=" + mProperties + '}'; + } + + /** A builder to construct an instance of {@link DisplayFoldFeature}. */ + public static final class Builder { + + @FoldType private int mType; + + private Set<@FoldProperty Integer> mProperties; + + /** + * Constructs a builder to create an instance of {@link DisplayFoldFeature}. + * + * @param type the type of hinge for the {@link DisplayFoldFeature}. + * @see DisplayFoldFeature.FoldType + */ + @RequiresVendorApiLevel(level = 6) + public Builder(@FoldType int type) { + mType = type; + if (Build.VERSION.SDK_INT >= 23) { + mProperties = SetUtilApi23.createSet(); + } else { + mProperties = new HashSet<>(); + } + } + + /** Add a property to the set of properties exposed by {@link DisplayFoldFeature}. */ + @SuppressLint("MissingGetterMatchingBuilder") + @RequiresVendorApiLevel(level = 6) + public @NonNull Builder addProperty(@FoldProperty int property) { + mProperties.add(property); + return this; + } + + /** + * Add a list of properties to the set of properties exposed by {@link DisplayFoldFeature}. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @RequiresVendorApiLevel(level = 6) + public @NonNull Builder addProperties(@FoldProperty int @NonNull ... properties) { + for (int i = 0; i < properties.length; i++) { + mProperties.add(properties[i]); + } + return this; + } + + /** Clear the properties in the builder. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull Builder clearProperties() { + mProperties.clear(); + return this; + } + + /** Returns an instance of {@link DisplayFoldFeature}. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull DisplayFoldFeature build() { + return new DisplayFoldFeature(mType, mProperties); + } + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/FoldingFeature.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/FoldingFeature.java index 9f95510..ff89f3f 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/FoldingFeature.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/FoldingFeature.java
@@ -19,77 +19,64 @@ import android.graphics.Rect; import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * A feature that describes a fold in a flexible display - * or a hinge between two physical display panels. + * A feature that describes a fold in a flexible display or a hinge between two physical display + * panels. */ public class FoldingFeature implements DisplayFeature { - /** - * A fold in the flexible screen without a physical gap. - */ + /** A fold in the flexible screen without a physical gap. */ public static final int TYPE_FOLD = 1; - /** - * A physical separation with a hinge that allows two display panels to fold. - */ + /** A physical separation with a hinge that allows two display panels to fold. */ public static final int TYPE_HINGE = 2; @Retention(RetentionPolicy.SOURCE) @IntDef({ - TYPE_FOLD, - TYPE_HINGE, + TYPE_FOLD, + TYPE_HINGE, }) - @interface Type{} + @interface Type {} /** * The foldable device's hinge is completely open, the screen space that is presented to the - * user is flat. See the - * <a href="https://developer.android.com/guide/topics/ui/foldables#postures">Posture</a> - * section in the official documentation for visual samples and references. + * user is flat. See the <a + * href="https://developer.android.com/guide/topics/ui/foldables#postures">Posture</a> section + * in the official documentation for visual samples and references. */ public static final int STATE_FLAT = 1; /** * The foldable device's hinge is in an intermediate position between opened and closed state, * there is a non-flat angle between parts of the flexible screen or between physical screen - * panels. See the - * <a href="https://developer.android.com/guide/topics/ui/foldables#postures">Posture</a> - * section in the official documentation for visual samples and references. + * panels. See the <a + * href="https://developer.android.com/guide/topics/ui/foldables#postures">Posture</a> section + * in the official documentation for visual samples and references. */ public static final int STATE_HALF_OPENED = 2; @Retention(RetentionPolicy.SOURCE) - @IntDef({ - STATE_HALF_OPENED, - STATE_FLAT - }) + @IntDef({STATE_HALF_OPENED, STATE_FLAT}) @interface State {} /** - * The bounding rectangle of the feature within the application window in the window - * coordinate space. + * The bounding rectangle of the feature within the application window in the window coordinate + * space. */ - @NonNull - private final Rect mBounds; + private final @NonNull Rect mBounds; - /** - * The physical type of the feature. - */ - @Type - private final int mType; + /** The physical type of the feature. */ + @Type private final int mType; - /** - * The state of the feature. - */ - @State - private final int mState; + /** The state of the feature. */ + @State private final int mState; public FoldingFeature(@NonNull Rect bounds, @Type int type, @State int state) { validateFeatureBounds(bounds); @@ -99,9 +86,8 @@ } /** Gets the bounding rect of the display feature in window coordinate space. */ - @NonNull @Override - public Rect getBounds() { + public @NonNull Rect getBounds() { return new Rect(mBounds); } @@ -117,21 +103,20 @@ return mState; } - /** - * Verifies the bounds of the folding feature. - */ + /** Verifies the bounds of the folding feature. */ private static void validateFeatureBounds(@NonNull Rect bounds) { if (bounds.width() == 0 && bounds.height() == 0) { throw new IllegalArgumentException("Bounds must be non zero. Bounds: " + bounds); } if (bounds.left != 0 && bounds.top != 0) { - throw new IllegalArgumentException("Bounding rectangle must start at the top or " - + "left window edge for folding features. Bounds: " + bounds); + throw new IllegalArgumentException( + "Bounding rectangle must start at the top or " + + "left window edge for folding features. Bounds: " + + bounds); } } - @NonNull - private static String typeToString(int type) { + private static @NonNull String typeToString(int type) { switch (type) { case TYPE_FOLD: return "FOLD"; @@ -142,8 +127,7 @@ } } - @NonNull - private static String stateToString(int state) { + private static @NonNull String stateToString(int state) { switch (state) { case STATE_FLAT: return "FLAT"; @@ -154,11 +138,15 @@ } } - @NonNull @Override - public String toString() { - return "ExtensionDisplayFoldFeature { " + mBounds - + ", type=" + typeToString(getType()) + ", state=" + stateToString(mState) + " }"; + public @NonNull String toString() { + return "ExtensionDisplayFoldFeature { " + + mBounds + + ", type=" + + typeToString(getType()) + + ", state=" + + stateToString(mState) + + " }"; } @Override @@ -179,10 +167,7 @@ return mBounds.equals(other.mBounds); } - /** - * Manually computes the hashCode for the {@link Rect} since it is not implemented - * on API 15 - */ + /** Manually computes the hashCode for the {@link Rect} since it is not implemented on API 15 */ private static int hashBounds(Rect bounds) { int result = bounds.left; result = 31 * result + bounds.top;
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/SupportedWindowFeatures.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/SupportedWindowFeatures.java new file mode 100644 index 0000000..e2c03a6 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/SupportedWindowFeatures.java
@@ -0,0 +1,61 @@ +/* + * Copyright 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 androidx.window.extensions.layout; + +import androidx.window.extensions.RequiresVendorApiLevel; + +import org.jspecify.annotations.NonNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class to represent all the possible features that may interact with or appear in a window, that + * an application might want to respond to. + */ +public final class SupportedWindowFeatures { + + private final List<DisplayFoldFeature> mDisplayFoldFeatureList; + + private SupportedWindowFeatures(@NonNull List<DisplayFoldFeature> displayFoldFeatureList) { + mDisplayFoldFeatureList = new ArrayList<>(displayFoldFeatureList); + } + + /** Returns the possible {@link DisplayFoldFeature}s that can be reported to an application. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull List<DisplayFoldFeature> getDisplayFoldFeatures() { + return new ArrayList<>(mDisplayFoldFeatureList); + } + + /** A class to create a {@link SupportedWindowFeatures} instance. */ + public static final class Builder { + + private final List<DisplayFoldFeature> mDisplayFoldFeatures; + + /** Creates a new instance of {@link Builder} */ + @RequiresVendorApiLevel(level = 6) + public Builder(@NonNull List<DisplayFoldFeature> displayFoldFeatures) { + mDisplayFoldFeatures = new ArrayList<>(displayFoldFeatures); + } + + /** Creates an instance of {@link SupportedWindowFeatures} with the features set. */ + @RequiresVendorApiLevel(level = 6) + public @NonNull SupportedWindowFeatures build() { + return new SupportedWindowFeatures(mDisplayFoldFeatures); + } + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutComponent.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutComponent.java index 0b7c01f..35ae14a 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutComponent.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutComponent.java
@@ -18,81 +18,121 @@ import android.app.Activity; import android.content.Context; +import android.os.Bundle; +import android.view.Display; -import androidx.annotation.NonNull; import androidx.annotation.UiContext; +import androidx.window.extensions.RequiresVendorApiLevel; import androidx.window.extensions.WindowExtensions; import androidx.window.extensions.core.util.function.Consumer; +import org.jspecify.annotations.NonNull; + /** * The interface definition that will be used by the WindowManager library to get custom - * OEM-provided information about the window that isn't covered by platform APIs. Exposes methods - * to listen to changes in the {@link WindowLayoutInfo}. A {@link WindowLayoutInfo} contains a list - * of {@link DisplayFeature}s. - * <p> - * Currently {@link FoldingFeature} is the only {@link DisplayFeature}. A {@link FoldingFeature} - * exposes the state of a hinge and the relative bounds within the window. Developers can - * optimize their UI to support a {@link FoldingFeature} by avoiding it and placing content in - * relevant logical areas. + * OEM-provided information about the window that isn't covered by platform APIs. Exposes methods to + * listen to changes in the {@link WindowLayoutInfo}. A {@link WindowLayoutInfo} contains a list of + * {@link DisplayFeature}s. + * + * <p>Currently {@link FoldingFeature} is the only {@link DisplayFeature}. A {@link FoldingFeature} + * exposes the state of a hinge and the relative bounds within the window. Developers can optimize + * their UI to support a {@link FoldingFeature} by avoiding it and placing content in relevant + * logical areas. * * <p>This interface should be implemented by OEM and deployed to the target devices. + * * @see WindowExtensions#getWindowLayoutComponent() */ public interface WindowLayoutComponent { /** - * @deprecated Use {@link #addWindowLayoutInfoListener(Context, Consumer)} - * starting with {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #addWindowLayoutInfoListener(Context, Consumer)} can't be - * called on {@link WindowExtensions#VENDOR_API_LEVEL_1}. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_1} + * @deprecated Use {@link #addWindowLayoutInfoListener(Context, Consumer)} starting with vendor + * API level 2. Only used if {@link #addWindowLayoutInfoListener(Context, Consumer)} can't + * be called on vendor API level 1. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated - void addWindowLayoutInfoListener(@NonNull Activity activity, - @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer); + void addWindowLayoutInfoListener( + @NonNull Activity activity, + java.util.function.@NonNull Consumer<WindowLayoutInfo> consumer); /** - * @deprecated Use {@link #removeWindowLayoutInfoListener(Consumer)} starting with - * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if - * {@link #removeWindowLayoutInfoListener(Consumer)} can't be called on - * {@link WindowExtensions#VENDOR_API_LEVEL_1}. - * Since {@link WindowExtensions#VENDOR_API_LEVEL_1} + * @deprecated Use {@link #removeWindowLayoutInfoListener(Consumer)} starting with vendor API + * level 2. Only used if {@link #removeWindowLayoutInfoListener(Consumer)} can't be called + * on vendor API level 1. */ + @RequiresVendorApiLevel(level = 1, deprecatedSince = 2) @Deprecated void removeWindowLayoutInfoListener( - @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer); + java.util.function.@NonNull Consumer<WindowLayoutInfo> consumer); /** - * Adds a listener interested in receiving updates to {@link WindowLayoutInfo}. - * Use {@link WindowLayoutComponent#removeWindowLayoutInfoListener} to remove listener. - * <p> - * A {@link Context} or a Consumer instance can only be registered once. - * Registering the same {@link Context} or Consumer more than once will result in - * a noop. + * Adds a listener interested in receiving updates to {@link WindowLayoutInfo}. Use {@link + * WindowLayoutComponent#removeWindowLayoutInfoListener} to remove listener. * - * @param context a {@link UiContext} that corresponds to a window or an area on the - * screen - an {@link Activity}, a {@link Context} created with - * {@link Context#createWindowContext(Display, int , Bundle)}, or - * {@link android.inputmethodservice.InputMethodService}. + * <p>A {@link Context} or a Consumer instance can only be registered once. Registering the same + * {@link Context} or Consumer more than once will result in a noop. + * + * @param context a {@link UiContext} that corresponds to a window or an area on the screen - an + * {@link Activity}, a {@link Context} created with {@link + * Context#createWindowContext(Display, int, Bundle)}, or {@link + * android.inputmethodservice.InputMethodService}. * @param consumer interested in receiving updates to {@link WindowLayoutInfo} - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} */ - // TODO(b/238905747): Add api guard for extensions. + @RequiresVendorApiLevel(level = 2) @SuppressWarnings("PairedRegistration") // The paired method for unregistering is also removeWindowLayoutInfoListener. - default void addWindowLayoutInfoListener(@NonNull @UiContext Context context, - @NonNull Consumer<WindowLayoutInfo> consumer) { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + default void addWindowLayoutInfoListener( + @UiContext @NonNull Context context, @NonNull Consumer<WindowLayoutInfo> consumer) { + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); } /** * Removes a listener no longer interested in receiving updates. * * @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo} - * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} */ + @RequiresVendorApiLevel(level = 2) default void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) { - throw new UnsupportedOperationException("This method must not be called unless there is a" - + " corresponding override implementation on the device."); + throw new UnsupportedOperationException( + "This method must not be called unless there is a" + + " corresponding override implementation on the device."); + } + + /** + * Returns the {@link SupportedWindowFeatures} for the device. This value will not change over + * time. + * + * @see WindowLayoutComponent#addWindowLayoutInfoListener(Context, Consumer) to register a + * listener for features that impact the window. + */ + @RequiresVendorApiLevel(level = 6) + default @NonNull SupportedWindowFeatures getSupportedWindowFeatures() { + throw new UnsupportedOperationException( + "This method will not be called unless there is a" + + " corresponding override implementation on the device"); + } + + /** + * Returns the current {@link WindowLayoutInfo} for the given {@link Context}. + * + * <p>This API provides a convenient way to access the current {@link WindowLayoutInfo} without + * registering a listener via {@link #addWindowLayoutInfoListener(Context, Consumer)}. It + * simplifies the retrieval of {@link WindowLayoutInfo} in scenarios like {@link + * Activity#onCreate(Bundle)}. + * + * @param context a {@link Context} that corresponds to a window or an area on the screen. This + * can be an {@link Activity}, a {@link Context} created with {@link + * Context#createWindowContext(Display, int, Bundle)}, or an {@link + * android.inputmethodservice.InputMethodService}. + * @return the current {@link WindowLayoutInfo} for the given {@link Context}. + */ + @RequiresVendorApiLevel(level = 9) + default @NonNull WindowLayoutInfo getCurrentWindowLayoutInfo( + @UiContext @NonNull Context context) { + throw new UnsupportedOperationException( + "This method will not be called unless there is a" + + " corresponding override implementation on the device"); } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutInfo.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutInfo.java index 9041f0a..6e03764 100644 --- a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutInfo.java +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutInfo.java
@@ -16,40 +16,102 @@ package androidx.window.extensions.layout; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import androidx.annotation.IntDef; +import androidx.annotation.RestrictTo; +import androidx.window.extensions.RequiresVendorApiLevel; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; +import java.util.Objects; -/** - * Contains information about the layout of display features within the window. - */ +/** Contains information about the layout of display features within the window. */ public class WindowLayoutInfo { /** + * A flag indicating the engagement mode includes a visual presentation. When this flag is set, + * it means the user can visually see the app UI on visible window. + */ + public static final int ENGAGEMENT_MODE_FLAG_VISUALS_ON = 1 << 0; + + /** + * A flag indicating the engagement mode includes an audio presentation. This can be set with or + * without {@link #ENGAGEMENT_MODE_FLAG_VISUALS_ON}. When set without, it signifies an + * audio-only experience. + */ + public static final int ENGAGEMENT_MODE_FLAG_AUDIO_ON = 1 << 1; + + /** Annotation for the engagement mode flags. */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Retention(RetentionPolicy.SOURCE) + @IntDef( + flag = true, + value = {ENGAGEMENT_MODE_FLAG_VISUALS_ON, ENGAGEMENT_MODE_FLAG_AUDIO_ON}) + public @interface EngagementModeFlags {} + + private static final int DEFAULT_ENGAGEMENT_MODE = + ENGAGEMENT_MODE_FLAG_VISUALS_ON | ENGAGEMENT_MODE_FLAG_AUDIO_ON; + + /** * List of display features within the window. + * * <p>NOTE: All display features returned with this container must be cropped to the application * window and reported within the coordinate space of the window that was provided by the app. */ - @NonNull - private List<DisplayFeature> mDisplayFeatures; + private final @NonNull List<DisplayFeature> mDisplayFeatures; - public WindowLayoutInfo(@NonNull List<DisplayFeature> displayFeatures) { - mDisplayFeatures = Collections.unmodifiableList(displayFeatures); - } + /** The user engagement mode flags for this window. */ + private final @EngagementModeFlags int mEngagementModeFlags; /** - * Gets the list of display features present within the window. + * @deprecated Use the {@link Builder} instead. */ - @NonNull - public List<DisplayFeature> getDisplayFeatures() { + @RequiresVendorApiLevel(level = 1, deprecatedSince = 10) + @Deprecated + public WindowLayoutInfo(@NonNull List<DisplayFeature> displayFeatures) { + this(displayFeatures, DEFAULT_ENGAGEMENT_MODE); + } + + private WindowLayoutInfo( + @NonNull List<DisplayFeature> displayFeatures, + @EngagementModeFlags int engagementModeFlags) { + mDisplayFeatures = Collections.unmodifiableList(displayFeatures); + mEngagementModeFlags = engagementModeFlags; + } + + /** Gets the list of display features present within the window. */ + public @NonNull List<DisplayFeature> getDisplayFeatures() { return mDisplayFeatures; } - @NonNull + /** + * Returns the current user engagement mode flags for this window. + * + * @return The current {@link EngagementModeFlags}. + */ + @RequiresVendorApiLevel(level = 10) + @EngagementModeFlags + public int getEngagementModeFlags() { + return mEngagementModeFlags; + } + + /** + * Checks if a specific flag is present in the engagement mode. + * + * @param flag The specific {@link EngagementModeFlags} flag to check for. + * @return {@code true} if the flag is set, {@code false} otherwise. + */ + @RequiresVendorApiLevel(level = 10) + public boolean hasEngagementModeFlag(@EngagementModeFlags int flag) { + return (mEngagementModeFlags & flag) == flag; + } + @Override - public String toString() { + public @NonNull String toString() { StringBuilder sb = new StringBuilder(); sb.append("ExtensionWindowLayoutInfo { ExtensionDisplayFeatures[ "); for (int i = 0; i < mDisplayFeatures.size(); i = i + 1) { @@ -58,7 +120,7 @@ sb.append(", "); } } - sb.append(" ] }"); + sb.append(" ], ExtensionEngagementModeFlags=").append(mEngagementModeFlags).append(" }"); return sb.toString(); } @@ -70,16 +132,55 @@ if (!(obj instanceof WindowLayoutInfo)) { return false; } - final WindowLayoutInfo - other = (WindowLayoutInfo) obj; - if (mDisplayFeatures == null) { - return other.mDisplayFeatures == null; - } - return mDisplayFeatures.equals(other.mDisplayFeatures); + final WindowLayoutInfo other = (WindowLayoutInfo) obj; + return mDisplayFeatures.equals(other.mDisplayFeatures) + && mEngagementModeFlags == other.mEngagementModeFlags; } @Override public int hashCode() { - return mDisplayFeatures != null ? mDisplayFeatures.size() : 0; + return Objects.hash(mDisplayFeatures, mEngagementModeFlags); + } + + /** Builder for {@link WindowLayoutInfo}. */ + public static final class Builder { + private @NonNull List<DisplayFeature> mDisplayFeatures = Collections.emptyList(); + private @EngagementModeFlags int mEngagementModeFlags = DEFAULT_ENGAGEMENT_MODE; + + /** Creates a new instance of the {@link Builder}. */ + public Builder() {} + + /** + * Sets the list of {@link DisplayFeature} present within the window. + * + * @param displayFeatures the list of {@link DisplayFeature} to set. + * @return this {@link Builder} instance. + */ + public @NonNull Builder setDisplayFeatures(@NonNull List<DisplayFeature> displayFeatures) { + mDisplayFeatures = displayFeatures; + return this; + } + + /** + * Sets the current user engagement mode flags for this window. + * + * @param flags the {@link EngagementModeFlags} to set. + * @return this {@link Builder} instance. + */ + @RequiresVendorApiLevel(level = 10) + public @NonNull Builder setEngagementModeFlags(@EngagementModeFlags int flags) { + mEngagementModeFlags = flags; + return this; + } + + /** + * Builds a new {@link WindowLayoutInfo} instance. + * + * @return a new {@link WindowLayoutInfo} instance. + */ + @RequiresVendorApiLevel(level = 10) + public @NonNull WindowLayoutInfo build() { + return new WindowLayoutInfo(mDisplayFeatures, mEngagementModeFlags); + } } }
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/util/SetCompat.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/util/SetCompat.java new file mode 100644 index 0000000..2893727 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/util/SetCompat.java
@@ -0,0 +1,59 @@ +/* + * Copyright 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 androidx.window.extensions.util; + +import android.os.Build; +import android.util.ArraySet; + +import androidx.annotation.RequiresApi; +import androidx.annotation.RestrictTo; + +import org.jspecify.annotations.NonNull; + +import java.util.HashSet; +import java.util.Set; + +/** + * A {@link Set} wrapper for compatibility. It {@link ArraySet} if it's available, and uses other + * compatible {@link Set} class, otherwise. + */ +@RestrictTo(RestrictTo.Scope.LIBRARY) +public final class SetCompat { + + private SetCompat() {} + + /** + * Creates a {@link Set}. + * + * @param <T> the type of the {@link Set}. + */ + public static <T> @NonNull Set<T> create() { + if (Build.VERSION.SDK_INT < 23) { + return new HashSet<>(); + } else { + return Api23Impl.create(); + } + } + + @RequiresApi(23) + private static class Api23Impl { + + static <T> @NonNull Set<T> create() { + return new ArraySet<>(); + } + } +}
diff --git a/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/util/SetUtilApi23.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/util/SetUtilApi23.java new file mode 100644 index 0000000..4c7f3ef --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/util/SetUtilApi23.java
@@ -0,0 +1,39 @@ +/* + * Copyright 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 androidx.window.extensions.util; + +import android.util.ArraySet; + +import androidx.annotation.RequiresApi; +import androidx.annotation.RestrictTo; + +import org.jspecify.annotations.NonNull; + +import java.util.Set; + +/** Set utilities for creating working with newer {@link ArraySet} apis. */ +@RestrictTo(RestrictTo.Scope.LIBRARY) +@RequiresApi(23) +public final class SetUtilApi23 { + + private SetUtilApi23() {} + + /** Creates an instance of {@link ArraySet}. */ + public static <T> @NonNull Set<T> createSet() { + return new ArraySet<>(); + } +}
diff --git a/window_extensions/sources.txt b/window_extensions/sources.txt index 2ccdd8c..1d1ddf1 100644 --- a/window_extensions/sources.txt +++ b/window_extensions/sources.txt
@@ -5,18 +5,34 @@ window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaStatus.java window/extensions/extensions/src/main/java/androidx/window/extensions/area/WindowAreaComponent.java window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingComponent.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingOptionsProperties.java window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityRule.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStackAttributesCalculatorParams.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStackAttributes.java window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStack.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/AnimationBackground.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/AnimationParams.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/DividerAttributes.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddedActivityWindowInfo.java window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddingRule.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ParentContainerInfo.java window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributesCalculatorParams.java window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributes.java window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitInfo.java window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPairRule.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPinRule.java window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPlaceholderRule.java window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitRule.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/WindowAttributes.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/WindowMetricsCompat.java window/extensions/extensions/src/main/java/androidx/window/extensions/layout/DisplayFeature.java +window/extensions/extensions/src/main/java/androidx/window/extensions/layout/DisplayFoldFeature.java window/extensions/extensions/src/main/java/androidx/window/extensions/layout/FoldingFeature.java +window/extensions/extensions/src/main/java/androidx/window/extensions/layout/SupportedWindowFeatures.java window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutComponent.java window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutInfo.java +window/extensions/extensions/src/main/java/androidx/window/extensions/RequiresVendorApiLevel.java +window/extensions/extensions/src/main/java/androidx/window/extensions/util/SetCompat.java +window/extensions/extensions/src/main/java/androidx/window/extensions/util/SetUtilApi23.java window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensionsProvider.java