Android: Make androidx.window.extensions library available This does not yet add it to chrome. Bug: 1415351 Change-Id: Ie4075e9e0f9ffc7d89fe60950c01b45b69552f46 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5148729 Reviewed-by: Mohamed Heikal <mheikal@chromium.org> Commit-Queue: Andrew Grieve <agrieve@chromium.org> Cr-Commit-Position: refs/heads/main@{#1242020} NOKEYCHECK=True GitOrigin-RevId: 70a1f51e438fd782bedac6fd76193270638678f4
diff --git a/BUILD.gn b/BUILD.gn index 970675e..7b3ff66 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -25,11 +25,19 @@ variables = [ "library_name=${invoker.library_name}" ] } java_group(target_name) { + forward_variables_from(invoker, [ "jar_deps" ]) deps = [ ":$_manifest_target" ] + if (defined(invoker.deps)) { + deps += invoker.deps + } # Makes the .jar appear in the classpath for compile steps. - input_jars_paths = - [ "${android_sdk}/optional/${invoker.library_name}.jar" ] + if (defined(invoker.input_jars_paths)) { + input_jars_paths = invoker.input_jars_paths + } else { + input_jars_paths = + [ "${android_sdk}/optional/${invoker.library_name}.jar" ] + } # Adds the <uses-library> tag to the manifest. mergeable_android_manifests = [ _manifest_path ] @@ -51,4 +59,12 @@ android_sdk_optional_library("android_car_java") { library_name = "android.car" } + android_sdk_optional_library("android_window_extensions_java") { + library_name = "androidx.window.extensions" + jar_deps = [ + "//third_party/android_sdk/window_extensions:androidx_window_extensions", + ] + deps = [ "//third_party/androidx:androidx_annotation_annotation_jvm_java" ] + input_jars_paths = [ "$root_build_dir/obj/third_party/android_sdk/window_extensions/androidx_window_extensions_java.javac.jar" ] + } }
diff --git a/window_extensions/BUILD.gn b/window_extensions/BUILD.gn new file mode 100644 index 0000000..ba3c865 --- /dev/null +++ b/window_extensions/BUILD.gn
@@ -0,0 +1,25 @@ +# Copyright 2023 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/android/rules.gni") + +# Contains placeholder files that define the API provided by the system library. +# The method bodies are likely different on actual devices. +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", + ] +} + +# Use an intermediate target that omits the "_java" suffix in order to avoid +# the classes being linked into APKs (our build system links in targets based +# on naming patterns). +group("androidx_window_extensions") { + visibility = ["//third_party/android_sdk:*"] + deps = [ ":androidx_window_extensions_java" ] +}
diff --git a/window_extensions/README.md b/window_extensions/README.md new file mode 100644 index 0000000..9e176be --- /dev/null +++ b/window_extensions/README.md
@@ -0,0 +1,24 @@ +# androidx.window.extensions + +This library is not available as a prebuilt like other optional system +libraries in order to encourage clients to access it via the `androidx.window` +wrapper library. However, Chrome uses it directly in order to reduce the binary +size overhead of the wrapper library. Googlers, see: [b/233785784]. + +As with other optional SDK libraries, these classes are not compiled in. They +are available at runtime through a system library loaded by the framework +when a `<uses-library>` tag exists in an app's `AndroidManifest.xml` (which is +added via manifest merging when depending on the GN target). + +Not all devices have this optional library available. You can check if it's +available via `org.chromium.window.WindowUtil#isAvailable()`. + +Library documentation: https://source.android.com/docs/core/display/windowmanager-extensions + +Library source code: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:window/extensions/ + +[b/233785784]: http://b/233785784 + +## Updating the Sources + +Run `update_sources.sh`
diff --git a/window_extensions/java/window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Consumer.java b/window_extensions/java/window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Consumer.java new file mode 100644 index 0000000..8fee672 --- /dev/null +++ b/window_extensions/java/window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Consumer.java
@@ -0,0 +1,36 @@ +/* + * 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.core.util.function; + +/** + * Represents a function that accepts an argument and produces no result. + * It is used internally to avoid using Java 8 functional interface that leads to desugaring and + * Proguard shrinking. + * + * @param <T>: the type of the input of the function + * + * @see java.util.function.Consumer + */ +@FunctionalInterface +public interface Consumer<T> { + /** + * Performs the operation on the given argument + * + * @param t the input argument + */ + void accept(T t); +}
diff --git a/window_extensions/java/window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Function.java b/window_extensions/java/window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Function.java new file mode 100644 index 0000000..4bc7bcb --- /dev/null +++ b/window_extensions/java/window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Function.java
@@ -0,0 +1,38 @@ +/* + * 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.core.util.function; + +/** + * Represents a function that accepts an argument and produces a result. + * It is used internally to avoid using Java 8 functional interface that leads to desugaring and + * Proguard shrinking. + * + * @param <T>: the type of the input of the function + * @param <R>: the type of the output of the function + * + * @see java.util.function.Function + */ +@FunctionalInterface +public interface Function<T, R> { + /** + * Applies this function to the given argument. + * + * @param t the function argument + * @return the function result + */ + R apply(T t); +}
diff --git a/window_extensions/java/window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Predicate.java b/window_extensions/java/window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Predicate.java new file mode 100644 index 0000000..4942523 --- /dev/null +++ b/window_extensions/java/window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Predicate.java
@@ -0,0 +1,37 @@ +/* + * 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.core.util.function; + +/** + * Represents a predicate boolean-valued function of one argument. + * It is used internally to avoid using Java 8 functional interface that leads to desugaring and + * Proguard shrinking. + * + * @param <T> the type of the input to the predicate + * + * @see java.util.function.Predicate + */ +@FunctionalInterface +public interface Predicate<T> { + /** + * Tests the predicate against a given argument. + * + * @param t the input of the predicate + * @return {@code true} if the input matches the {@code Predicate}, otherwise, {@code false} + */ + boolean test(T t); +}
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 new file mode 100644 index 0000000..74163e6 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
@@ -0,0 +1,162 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions; + +import 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; + +/** + * 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()}. + */ +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 + * 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() { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** + * 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(); + + /** + * 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() { + 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 + * {@link WindowExtensions}. + * @return the OEM implementation of {@link WindowAreaComponent} + */ + @Nullable + default 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 new file mode 100644 index 0000000..1f3aefd --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensionsProvider.java
@@ -0,0 +1,37 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions; + +import androidx.annotation.NonNull; + +/** + * 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. + * @return the OEM implementation of {@link WindowExtensions} + */ + @NonNull + public static 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 new file mode 100644 index 0000000..0ce24b8 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaPresentation.java
@@ -0,0 +1,44 @@ +/* + * 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.area; + +import android.content.Context; +import android.view.View; + +import androidx.annotation.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. + */ + @NonNull + Context getPresentationContext(); + + /** + * Sets the {@link View} that the application wants to display in the extension window area. + */ + void setPresentationView(@NonNull View view); +}
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 new file mode 100644 index 0000000..0dcd47f --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaStatus.java
@@ -0,0 +1,44 @@ +/* + * 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.area; + +import android.util.DisplayMetrics; + +import androidx.annotation.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 { + + /** + * Returns the {@link androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus} + * value that relates to the current status of a feature. + */ + @WindowAreaComponent.WindowAreaStatus + int getWindowAreaStatus(); + + /** + * 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(); +}
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 new file mode 100644 index 0000000..e5705ac --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/area/WindowAreaComponent.java
@@ -0,0 +1,277 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.area; + +import android.app.Activity; +import android.util.DisplayMetrics; + +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; +import androidx.window.extensions.WindowExtensions; +import androidx.window.extensions.core.util.function.Consumer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 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>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} + */ + 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} + */ + 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} + */ + int STATUS_AVAILABLE = 2; + + /** + * WindowArea status constant to signify that the feature is + * already enabled. + * + * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} + */ + 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 + }) + @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. + */ + int SESSION_STATE_INACTIVE = 0; + + /** + * Session state constant to represent that there is currently an active session. The library + * uses this state to know when a session is created and active. Note that this state is + * different from SESSION_STATE_CONTENT_VISIBLE, because the presentation content in this state + * is not visible. + */ + 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. + */ + 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 + }) + @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. + * + * 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} + */ + 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} + */ + 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. + * + * 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 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} + */ + @SuppressWarnings("ExecutorRegistration") // Jetpack will post it on the app-provided executor. + void startRearDisplaySession(@NonNull Activity activity, + @NonNull Consumer<@WindowAreaSessionState Integer> consumer); + + /** + * 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} + */ + 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. + * + * 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} + */ + 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."); + } + + /** + * 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} + */ + 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."); + } + + /** + * 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. + * + * 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. + * + * @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} + */ + 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."); + } + + /** + * 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} + */ + default void endRearDisplayPresentationSession() { + 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} + */ + @Nullable + default 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} + */ + // 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."); + } +}
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 new file mode 100644 index 0000000..121520c --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingComponent.java
@@ -0,0 +1,185 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import android.app.Activity; +import android.app.ActivityOptions; +import android.os.IBinder; +import android.view.WindowMetrics; + +import androidx.annotation.NonNull; +import androidx.window.extensions.WindowExtensions; +import androidx.window.extensions.core.util.function.Consumer; +import androidx.window.extensions.core.util.function.Function; + +import java.util.List; +import java.util.Set; + +/** + * 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} + */ + 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 + @SuppressWarnings("ExecutorRegistration") // Jetpack will post it on the app-provided executor. + void setSplitInfoCallback(@NonNull java.util.function.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. + * + * @param consumer the callback to notify {@link SplitInfo} list changes + * Since {@link WindowExtensions#VENDOR_API_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."); + } + + /** + * 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} + */ + 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} + */ + boolean isActivityEmbedded(@NonNull Activity activity); + + /** + * 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 + * 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: + * <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> + * </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. + * + * @param calculator the callback to set. It will replace the previously set callback if it + * exists. + * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + */ + void setSplitAttributesCalculator( + @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator); + + /** + * Clears the previously callback set in {@link #setSplitAttributesCalculator(Function)}. + * + * @see #setSplitAttributesCalculator(Function) + * Since {@link WindowExtensions#VENDOR_API_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} + */ + @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."); + } + + /** + * Finishes a set of {@link ActivityStack}s. When an {@link ActivityStack} that was in an active + * split is finished, the other {@link ActivityStack} in the same {@link SplitInfo} can be + * 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} + */ + 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."); + } + + /** + * Triggers an update of the split attributes for the top split if there is one visible by + * making extensions invoke the split attributes calculator callback. This method can be used + * 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} + */ + default void invalidateTopVisibleSplitAttributes() { + 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."); + } +}
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 new file mode 100644 index 0000000..c741647 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityRule.java
@@ -0,0 +1,168 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.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.WindowExtensions; +import androidx.window.extensions.core.util.function.Predicate; + +import java.util.Objects; + +/** + * 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 boolean mShouldAlwaysExpand; + + ActivityRule(@NonNull Predicate<Activity> activityPredicate, + @NonNull Predicate<Intent> intentPredicate, boolean shouldAlwaysExpand, + @Nullable String tag) { + super(tag); + mActivityPredicate = activityPredicate; + mIntentPredicate = intentPredicate; + mShouldAlwaysExpand = shouldAlwaysExpand; + } + + /** + * Checks if the rule is applicable to the provided activity. + */ + @SuppressLint("ClassVerificationFailure") // Only called by Extensions implementation on device. + @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. + @RequiresApi(api = Build.VERSION_CODES.N) + public boolean matchesIntent(@NonNull Intent intent) { + return mIntentPredicate.test(intent); + } + + /** + * Indicates whether the activity or activities that are covered by this rule should always be + * launched in an expanded state and avoid the splits. + */ + public boolean shouldAlwaysExpand() { + return mShouldAlwaysExpand; + } + + /** + * Builder for {@link ActivityRule}. + */ + public static final class Builder { + @NonNull + private final Predicate<Activity> mActivityPredicate; + @NonNull + private final Predicate<Intent> mIntentPredicate; + private boolean mAlwaysExpand; + @Nullable + private 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 + @RequiresApi(Build.VERSION_CODES.N) + public Builder(@NonNull java.util.function.Predicate<Activity> activityPredicate, + @NonNull java.util.function.Predicate<Intent> intentPredicate) { + mActivityPredicate = activityPredicate::test; + mIntentPredicate = intentPredicate::test; + } + + /** + * 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} + */ + public Builder(@NonNull Predicate<Activity> activityPredicate, + @NonNull Predicate<Intent> intentPredicate) { + mActivityPredicate = activityPredicate; + mIntentPredicate = intentPredicate; + } + + /** @see ActivityRule#shouldAlwaysExpand() */ + @NonNull + public 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) { + mTag = Objects.requireNonNull(tag); + return this; + } + + /** Builds a new instance of {@link ActivityRule}. */ + @NonNull + public ActivityRule build() { + return new ActivityRule(mActivityPredicate, mIntentPredicate, mAlwaysExpand, mTag); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ActivityRule)) return false; + ActivityRule that = (ActivityRule) o; + return super.equals(o) + && mShouldAlwaysExpand == that.mShouldAlwaysExpand + && mActivityPredicate.equals(that.mActivityPredicate) + && mIntentPredicate.equals(that.mIntentPredicate); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + mActivityPredicate.hashCode(); + result = 31 * result + mIntentPredicate.hashCode(); + result = 31 * result + (mShouldAlwaysExpand ? 1 : 0); + return result; + } + + @NonNull + @Override + public 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 new file mode 100644 index 0000000..e568666 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStack.java
@@ -0,0 +1,138 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import android.app.Activity; +import android.os.Binder; +import android.os.IBinder; + +import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; +import androidx.window.extensions.WindowExtensions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Description of a group of activities stacked on top of each other and shown as a single + * container, all within the same task. + */ +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 boolean mIsEmpty; + + @NonNull + private final IBinder mToken; + + /** + * 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 token The token to identify this {@code ActivityStack} + * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} + */ + ActivityStack(@NonNull List<Activity> activities, boolean isEmpty, @NonNull IBinder token) { + 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); + } + + /** + * 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> + */ + @NonNull + public 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 + * 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() { + return mToken; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ActivityStack)) return false; + ActivityStack that = (ActivityStack) o; + return mActivities.equals(that.mActivities) + && mIsEmpty == that.mIsEmpty + && mToken.equals(that.mToken); + } + + @Override + public int hashCode() { + int result = (mIsEmpty ? 1 : 0); + result = result * 31 + mActivities.hashCode(); + result = result * 31 + mToken.hashCode(); + return result; + } + + @NonNull + @Override + public String toString() { + return "ActivityStack{" + "mActivities=" + mActivities + + ", mIsEmpty=" + mIsEmpty + + ", mToken=" + mToken + + '}'; + } +}
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 new file mode 100644 index 0000000..9333bdc --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddingRule.java
@@ -0,0 +1,63 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import androidx.annotation.Nullable; +import androidx.window.extensions.core.util.function.Function; + +import java.util.Objects; + +/** + * Base interface for activity embedding rules. Used to group different types of rules together when + * updating from the core library. + */ +public abstract class EmbeddingRule { + @Nullable + private final 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 + * {@link ActivityEmbeddingComponent#setSplitAttributesCalculator(Function)}. + * + * Since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2} + */ + @Nullable + public String getTag() { + return mTag; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof EmbeddingRule)) return false; + final EmbeddingRule otherRule = (EmbeddingRule) other; + return Objects.equals(mTag, otherRule.mTag); + } + + @Override + public int hashCode() { + return Objects.hashCode(mTag); + } +}
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 new file mode 100644 index 0000000..e00e910 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributes.java
@@ -0,0 +1,587 @@ +/* + * Copyright 2022 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.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 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.core.util.function.Function; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * 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> + * </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> + * </ul> + * + * @see SplitAttributes.SplitType + * @see SplitAttributes.LayoutDirection + * Since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_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. + */ + public static class SplitType { + @NonNull + private final String mDescription; + + SplitType(@NonNull String description) { + mDescription = description; + } + + @Override + public int hashCode() { + return mDescription.hashCode(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SplitType)) { + return false; + } + final SplitType that = (SplitType) obj; + return mDescription.equals(that.mDescription); + } + + @NonNull + @Override + public String toString() { + return mDescription; + } + + @SuppressLint("Range") // The range is covered. + @NonNull + static 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. + if (splitRatio == 0.0 || splitRatio == 1.0) { + return new ExpandContainersSplitType(); + } + return new RatioSplitType(splitRatio); + } + + /** + * A window split that's based on the ratio of the size of the primary + * container 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> + * </ul> + */ + public static final class RatioSplitType extends SplitType { + @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false) + private final float mRatio; + + /** + * 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. + */ + public RatioSplitType( + @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false) + 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."); + } + mRatio = ratio; + } + + /** + * Gets the proportion of the parent window occupied by the primary + * activity container of the split. + * + * @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() { + return mRatio; + } + + /** + * 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. + * + * @return A {@code RatioSplitType} in which the activity containers + * occupy equal portions of the parent window. + */ + @NonNull + public static 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. + * + * 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> + * </ul> + * + * 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; + + /** + * 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 + * {@link ExpandContainersSplitType}. + */ + public HingeSplitType(@NonNull SplitType fallbackSplitType) { + super("hinge, fallbackType=" + fallbackSplitType); + mFallbackSplitType = fallbackSplitType; + } + + /** + * Returns the fallback {@link SplitType} if a split based on the + * device hinge or separating fold cannot be determined. + */ + @NonNull + public SplitType getFallbackSplitType() { + return mFallbackSplitType; + } + } + + /** + * A window split in which the primary and secondary activity containers + * each occupy the entire parent window. + * + * The secondary container overlays the primary container. + */ + public static final class ExpandContainersSplitType extends SplitType { + + /** + * Creates an instance of this {@code ExpandContainersSplitType}. + */ + public ExpandContainersSplitType() { + super("expandContainers"); + } + } + } + + /** + * 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. + * + * A possible return value of {@link SplitType#getLayoutDirection()}. + */ + // + // ------------------------- + // | | | + // | 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). + * + * 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()}. + */ + // ------------------------- + // | | | + // | 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}. + * + * A possible return value of {@link SplitType#getLayoutDirection()}. + */ + // 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). + * + * 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}. + * + * A possible return value of {@link SplitType#getLayoutDirection()}. + */ + // ------------- + // | | + // | Primary | + // | | + // ------------- + // | | + // | Secondary | + // | | + // ------------- + public static final int TOP_TO_BOTTOM = 4; + + /** + * 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. + * + * 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()}. + */ + // ------------- + // | | + // | Secondary | + // | | + // ------------- + // | | + // | Primary | + // | | + // ------------- + public static final int BOTTOM_TO_TOP = 5; + + private LayoutDirection() {} + } + + @IntDef({LEFT_TO_RIGHT, RIGHT_TO_LEFT, LOCALE, TOP_TO_BOTTOM, BOTTOM_TO_TOP}) + @Retention(RetentionPolicy.SOURCE) + @interface ExtLayoutDirection {} + + @ExtLayoutDirection + private final int mLayoutDirection; + + private final SplitType mSplitType; + + @ColorInt + private final int mAnimationBackgroundColor; + + /** + * 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. + */ + SplitAttributes(@NonNull SplitType splitType, @ExtLayoutDirection int layoutDirection, + @ColorInt int animationBackgroundColor) { + mSplitType = splitType; + mLayoutDirection = layoutDirection; + mAnimationBackgroundColor = animationBackgroundColor; + } + + /** + * Gets the layout direction of the split. + * + * @return The layout direction of the split. + */ + @ExtLayoutDirection + public int getLayoutDirection() { + return mLayoutDirection; + } + + /** + * Gets the split type. + * + * @return The split type. + */ + @NonNull + public 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}. + */ + @ColorInt + @RestrictTo(LIBRARY) + public int getAnimationBackgroundColor() { + return mAnimationBackgroundColor; + } + + /** + * 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. + */ + public static final class Builder { + @NonNull + private SplitType mSplitType = new SplitType.RatioSplitType(0.5f); + @ExtLayoutDirection + private int mLayoutDirection = LOCALE; + + @ColorInt + private int mAnimationBackgroundColor = 0; + + /** + * Sets the split type attribute. + * + * 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) { + mSplitType = splitType; + return this; + } + + /** + * Sets the split layout direction attribute. + * + * The default is based on locale. + * + * 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> + * </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) { + 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}. + */ + @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; + return this; + } + + /** + * Builds a {@link SplitAttributes} instance with the attributes + * specified by {@link #setSplitType}, {@link #setLayoutDirection}, and + * {@link #setAnimationBackgroundColor}. + * + * @return The new {@code SplitAttributes} instance. + */ + @NonNull + public SplitAttributes build() { + return new SplitAttributes(mSplitType, mLayoutDirection, mAnimationBackgroundColor); + } + } + + @Override + public int hashCode() { + int result = mSplitType.hashCode(); + result = result * 31 + mLayoutDirection; + result = result * 31 + mAnimationBackgroundColor; + return result; + } + + @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) + + "}"; + } + + @NonNull + private String layoutDirectionToString() { + switch(mLayoutDirection) { + case LEFT_TO_RIGHT: + return "LEFT_TO_RIGHT"; + case RIGHT_TO_LEFT: + return "RIGHT_TO_LEFT"; + case LOCALE: + return "LOCALE"; + case TOP_TO_BOTTOM: + return "TOP_TO_BOTTOM"; + case BOTTOM_TO_TOP: + return "BOTTOM_TO_TOP"; + default: + throw new IllegalArgumentException("Invalid layout direction:" + mLayoutDirection); + } + } +}
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 new file mode 100644 index 0000000..80f98fe --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributesCalculatorParams.java
@@ -0,0 +1,149 @@ +/* + * Copyright 2022 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.os.Build; +import android.view.WindowMetrics; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.window.extensions.layout.WindowLayoutInfo; + +/** + * 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} + */ +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 boolean mAreDefaultConstraintsSatisfied; + @Nullable + private final String mSplitRuleTag; + + /** Returns the parent container's {@link WindowMetrics} */ + @NonNull + public WindowMetrics getParentWindowMetrics() { + return mParentWindowMetrics; + } + + /** Returns the parent container's {@link Configuration} */ + @NonNull + public 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. + */ + @NonNull + public 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} + */ + public boolean areDefaultConstraintsSatisfied() { + return mAreDefaultConstraintsSatisfied; + } + + /** Returns the parent container's {@link WindowLayoutInfo} */ + @NonNull + public WindowLayoutInfo getParentWindowLayoutInfo() { + return mParentWindowLayoutInfo; + } + + /** + * Returns {@link SplitRule#getTag()} to apply the {@link SplitAttributes} result if it was + * set. + */ + @Nullable + public String getSplitRuleTag() { + return mSplitRuleTag; + } + + SplitAttributesCalculatorParams( + @NonNull WindowMetrics parentWindowMetrics, + @NonNull Configuration parentConfiguration, + @NonNull WindowLayoutInfo parentWindowLayoutInfo, + @NonNull SplitAttributes defaultSplitAttributes, + boolean areDefaultConstraintsSatisfied, + @Nullable String splitRuleTag + ) { + mParentWindowMetrics = parentWindowMetrics; + mParentConfiguration = parentConfiguration; + mParentWindowLayoutInfo = parentWindowLayoutInfo; + mDefaultSplitAttributes = defaultSplitAttributes; + mAreDefaultConstraintsSatisfied = areDefaultConstraintsSatisfied; + 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() + + "}"; + } + } +}
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 new file mode 100644 index 0000000..bac42a4 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitInfo.java
@@ -0,0 +1,151 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import android.os.Binder; +import android.os.IBinder; + +import androidx.annotation.NonNull; +import androidx.window.extensions.WindowExtensions; +import androidx.window.extensions.embedding.SplitAttributes.SplitType; + +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(); + + @NonNull + private final ActivityStack mPrimaryActivityStack; + @NonNull + private final ActivityStack mSecondaryActivityStack; + @NonNull + private final SplitAttributes mSplitAttributes; + + @NonNull + private final IBinder mToken; + + /** + * The {@code SplitInfo} constructor + * + * @param primaryActivityStack The primary {@link ActivityStack} + * @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, + @NonNull ActivityStack secondaryActivityStack, + @NonNull SplitAttributes splitAttributes, + @NonNull IBinder token) { + Objects.requireNonNull(primaryActivityStack); + Objects.requireNonNull(secondaryActivityStack); + Objects.requireNonNull(splitAttributes); + Objects.requireNonNull(token); + mPrimaryActivityStack = primaryActivityStack; + mSecondaryActivityStack = secondaryActivityStack; + mSplitAttributes = splitAttributes; + 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() { + return mPrimaryActivityStack; + } + + @NonNull + public 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 + public float getSplitRatio() { + final SplitType splitType = mSplitAttributes.getSplitType(); + if (splitType instanceof SplitType.RatioSplitType) { + return ((SplitType.RatioSplitType) splitType).getRatio(); + } else { // Fallback to use 0.0 because the WM Jetpack may not support HingeSplitType. + return 0.0f; + } + } + + /** + * Returns the {@link SplitAttributes} of this split. + * Since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2} + */ + @NonNull + public SplitAttributes getSplitAttributes() { + return mSplitAttributes; + } + + /** + * Returns a token uniquely identifying the container. + * Since {@link WindowExtensions#VENDOR_API_LEVEL_3} + */ + @NonNull + public IBinder getToken() { + return mToken; + } + + @Override + public boolean equals(Object o) { + 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); + } + + @Override + public int hashCode() { + int result = mPrimaryActivityStack.hashCode(); + result = result * 31 + mSecondaryActivityStack.hashCode(); + result = result * 31 + mSplitAttributes.hashCode(); + result = result * 31 + mToken.hashCode(); + return result; + } + + @NonNull + @Override + public String toString() { + return "SplitInfo{" + + "mPrimaryActivityStack=" + mPrimaryActivityStack + + ", mSecondaryActivityStack=" + mSecondaryActivityStack + + ", mSplitAttributes=" + mSplitAttributes + + ", mToken=" + mToken + + '}'; + } +}
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 new file mode 100644 index 0000000..ada0637 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPairRule.java
@@ -0,0 +1,319 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +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; +import android.util.Pair; +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.core.util.function.Predicate; + +import java.util.Objects; + +/** + * 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 boolean mClearTop; + + SplitPairRule(@NonNull SplitAttributes defaultSplitAttributes, + @SplitFinishBehavior int finishPrimaryWithSecondary, + @SplitFinishBehavior int finishSecondaryWithPrimary, boolean clearTop, + @NonNull Predicate<Pair<Activity, Activity>> activityPairPredicate, + @NonNull Predicate<Pair<Activity, Intent>> activityIntentPredicate, + @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate, + @Nullable String tag) { + super(parentWindowMetricsPredicate, defaultSplitAttributes, tag); + mActivityPairPredicate = activityPairPredicate; + mActivityIntentPredicate = activityIntentPredicate; + mFinishPrimaryWithSecondary = finishPrimaryWithSecondary; + mFinishSecondaryWithPrimary = finishSecondaryWithPrimary; + mClearTop = clearTop; + } + + /** + * Checks if the rule is applicable to the provided activities. + */ + @SuppressLint("ClassVerificationFailure") // Only called by Extensions implementation on device. + @RequiresApi(api = Build.VERSION_CODES.N) + public boolean matchesActivityPair(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity) { + return mActivityPairPredicate.test(new Pair<>(primaryActivity, secondaryActivity)); + } + + /** + * 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) { + return mActivityIntentPredicate.test(new Pair<>(primaryActivity, secondaryActivityIntent)); + } + + /** + * Determines what happens with the primary container when all activities are finished in the + * associated secondary container. + */ + @SplitFinishBehavior + public int getFinishPrimaryWithSecondary() { + return mFinishPrimaryWithSecondary; + } + + /** + * Determines what happens with the secondary container when all activities are finished in the + * associated primary container. + */ + @SplitFinishBehavior + public int getFinishSecondaryWithPrimary() { + return mFinishSecondaryWithPrimary; + } + + /** + * 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}. + */ + 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; + // Keep for backward compatibility + @FloatRange(from = 0.0, to = 1.0) + private float mSplitRatio; + // Keep for backward compatibility + @SplitAttributes.ExtLayoutDirection + private int mLayoutDirection; + private SplitAttributes mDefaultSplitAttributes; + private boolean mClearTop; + @SplitFinishBehavior + private int mFinishPrimaryWithSecondary; + @SplitFinishBehavior + private int mFinishSecondaryWithPrimary; + @Nullable + private 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 + @RequiresApi(Build.VERSION_CODES.N) + public Builder(@NonNull java.util.function.Predicate<Pair<Activity, Activity>> + activityPairPredicate, + @NonNull java.util.function.Predicate<Pair<Activity, Intent>> + activityIntentPredicate, + @NonNull java.util.function.Predicate<WindowMetrics> + parentWindowMetricsPredicate) { + mActivityPairPredicate = activityPairPredicate::test; + mActivityIntentPredicate = activityIntentPredicate::test; + mParentWindowMetricsPredicate = parentWindowMetricsPredicate::test; + } + + /** + * The {@link SplitPairRule} builder constructor + * + * @param activityPairPredicate the {@link Predicate} to verify if an {@link Activity} pair + * matches this rule + * @param activityIntentPredicate the {@link Predicate} to verify if an ({@link Activity}, + * {@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} + */ + public Builder(@NonNull Predicate<Pair<Activity, Activity>> activityPairPredicate, + @NonNull Predicate<Pair<Activity, Intent>> activityIntentPredicate, + @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate) { + mActivityPairPredicate = activityPairPredicate; + mActivityIntentPredicate = activityIntentPredicate; + mParentWindowMetricsPredicate = parentWindowMetricsPredicate; + } + + /** + * @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 + @NonNull + public 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 + @NonNull + public 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} + */ + @NonNull + public Builder setDefaultSplitAttributes(@NonNull SplitAttributes attrs) { + mDefaultSplitAttributes = attrs; + return this; + } + + /** @deprecated To be removed with next developer preview. */ + @Deprecated + @NonNull + public Builder setShouldFinishPrimaryWithSecondary( + boolean finishPrimaryWithSecondary) { + return this; + } + + /** @deprecated To be removed with next developer preview. */ + @Deprecated + @NonNull + public Builder setShouldFinishSecondaryWithPrimary(boolean finishSecondaryWithPrimary) { + return this; + } + + /** @see SplitPairRule#getFinishPrimaryWithSecondary() */ + @NonNull + public Builder setFinishPrimaryWithSecondary(@SplitFinishBehavior int finishBehavior) { + mFinishPrimaryWithSecondary = finishBehavior; + return this; + } + + /** @see SplitPairRule#getFinishSecondaryWithPrimary() */ + @NonNull + public Builder setFinishSecondaryWithPrimary(@SplitFinishBehavior int finishBehavior) { + mFinishSecondaryWithPrimary = finishBehavior; + return this; + } + + /** @see SplitPairRule#shouldClearTop() */ + @NonNull + public 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) { + mTag = Objects.requireNonNull(tag); + return this; + } + + /** Builds a new instance of {@link SplitPairRule}. */ + @NonNull + public 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); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SplitPairRule)) return false; + SplitPairRule that = (SplitPairRule) o; + return super.equals(o) + && mActivityPairPredicate.equals(that.mActivityPairPredicate) + && mActivityIntentPredicate.equals(that.mActivityIntentPredicate) + && mFinishPrimaryWithSecondary == that.mFinishPrimaryWithSecondary + && mFinishSecondaryWithPrimary == that.mFinishSecondaryWithPrimary + && mClearTop == that.mClearTop; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + mActivityPairPredicate.hashCode(); + result = 31 * result + mActivityIntentPredicate.hashCode(); + result = 31 * result + mFinishPrimaryWithSecondary; + result = 31 * result + mFinishSecondaryWithPrimary; + result = 31 * result + (mClearTop ? 1 : 0); + return result; + } + + @NonNull + @Override + public String toString() { + return "SplitPairRule{" + + "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/SplitPlaceholderRule.java b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPlaceholderRule.java new file mode 100644 index 0000000..38b5451 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPlaceholderRule.java
@@ -0,0 +1,346 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +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; +import android.view.WindowMetrics; + +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.core.util.function.Predicate; + +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. + */ +public class SplitPlaceholderRule extends SplitRule { + @NonNull + private final Predicate<Activity> mActivityPredicate; + @NonNull + private final Predicate<Intent> mIntentPredicate; + @NonNull + private final 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. + */ + @IntDef({ + FINISH_ALWAYS, + FINISH_ADJACENT + }) + @Retention(RetentionPolicy.SOURCE) + @interface SplitPlaceholderFinishBehavior{} + + @SplitPlaceholderFinishBehavior + private final int mFinishPrimaryWithPlaceholder; + + SplitPlaceholderRule(@NonNull Intent placeholderIntent, + @NonNull SplitAttributes defaultSplitAttributes, + boolean isSticky, + @SplitPlaceholderFinishBehavior int finishPrimaryWithPlaceholder, + @NonNull Predicate<Activity> activityPredicate, + @NonNull Predicate<Intent> intentPredicate, + @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate, + @Nullable String tag) { + super(parentWindowMetricsPredicate, defaultSplitAttributes, tag); + mIsSticky = isSticky; + mFinishPrimaryWithPlaceholder = finishPrimaryWithPlaceholder; + mActivityPredicate = activityPredicate; + mIntentPredicate = intentPredicate; + mPlaceholderIntent = placeholderIntent; + } + + /** + * Checks if the rule is applicable to the provided activity. + */ + @SuppressLint("ClassVerificationFailure") // Only called by Extensions implementation on device. + @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. + @RequiresApi(api = Build.VERSION_CODES.N) + public boolean matchesIntent(@NonNull Intent intent) { + return mIntentPredicate.test(intent); + } + + /** + * An {@link Intent} used by Extensions Sidecar to launch the placeholder when the space allows. + */ + @NonNull + public Intent getPlaceholderIntent() { + return mPlaceholderIntent; + } + + /** + * Determines whether the placeholder will show on top in a smaller window size after it first + * appeared in a split with sufficient minimum width. + */ + public boolean isSticky() { + return mIsSticky; + } + + /** + * @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 + @SplitPlaceholderFinishBehavior + public int getFinishPrimaryWithSecondary() { + return getFinishPrimaryWithPlaceholder(); + } + + /** + * 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. + @SplitPlaceholderFinishBehavior + public int getFinishPrimaryWithPlaceholder() { + return mFinishPrimaryWithPlaceholder; + } + + /** + * 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; + // Keep for backward compatibility + @FloatRange(from = 0.0, to = 1.0) + private float mSplitRatio; + // Keep for backward compatibility + @SplitAttributes.ExtLayoutDirection + private int mLayoutDirection; + private SplitAttributes mDefaultSplitAttributes; + private boolean mIsSticky = false; + @SplitPlaceholderFinishBehavior + private int mFinishPrimaryWithPlaceholder = FINISH_ALWAYS; + @Nullable + private 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}. + */ + @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) { + mActivityPredicate = activityPredicate::test; + mIntentPredicate = intentPredicate::test; + mPlaceholderIntent = placeholderIntent; + mParentWindowMetricsPredicate = parentWindowMetricsPredicate::test; + } + + /** + * The {@link SplitPlaceholderRule} Builder constructor + * @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 + * @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} + */ + public Builder(@NonNull Intent placeholderIntent, + @NonNull Predicate<Activity> activityPredicate, + @NonNull Predicate<Intent> intentPredicate, + @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate) { + mActivityPredicate = activityPredicate; + mIntentPredicate = intentPredicate; + mPlaceholderIntent = placeholderIntent; + mParentWindowMetricsPredicate = parentWindowMetricsPredicate; + } + + /** + * @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 + @NonNull + public 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 + @NonNull + public 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} + */ + @NonNull + public Builder setDefaultSplitAttributes(@NonNull SplitAttributes attrs) { + mDefaultSplitAttributes = attrs; + return this; + } + + /** @see SplitPlaceholderRule#isSticky() */ + @NonNull + public Builder setSticky(boolean sticky) { + mIsSticky = sticky; + return this; + } + + /** + * @deprecated Use SplitPlaceholderRule#setFinishPrimaryWithPlaceholder(int)} starting with + * {@link WindowExtensions#VENDOR_API_LEVEL_2}. + */ + @Deprecated + @NonNull + public Builder setFinishPrimaryWithSecondary( + @SplitPlaceholderFinishBehavior int finishBehavior) { + if (finishBehavior == FINISH_NEVER) { + finishBehavior = FINISH_ALWAYS; + } + return setFinishPrimaryWithPlaceholder(finishBehavior); + } + + /** + * @see SplitPlaceholderRule#getFinishPrimaryWithPlaceholder() + * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + */ + // TODO(b/238905747): Add api guard for extensions. + @NonNull + public Builder setFinishPrimaryWithPlaceholder( + @SplitPlaceholderFinishBehavior int finishBehavior) { + mFinishPrimaryWithPlaceholder = finishBehavior; + return this; + } + + /** + * @see SplitPlaceholderRule#getTag() + * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + */ + @NonNull + public Builder setTag(@NonNull String tag) { + mTag = Objects.requireNonNull(tag); + return this; + } + + /** Builds a new instance of {@link SplitPlaceholderRule}. */ + @NonNull + public 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); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SplitPlaceholderRule)) return false; + if (!super.equals(o)) return false; + + SplitPlaceholderRule that = (SplitPlaceholderRule) o; + + if (mIsSticky != that.mIsSticky) return false; + if (mFinishPrimaryWithPlaceholder != that.mFinishPrimaryWithPlaceholder) return false; + if (!mActivityPredicate.equals(that.mActivityPredicate)) return false; + if (!mIntentPredicate.equals(that.mIntentPredicate)) return false; + return mPlaceholderIntent.equals(that.mPlaceholderIntent); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + mActivityPredicate.hashCode(); + result = 31 * result + mIntentPredicate.hashCode(); + result = 31 * result + mPlaceholderIntent.hashCode(); + result = 31 * result + (mIsSticky ? 1 : 0); + result = 31 * result + mFinishPrimaryWithPlaceholder; + return result; + } + + @NonNull + @Override + public String toString() { + return "SplitPlaceholderRule{" + + "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 new file mode 100644 index 0000000..dd02b14 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitRule.java
@@ -0,0 +1,177 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.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.core.util.function.Predicate; +import androidx.window.extensions.embedding.SplitAttributes.SplitType; + +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. + */ +public abstract class SplitRule extends EmbeddingRule { + @NonNull + private final Predicate<WindowMetrics> mParentWindowMetricsPredicate; + + @NonNull + private final 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> + * + * @see SplitPairRule#getFinishPrimaryWithSecondary() + * @see SplitPairRule#getFinishSecondaryWithPrimary() + * @see SplitPlaceholderRule#getFinishPrimaryWithSecondary() + */ + @IntDef({ + FINISH_NEVER, + FINISH_ALWAYS, + FINISH_ADJACENT + }) + @Retention(RetentionPolicy.SOURCE) + @interface SplitFinishBehavior {} + + SplitRule(@NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate, + @NonNull SplitAttributes defaultSplitAttributes, @Nullable String tag) { + super(tag); + mParentWindowMetricsPredicate = parentWindowMetricsPredicate; + mDefaultSplitAttributes = defaultSplitAttributes; + } + + /** + * 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}. + * + * @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 + public float getSplitRatio() { + final SplitType splitType = mDefaultSplitAttributes.getSplitType(); + if (splitType instanceof SplitType.RatioSplitType) { + return ((SplitType.RatioSplitType) splitType).getRatio(); + } else { // Fallback to use 0.0 because the WM Jetpack may not support HingeSplitType. + return 0.0f; + } + } + + /** + * @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 + @SplitAttributes.ExtLayoutDirection + public int getLayoutDirection() { + return mDefaultSplitAttributes.getLayoutDirection(); + } + + /** + * Returns the default {@link SplitAttributes} which is applied if + * {@link #checkParentMetrics(WindowMetrics)} is {@code true}. + * + * Since {@link WindowExtensions#VENDOR_API_LEVEL_2} + */ + @NonNull + public SplitAttributes getDefaultSplitAttributes() { + return mDefaultSplitAttributes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SplitRule)) return false; + SplitRule that = (SplitRule) o; + return super.equals(that) + && mDefaultSplitAttributes.equals(that.mDefaultSplitAttributes) + && mParentWindowMetricsPredicate.equals(that.mParentWindowMetricsPredicate); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + mParentWindowMetricsPredicate.hashCode(); + result = 31 * result + Objects.hashCode(mDefaultSplitAttributes); + return result; + } + + @NonNull + @Override + public String toString() { + return "SplitRule{" + + "mTag=" + getTag() + + ", mDefaultSplitAttributes=" + mDefaultSplitAttributes + + '}'; + } +}
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 new file mode 100644 index 0000000..32b0fa1 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/DisplayFeature.java
@@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.layout; + +import android.graphics.Rect; + +import androidx.annotation.NonNull; + +/** + * 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. + * + * @return bounds of display feature. + */ + @NonNull + Rect getBounds(); +}
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 new file mode 100644 index 0000000..9f95510 --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/FoldingFeature.java
@@ -0,0 +1,201 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.layout; + +import android.graphics.Rect; + +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.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. + */ +public class FoldingFeature implements DisplayFeature { + + /** + * 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. + */ + public static final int TYPE_HINGE = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + TYPE_FOLD, + TYPE_HINGE, + }) + @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. + */ + 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. + */ + public static final int STATE_HALF_OPENED = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + STATE_HALF_OPENED, + STATE_FLAT + }) + @interface State {} + + /** + * The bounding rectangle of the feature within the application window in the window + * coordinate space. + */ + @NonNull + private final Rect mBounds; + + /** + * The physical type of the feature. + */ + @Type + private final int mType; + + /** + * The state of the feature. + */ + @State + private final int mState; + + public FoldingFeature(@NonNull Rect bounds, @Type int type, @State int state) { + validateFeatureBounds(bounds); + mBounds = new Rect(bounds); + mType = type; + mState = state; + } + + /** Gets the bounding rect of the display feature in window coordinate space. */ + @NonNull + @Override + public Rect getBounds() { + return new Rect(mBounds); + } + + /** Gets the type of the display feature. */ + @Type + public int getType() { + return mType; + } + + /** Gets the state of the display feature. */ + @State + public int getState() { + return mState; + } + + /** + * 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); + } + } + + @NonNull + private static String typeToString(int type) { + switch (type) { + case TYPE_FOLD: + return "FOLD"; + case TYPE_HINGE: + return "HINGE"; + default: + return "Unknown feature type (" + type + ")"; + } + } + + @NonNull + private static String stateToString(int state) { + switch (state) { + case STATE_FLAT: + return "FLAT"; + case STATE_HALF_OPENED: + return "HALF_OPENED"; + default: + return "Unknown feature state (" + state + ")"; + } + } + + @NonNull + @Override + public String toString() { + return "ExtensionDisplayFoldFeature { " + mBounds + + ", type=" + typeToString(getType()) + ", state=" + stateToString(mState) + " }"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof FoldingFeature)) { + return false; + } + final FoldingFeature other = (FoldingFeature) obj; + if (mType != other.mType) { + return false; + } + if (mState != other.mState) { + return false; + } + return mBounds.equals(other.mBounds); + } + + /** + * 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; + result = 31 * result + bounds.right; + result = 31 * result + bounds.bottom; + return result; + } + + @Override + public int hashCode() { + int result = hashBounds(mBounds); + result = 31 * result + mType; + result = 31 * result + mState; + return result; + } +}
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 new file mode 100644 index 0000000..0b7c01f --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutComponent.java
@@ -0,0 +1,98 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.layout; + +import android.app.Activity; +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.UiContext; +import androidx.window.extensions.WindowExtensions; +import androidx.window.extensions.core.util.function.Consumer; + +/** + * 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. + * + * <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 + void addWindowLayoutInfoListener(@NonNull Activity activity, + @NonNull java.util.function.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 + void removeWindowLayoutInfoListener( + @NonNull java.util.function.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. + * + * @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. + @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."); + } + + /** + * 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} + */ + 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."); + } +}
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 new file mode 100644 index 0000000..9041f0a --- /dev/null +++ b/window_extensions/java/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutInfo.java
@@ -0,0 +1,85 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.layout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Collections; +import java.util.List; + +/** + * Contains information about the layout of display features within the window. + */ +public class WindowLayoutInfo { + + /** + * 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; + + public WindowLayoutInfo(@NonNull List<DisplayFeature> displayFeatures) { + mDisplayFeatures = Collections.unmodifiableList(displayFeatures); + } + + /** + * Gets the list of display features present within the window. + */ + @NonNull + public List<DisplayFeature> getDisplayFeatures() { + return mDisplayFeatures; + } + + @NonNull + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("ExtensionWindowLayoutInfo { ExtensionDisplayFeatures[ "); + for (int i = 0; i < mDisplayFeatures.size(); i = i + 1) { + sb.append(mDisplayFeatures.get(i)); + if (i < mDisplayFeatures.size() - 1) { + sb.append(", "); + } + } + sb.append(" ] }"); + return sb.toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof WindowLayoutInfo)) { + return false; + } + final WindowLayoutInfo + other = (WindowLayoutInfo) obj; + if (mDisplayFeatures == null) { + return other.mDisplayFeatures == null; + } + return mDisplayFeatures.equals(other.mDisplayFeatures); + } + + @Override + public int hashCode() { + return mDisplayFeatures != null ? mDisplayFeatures.size() : 0; + } +}
diff --git a/window_extensions/sources.txt b/window_extensions/sources.txt new file mode 100644 index 0000000..2ccdd8c --- /dev/null +++ b/window_extensions/sources.txt
@@ -0,0 +1,22 @@ +window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Consumer.java +window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Function.java +window/extensions/core/core/src/main/java/androidx/window/extensions/core/util/function/Predicate.java +window/extensions/extensions/src/main/java/androidx/window/extensions/area/ExtensionWindowAreaPresentation.java +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/ActivityRule.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityStack.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddingRule.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/SplitPlaceholderRule.java +window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitRule.java +window/extensions/extensions/src/main/java/androidx/window/extensions/layout/DisplayFeature.java +window/extensions/extensions/src/main/java/androidx/window/extensions/layout/FoldingFeature.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/WindowExtensions.java +window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensionsProvider.java
diff --git a/window_extensions/update_source.sh b/window_extensions/update_source.sh new file mode 100755 index 0000000..56708cb --- /dev/null +++ b/window_extensions/update_source.sh
@@ -0,0 +1,24 @@ +#!/bin/bash + +set -e +cd $(dirname $0) + +# Clone androidx repo. +rm -rf temp-git-clone +git clone --depth 1 https://android.googlesource.com/platform/frameworks/support temp-git-clone + +# Update sources.txt +cd temp-git-clone +find window/extensions -path "*/main/*.java" | sort > ../sources.txt +cd .. + +# Copy the .java files. +rm -r java +for path in $(cat sources.txt); do + mkdir -p java/$(dirname "$path") + cp "temp-git-clone/$path" "java/$path" +done + +rm -rf temp-git-clone + +echo "Job's Done."