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