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 com.android.server.wm;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.NonNull;
21 import android.content.Context;
22 import android.util.ArrayMap;
23 import android.util.Pair;
24 
25 import com.android.internal.R;
26 import com.android.internal.annotations.GuardedBy;
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.util.ArrayUtils;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.concurrent.Executor;
34 import java.util.function.Consumer;
35 
36 /**
37  * Class that listens for a callback from display manager and responds to device state
38  * changes.
39  */
40 final class DeviceStateController {
41 
42     // Used to synchronize WindowManager services call paths with DeviceStateManager's callbacks.
43     @NonNull
44     private final WindowManagerGlobalLock mWmLock;
45     @NonNull
46     private final int[] mOpenDeviceStates;
47     @NonNull
48     private final int[] mHalfFoldedDeviceStates;
49     @NonNull
50     private final int[] mFoldedDeviceStates;
51     @NonNull
52     private final int[] mRearDisplayDeviceStates;
53     private final int mConcurrentDisplayDeviceState;
54     @NonNull
55     private final int[] mReverseRotationAroundZAxisStates;
56     @GuardedBy("mWmLock")
57     @NonNull
58     @VisibleForTesting
59     final Map<Consumer<DeviceState>, Executor> mDeviceStateCallbacks = new ArrayMap<>();
60 
61     private final boolean mMatchBuiltInDisplayOrientationToDefaultDisplay;
62 
63     @NonNull
64     private DeviceState mCurrentDeviceState = DeviceState.UNKNOWN;
65     private int mCurrentState;
66 
67     public enum DeviceState {
68         UNKNOWN,
69         OPEN,
70         FOLDED,
71         HALF_FOLDED,
72         REAR,
73         CONCURRENT,
74     }
75 
DeviceStateController(@onNull Context context, @NonNull WindowManagerGlobalLock wmLock)76     DeviceStateController(@NonNull Context context, @NonNull WindowManagerGlobalLock wmLock) {
77         mWmLock = wmLock;
78 
79         mOpenDeviceStates = context.getResources()
80                 .getIntArray(R.array.config_openDeviceStates);
81         mHalfFoldedDeviceStates = context.getResources()
82                 .getIntArray(R.array.config_halfFoldedDeviceStates);
83         mFoldedDeviceStates = context.getResources()
84                 .getIntArray(R.array.config_foldedDeviceStates);
85         mRearDisplayDeviceStates = context.getResources()
86                 .getIntArray(R.array.config_rearDisplayDeviceStates);
87         mConcurrentDisplayDeviceState = context.getResources()
88                 .getInteger(R.integer.config_deviceStateConcurrentRearDisplay);
89         mReverseRotationAroundZAxisStates = context.getResources()
90                 .getIntArray(R.array.config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis);
91         mMatchBuiltInDisplayOrientationToDefaultDisplay = context.getResources()
92                 .getBoolean(R.bool
93                         .config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay);
94     }
95 
96     /**
97      * Registers a callback to be notified when the device state changes. Callers should always
98      * post the work onto their own worker thread to avoid holding the WindowManagerGlobalLock for
99      * an extended period of time.
100      */
registerDeviceStateCallback(@onNull Consumer<DeviceState> callback, @NonNull @CallbackExecutor Executor executor)101     void registerDeviceStateCallback(@NonNull Consumer<DeviceState> callback,
102             @NonNull @CallbackExecutor Executor executor) {
103         synchronized (mWmLock) {
104             mDeviceStateCallbacks.put(callback, executor);
105         }
106     }
107 
unregisterDeviceStateCallback(@onNull Consumer<DeviceState> callback)108     void unregisterDeviceStateCallback(@NonNull Consumer<DeviceState> callback) {
109         synchronized (mWmLock) {
110             mDeviceStateCallbacks.remove(callback);
111         }
112     }
113 
114     /**
115      * @return true if the rotation direction on the Z axis should be reversed for the default
116      * display.
117      */
shouldReverseRotationDirectionAroundZAxis(@onNull DisplayContent displayContent)118     boolean shouldReverseRotationDirectionAroundZAxis(@NonNull DisplayContent displayContent) {
119         if (!displayContent.isDefaultDisplay) {
120             return false;
121         }
122         return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState);
123     }
124 
125     /**
126      * @return true if non-default built-in displays should match the default display's rotation.
127      */
shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()128     boolean shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay() {
129         // TODO(b/265991392): This should come from display_settings.xml once it's easier to
130         //  extend with complex configurations.
131         return mMatchBuiltInDisplayOrientationToDefaultDisplay;
132     }
133 
134     /**
135      * This event is sent from DisplayManager just before the device state is applied to
136      * the displays. This is needed to make sure that we first receive this callback before
137      * any device state related display updates from the DisplayManager.
138      *
139      * The flow for this event is the following:
140      *  - {@link DeviceStateManager} sends event to {@link android.hardware.display.DisplayManager}
141      *  - {@link android.hardware.display.DisplayManager} sends it to {@link WindowManagerInternal}
142      *  - {@link WindowManagerInternal} eventually calls this method
143      *
144      * @param state device state as defined by {@link DeviceStateManager}
145      */
onDeviceStateReceivedByDisplayManager(int state)146     public void onDeviceStateReceivedByDisplayManager(int state) {
147         mCurrentState = state;
148 
149         final DeviceState deviceState;
150         if (ArrayUtils.contains(mHalfFoldedDeviceStates, state)) {
151             deviceState = DeviceState.HALF_FOLDED;
152         } else if (ArrayUtils.contains(mFoldedDeviceStates, state)) {
153             deviceState = DeviceState.FOLDED;
154         } else if (ArrayUtils.contains(mRearDisplayDeviceStates, state)) {
155             deviceState = DeviceState.REAR;
156         } else if (ArrayUtils.contains(mOpenDeviceStates, state)) {
157             deviceState = DeviceState.OPEN;
158         } else if (state == mConcurrentDisplayDeviceState) {
159             deviceState = DeviceState.CONCURRENT;
160         } else {
161             deviceState = DeviceState.UNKNOWN;
162         }
163 
164         if (mCurrentDeviceState == null || !mCurrentDeviceState.equals(deviceState)) {
165             mCurrentDeviceState = deviceState;
166 
167             // Make a copy here because it's possible that the consumer tries to remove a callback
168             // while we're still iterating through the list, which would end up in a
169             // ConcurrentModificationException. Note that cannot use a List<Map.Entry> because the
170             // entries are tied to the backing map. So, if a client removes a callback while
171             // we are notifying clients, we will get a NPE.
172             final List<Pair<Consumer<DeviceState>, Executor>> entries = copyDeviceStateCallbacks();
173 
174             for (int i = 0; i < entries.size(); i++) {
175                 final Pair<Consumer<DeviceState>, Executor> entry = entries.get(i);
176                 entry.second.execute(() -> entry.first.accept(deviceState));
177             }
178         }
179     }
180 
181     @VisibleForTesting
182     @NonNull
copyDeviceStateCallbacks()183     List<Pair<Consumer<DeviceState>, Executor>> copyDeviceStateCallbacks() {
184         final List<Pair<Consumer<DeviceState>, Executor>> entries = new ArrayList<>();
185 
186         synchronized (mWmLock) {
187             mDeviceStateCallbacks.forEach((deviceStateConsumer, executor) -> {
188                 entries.add(new Pair<>(deviceStateConsumer, executor));
189             });
190         }
191         return entries;
192     }
193 }
194