• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.window.extensions.area;
18 
19 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
20 
21 import android.app.Activity;
22 import android.content.Context;
23 import android.hardware.devicestate.DeviceStateManager;
24 import android.hardware.devicestate.DeviceStateRequest;
25 import android.util.ArraySet;
26 
27 import androidx.annotation.NonNull;
28 import androidx.window.extensions.core.util.function.Consumer;
29 
30 import com.android.internal.R;
31 import com.android.internal.annotations.GuardedBy;
32 
33 import java.util.concurrent.Executor;
34 
35 /**
36  * Reference implementation of androidx.window.extensions.area OEM interface for use with
37  * WindowManager Jetpack.
38  *
39  * This component currently supports Rear Display mode with the ability to add and remove
40  * status listeners for this mode.
41  *
42  * The public methods in this class are thread-safe.
43  **/
44 public class WindowAreaComponentImpl implements WindowAreaComponent,
45         DeviceStateManager.DeviceStateCallback {
46 
47     private final Object mLock = new Object();
48 
49     private final DeviceStateManager mDeviceStateManager;
50     private final Executor mExecutor;
51 
52     @GuardedBy("mLock")
53     private final ArraySet<Consumer<Integer>> mRearDisplayStatusListeners = new ArraySet<>();
54     private final int mRearDisplayState;
55     @WindowAreaSessionState
56     private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
57 
58     @GuardedBy("mLock")
59     private int mCurrentDeviceState = INVALID_DEVICE_STATE;
60     @GuardedBy("mLock")
61     private int mCurrentDeviceBaseState = INVALID_DEVICE_STATE;
62     @GuardedBy("mLock")
63     private DeviceStateRequest mDeviceStateRequest;
64 
WindowAreaComponentImpl(@onNull Context context)65     public WindowAreaComponentImpl(@NonNull Context context) {
66         mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
67         mExecutor = context.getMainExecutor();
68 
69         // TODO(b/236022708) Move rear display state to device state config file
70         mRearDisplayState = context.getResources().getInteger(
71                 R.integer.config_deviceStateRearDisplay);
72 
73         mDeviceStateManager.registerCallback(mExecutor, this);
74     }
75 
76     /**
77      * Adds a listener interested in receiving updates on the RearDisplayStatus
78      * of the device. Because this is being called from the OEM provided
79      * extensions, we will post the result of the listener on the executor
80      * provided by the developer at the initial call site.
81      *
82      * Depending on the initial state of the device, we will return either
83      * {@link WindowAreaComponent#STATUS_AVAILABLE} or
84      * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that
85      * state respectively. When the rear display feature is triggered, we update the status to be
86      * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. TODO(b/240727590) Prefix with AREA_
87      *
88      * TODO(b/239833099) Add a STATUS_ACTIVE option to let apps know if a feature is currently
89      * enabled.
90      *
91      * @param consumer {@link Consumer} interested in receiving updates to the status of
92      * rear display mode.
93      */
addRearDisplayStatusListener( @onNull Consumer<@WindowAreaStatus Integer> consumer)94     public void addRearDisplayStatusListener(
95             @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
96         synchronized (mLock) {
97             mRearDisplayStatusListeners.add(consumer);
98 
99             // If current device state is still invalid, we haven't gotten our initial value yet
100             if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
101                 return;
102             }
103             consumer.accept(getCurrentStatus());
104         }
105     }
106 
107     /**
108      * Removes a listener no longer interested in receiving updates.
109      * @param consumer no longer interested in receiving updates to RearDisplayStatus
110      */
removeRearDisplayStatusListener( @onNull Consumer<@WindowAreaStatus Integer> consumer)111     public void removeRearDisplayStatusListener(
112             @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
113         synchronized (mLock) {
114             mRearDisplayStatusListeners.remove(consumer);
115         }
116     }
117 
118     /**
119      * Creates and starts a rear display session and provides updates to the
120      * callback provided. Because this is being called from the OEM provided
121      * extensions, we will post the result of the listener on the executor
122      * provided by the developer at the initial call site.
123      *
124      * When we enable rear display mode, we submit a request to {@link DeviceStateManager}
125      * to override the device state to the state that corresponds to RearDisplay
126      * mode. When the {@link DeviceStateRequest} is activated, we let the
127      * consumer know that the session is active by sending
128      * {@link WindowAreaComponent#SESSION_STATE_ACTIVE}.
129      *
130      * @param activity to provide updates to the client on
131      * the status of the Session
132      * @param rearDisplaySessionCallback to provide updates to the client on
133      * the status of the Session
134      */
startRearDisplaySession(@onNull Activity activity, @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback)135     public void startRearDisplaySession(@NonNull Activity activity,
136             @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback) {
137         synchronized (mLock) {
138             if (mDeviceStateRequest != null) {
139                 // Rear display session is already active
140                 throw new IllegalStateException(
141                         "Unable to start new rear display session as one is already active");
142             }
143             mDeviceStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build();
144             mDeviceStateManager.requestState(
145                     mDeviceStateRequest,
146                     mExecutor,
147                     new DeviceStateRequestCallbackAdapter(rearDisplaySessionCallback)
148             );
149         }
150     }
151 
152     /**
153      * Ends the current rear display session and provides updates to the
154      * callback provided. Because this is being called from the OEM provided
155      * extensions, we will post the result of the listener on the executor
156      * provided by the developer.
157      */
endRearDisplaySession()158     public void endRearDisplaySession() {
159         synchronized (mLock) {
160             if (mDeviceStateRequest != null || isRearDisplayActive()) {
161                 mDeviceStateRequest = null;
162                 mDeviceStateManager.cancelStateRequest();
163             } else {
164                 throw new IllegalStateException(
165                         "Unable to cancel a rear display session as there is no active session");
166             }
167         }
168     }
169 
170     @Override
onBaseStateChanged(int state)171     public void onBaseStateChanged(int state) {
172         synchronized (mLock) {
173             mCurrentDeviceBaseState = state;
174             if (state == mCurrentDeviceState) {
175                 updateStatusConsumers(getCurrentStatus());
176             }
177         }
178     }
179 
180     @Override
onStateChanged(int state)181     public void onStateChanged(int state) {
182         synchronized (mLock) {
183             mCurrentDeviceState = state;
184             updateStatusConsumers(getCurrentStatus());
185         }
186     }
187 
188     @GuardedBy("mLock")
getCurrentStatus()189     private int getCurrentStatus() {
190         if (mRearDisplayState == INVALID_DEVICE_STATE) {
191             return WindowAreaComponent.STATUS_UNSUPPORTED;
192         }
193 
194         if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
195                 || isRearDisplayActive()) {
196             return WindowAreaComponent.STATUS_UNAVAILABLE;
197         }
198         return WindowAreaComponent.STATUS_AVAILABLE;
199     }
200 
201     /**
202      * Helper method to determine if a rear display session is currently active by checking
203      * if the current device configuration matches that of rear display. This would be true
204      * if there is a device override currently active (base state != current state) and the current
205      * state is that which corresponds to {@code mRearDisplayState}
206      * @return {@code true} if the device is in rear display mode and {@code false} if not
207      */
208     @GuardedBy("mLock")
isRearDisplayActive()209     private boolean isRearDisplayActive() {
210         return (mCurrentDeviceState != mCurrentDeviceBaseState) && (mCurrentDeviceState
211                 == mRearDisplayState);
212     }
213 
214     @GuardedBy("mLock")
updateStatusConsumers(@indowAreaStatus int windowAreaStatus)215     private void updateStatusConsumers(@WindowAreaStatus int windowAreaStatus) {
216         synchronized (mLock) {
217             for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) {
218                 mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus);
219             }
220         }
221     }
222 
223     /**
224      * Callback for the {@link DeviceStateRequest} to be notified of when the request has been
225      * activated or cancelled. This callback provides information to the client library
226      * on the status of the RearDisplay session through {@code mRearDisplaySessionCallback}
227      */
228     private class DeviceStateRequestCallbackAdapter implements DeviceStateRequest.Callback {
229 
230         private final Consumer<Integer> mRearDisplaySessionCallback;
231 
DeviceStateRequestCallbackAdapter(@onNull Consumer<Integer> callback)232         DeviceStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) {
233             mRearDisplaySessionCallback = callback;
234         }
235 
236         @Override
onRequestActivated(@onNull DeviceStateRequest request)237         public void onRequestActivated(@NonNull DeviceStateRequest request) {
238             synchronized (mLock) {
239                 if (request.equals(mDeviceStateRequest)) {
240                     mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE;
241                     mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
242                     updateStatusConsumers(getCurrentStatus());
243                 }
244             }
245         }
246 
247         @Override
onRequestCanceled(DeviceStateRequest request)248         public void onRequestCanceled(DeviceStateRequest request) {
249             synchronized (mLock) {
250                 if (request.equals(mDeviceStateRequest)) {
251                     mDeviceStateRequest = null;
252                 }
253                 mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
254                 mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
255                 updateStatusConsumers(getCurrentStatus());
256             }
257         }
258     }
259 
260     @Override
addRearDisplayPresentationStatusListener( @onNull Consumer<ExtensionWindowAreaStatus> consumer)261     public void addRearDisplayPresentationStatusListener(
262             @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {
263         throw new UnsupportedOperationException(
264                 "addRearDisplayPresentationStatusListener is not supported in API_VERSION=2");
265     }
266 
267     @Override
removeRearDisplayPresentationStatusListener( @onNull Consumer<ExtensionWindowAreaStatus> consumer)268     public void removeRearDisplayPresentationStatusListener(
269             @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {
270         throw new UnsupportedOperationException(
271                 "removeRearDisplayPresentationStatusListener is not supported in API_VERSION=2");
272     }
273 
274     @Override
startRearDisplayPresentationSession(@onNull Activity activity, @NonNull Consumer<@WindowAreaSessionState Integer> consumer)275     public void startRearDisplayPresentationSession(@NonNull Activity activity,
276             @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {
277         throw new UnsupportedOperationException(
278                 "startRearDisplayPresentationSession is not supported in API_VERSION=2");
279     }
280 
281     @Override
endRearDisplayPresentationSession()282     public void endRearDisplayPresentationSession() {
283         throw new UnsupportedOperationException(
284                 "endRearDisplayPresentationSession is not supported in API_VERSION=2");
285     }
286 
287     @Override
getRearDisplayPresentation()288     public ExtensionWindowAreaPresentation getRearDisplayPresentation() {
289         throw new UnsupportedOperationException(
290                 "getRearDisplayPresentation is not supported in API_VERSION=2");
291     }
292 }
293