1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.xr.runtime.internal
18 
19 import androidx.annotation.IntDef
20 import androidx.annotation.RestrictTo
21 import java.util.Objects
22 import java.util.function.Consumer
23 
24 /**
25  * Interface for updating the background image/geometry and passthrough settings.
26  *
27  * <p>The application can set either / both a skybox and a glTF for geometry, then toggle their
28  * visibility by enabling or disabling passthrough. The skybox and geometry will be remembered
29  * across passthrough mode changes.
30  */
31 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
32 public interface SpatialEnvironment {
33     /**
34      * Gets the current passthrough opacity value between 0 and 1 where 0.0f means no passthrough,
35      * and 1.0f means full passthrough.
36      *
37      * <p>This value can be overwritten by user-enabled or system-enabled passthrough and will not
38      * always match the opacity value returned by [getPassthroughOpacityPreference].
39      */
40     public val currentPassthroughOpacity: Float
41 
42     /**
43      * Gets the preferred spatial environment for the application.
44      *
45      * <p>The returned value is always what was most recently supplied to
46      * [setSpatialEnvironmentPreference], or null if no preference has been set.
47      *
48      * <p>See [isSpatialEnvironmentPreferenceActive] or the [OnSpatialEnvironmentChangedListener]
49      * events to know when this preference becomes active.
50      */
51     public val spatialEnvironmentPreference: SpatialEnvironmentPreference?
52 
53     /**
54      * Gets the last passthrough opacity requested through [setPassthroughOpacityPreference].
55      *
56      * <p>This may be different from the actual current state returned by
57      * [getCurrentPassthroughOpacity], but it should be applied as soon as the
58      * [SpatialCapabilities.SPATIAL_CAPABILITY_PASSTHROUGH_CONTROL] capability is gained. Defaults
59      * to null, if [setPassthroughOpacityPreference] was never called.
60      *
61      * <p>If set to null, the passthrough opacity will default to the user preference managed
62      * through the system.
63      */
64     public val passthroughOpacityPreference: Float?
65 
66     /**
67      * Notifies an application when the passthrough state changes, such as when the application
68      * enters or exits passthrough or when the passthrough opacity changes. This [listener] will be
69      * called on the Application's UI thread.
70      */
addOnPassthroughOpacityChangedListenernull71     public fun addOnPassthroughOpacityChangedListener(listener: Consumer<Float>)
72 
73     /** Remove a listener previously added by [addOnPassthroughOpacityChangedListener]. */
74     public fun removeOnPassthroughOpacityChangedListener(listener: Consumer<Float>)
75 
76     /**
77      * Returns true if the environment set by [setSpatialEnvironmentPreference] is active.
78      *
79      * <p>Spatial environment preference set through [setSpatialEnvironmentPreference] are shown
80      * when this is true, but passthrough or other objects in the scene could partially or totally
81      * occlude them. When this is false, the default system environment will be active instead.
82      */
83     public fun isSpatialEnvironmentPreferenceActive(): Boolean
84 
85     /**
86      * Sets the preferred spatial environment for the application.
87      *
88      * <p>Note that this method only sets a preference and does not cause an immediate change unless
89      * [isSpatialEnvironmentPreferenceActive] is already true. Once the device enters a state where
90      * the XR background can be changed and the
91      * [SpatialCapabilities.SPATIAL_CAPABILITY_APP_ENVIRONMENTS] capability is available, the
92      * preferred spatial environment for the application will be automatically displayed.
93      *
94      * <p>Setting the preference to null will disable the preferred spatial environment for the
95      * application, meaning the default system environment will be displayed instead.
96      *
97      * <p>If the given [SpatialEnvironmentPreference] is not null, but all of its properties are
98      * null, then the spatial environment will consist of a black skybox and no geometry
99      * [isSpatialEnvironmentPreferenceActive] is true.
100      *
101      * <p>Changes to the Environment state will be notified via the
102      * [OnSpatialEnvironmentChangedListener].
103      */
104     public fun setSpatialEnvironmentPreference(
105         preference: SpatialEnvironmentPreference?
106     ): @SetSpatialEnvironmentPreferenceResult Int
107 
108     /**
109      * Sets the preference for passthrough state by requesting a change in passthrough opacity.
110      *
111      * <p>Passthrough visibility cannot be set directly to on/off modes. Instead, a desired
112      * passthrough opacity value between 0.0f and 1.0f can be requested which will dictate which
113      * mode is used. A passthrough opacity within 0.01f of 0.0f will disable passthrough, and will
114      * be returned as 0.0f by [getPassthroughOpacityPreference]. An opacity value within 0.01f of
115      * 1.0f will enable full passthrough and it will be returned as 1.0f by
116      * [getPassthroughOpacityPreference]. Any other value in the range will result in a
117      * semi-transparent passthrough.
118      *
119      * <p>Requesting to set passthrough opacity to a value that is not in the range of 0.0f to 1.0f
120      * will result in the value getting clamped to 0.0f or 1.0f depending on which one is closer.
121      *
122      * <p>If the value is set to null, the opacity will be managed by the system.
123      *
124      * <p>Requests to change opacity are only immediately attempted to be honored if the activity
125      * has the [SpatialCapabilities.SPATIAL_CAPABILITY_PASSTHROUGH_CONTROL] capability. When the
126      * request is honored, this returns [SetPassthroughOpacityPreferenceChangeApplied]. When the
127      * activity does not have the capability to control the passthrough state, this returns
128      * [SetPassthroughOpacityPreferenceChangePending] to indicate that the application passthrough
129      * opacity preference has been set and is pending to be automatically applied when the app
130      * regains capabilities to control passthrough state.
131      *
132      * <p>When passthrough state changes, whether due to this request succeeding or due to any other
133      * system or user initiated change, [OnPassthroughOpacityChangedListener] will be notified.
134      */
135     public fun setPassthroughOpacityPreference(
136         passthroughOpacityPreference: Float?
137     ): @SetPassthroughOpacityPreferenceResult Int
138 
139     /**
140      * Notifies an application whether or not the preferred spatial environment for the application
141      * is active.
142      *
143      * <p>The environment will try to transition to the application environment when a non-null
144      * preference is set through [setSpatialEnvironmentPreference] and the application has the
145      * [SpatialCapabilities.SPATIAL_CAPABILITY_APP_ENVIRONMENTS] capability. The environment
146      * preferences will otherwise not be active.
147      *
148      * <p>The listener consumes a boolean value that is true if the environment preference is active
149      * when the listener is notified.
150      *
151      * <p>This listener will be invoked on the Application's UI thread.
152      */
153     public fun addOnSpatialEnvironmentChangedListener(listener: Consumer<Boolean>)
154 
155     /** Remove a listener previously added by [addOnSpatialEnvironmentChangedListener]. */
156     public fun removeOnSpatialEnvironmentChangedListener(listener: Consumer<Boolean>)
157 
158     /** Result values for calls to SpatialEnvironment.setPassthroughOpacityPreference */
159     @IntDef(
160         SetPassthroughOpacityPreferenceResult.CHANGE_APPLIED,
161         SetPassthroughOpacityPreferenceResult.CHANGE_PENDING,
162     )
163     @Target(AnnotationTarget.TYPE)
164     @Retention(AnnotationRetention.SOURCE)
165     @Suppress("PublicTypedef")
166     public annotation class SetPassthroughOpacityPreferenceResult {
167         public companion object {
168             /**
169              * The call to [setPassthroughOpacityPreference] succeeded and should now be visible.
170              */
171             public const val CHANGE_APPLIED: Int = 0
172             /**
173              * The preference has been set, but will be applied only when the
174              * [SpatialCapabilities.SPATIAL_CAPABILITY_PASSTHROUGH_CONTROL] is acquired
175              */
176             public const val CHANGE_PENDING: Int = 1
177         }
178     }
179 
180     /** Result values for calls to SpatialEnvironment.setSpatialEnvironmentPreference */
181     @IntDef(
182         SetSpatialEnvironmentPreferenceResult.CHANGE_APPLIED,
183         SetSpatialEnvironmentPreferenceResult.CHANGE_PENDING,
184     )
185     @Target(AnnotationTarget.TYPE)
186     @Retention(AnnotationRetention.SOURCE)
187     @Suppress("PublicTypedef")
188     public annotation class SetSpatialEnvironmentPreferenceResult {
189         public companion object {
190             /**
191              * The call to [setSpatialEnvironmentPreference] succeeded and should now be visible.
192              */
193             public const val CHANGE_APPLIED: Int = 0
194             /**
195              * The call to [setSpatialEnvironmentPreference] successfully applied the preference,
196              * but it is not immediately visible due to requesting a state change while the activity
197              * does not have the [SpatialCapabilities.SPATIAL_CAPABILITY_APP_ENVIRONMENTS]
198              * capability to control the app environment state. The preference was still set and
199              * will be applied when the capability is gained.
200              */
201             public const val CHANGE_PENDING: Int = 1
202         }
203     }
204 
205     /**
206      * A class that represents the user's preferred spatial environment.
207      *
208      * @param geometry the preferred geometry for the environment based on a pre-loaded glTF model.
209      *   If null, there will be no geometry.
210      * @param skybox the preferred skybox for the environment based on a pre-loaded EXR Image. If
211      *   null, it will be all black.
212      * @param geometryMaterial the material to override a given mesh in the geometry. If null, the
213      *   material will not override any mesh.
214      * @param geometryMeshName the name of the mesh to override with the material. If null, the
215      *   material will not override any mesh.
216      * @param geometryAnimationName the name of the animation to play on the geometry. If null, the
217      *   geometry will not play any animation. Note that the animation will be played in loop.
218      */
219     public class SpatialEnvironmentPreference
220     @JvmOverloads
221     constructor(
222         public val skybox: ExrImageResource?,
223         public val geometry: GltfModelResource?,
224         public val geometryMaterial: MaterialResource? = null,
225         public val geometryMeshName: String? = null,
226         public val geometryAnimationName: String? = null,
227     ) {
equalsnull228         override fun equals(other: Any?): Boolean {
229             if (this === other) return true
230             if (other !is SpatialEnvironmentPreference) return false
231 
232             return skybox == other.skybox &&
233                 geometry == other.geometry &&
234                 geometryMaterial == other.geometryMaterial &&
235                 geometryMeshName == other.geometryMeshName &&
236                 geometryAnimationName == other.geometryAnimationName
237         }
238 
hashCodenull239         override fun hashCode(): Int {
240             return Objects.hash(skybox, geometry)
241         }
242     }
243 }
244