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