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