blob: 41c052d95053964b35470193aa686f0f61857320 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wm;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.util.ArrayMap;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* Class that registers callbacks with the {@link DeviceStateManager} and responds to device
* changes.
*/
final class DeviceStateController implements DeviceStateManager.DeviceStateCallback {
// Used to synchronize WindowManager services call paths with DeviceStateManager's callbacks.
@NonNull
private final WindowManagerGlobalLock mWmLock;
@NonNull
private final DeviceStateManager mDeviceStateManager;
@NonNull
private final int[] mOpenDeviceStates;
@NonNull
private final int[] mHalfFoldedDeviceStates;
@NonNull
private final int[] mFoldedDeviceStates;
@NonNull
private final int[] mRearDisplayDeviceStates;
private final int mConcurrentDisplayDeviceState;
@NonNull
private final int[] mReverseRotationAroundZAxisStates;
@GuardedBy("mWmLock")
@NonNull
@VisibleForTesting
final Map<Consumer<DeviceState>, Executor> mDeviceStateCallbacks = new ArrayMap<>();
private final boolean mMatchBuiltInDisplayOrientationToDefaultDisplay;
@NonNull
private DeviceState mCurrentDeviceState = DeviceState.UNKNOWN;
private int mCurrentState;
public enum DeviceState {
UNKNOWN,
OPEN,
FOLDED,
HALF_FOLDED,
REAR,
CONCURRENT,
}
DeviceStateController(@NonNull Context context, @NonNull Handler handler,
@NonNull WindowManagerGlobalLock wmLock) {
mWmLock = wmLock;
mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
mOpenDeviceStates = context.getResources()
.getIntArray(R.array.config_openDeviceStates);
mHalfFoldedDeviceStates = context.getResources()
.getIntArray(R.array.config_halfFoldedDeviceStates);
mFoldedDeviceStates = context.getResources()
.getIntArray(R.array.config_foldedDeviceStates);
mRearDisplayDeviceStates = context.getResources()
.getIntArray(R.array.config_rearDisplayDeviceStates);
mConcurrentDisplayDeviceState = context.getResources()
.getInteger(R.integer.config_deviceStateConcurrentRearDisplay);
mReverseRotationAroundZAxisStates = context.getResources()
.getIntArray(R.array.config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis);
mMatchBuiltInDisplayOrientationToDefaultDisplay = context.getResources()
.getBoolean(R.bool
.config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay);
if (mDeviceStateManager != null) {
mDeviceStateManager.registerCallback(new HandlerExecutor(handler), this);
}
}
/**
* Registers a callback to be notified when the device state changes. Callers should always
* post the work onto their own worker thread to avoid holding the WindowManagerGlobalLock for
* an extended period of time.
*/
void registerDeviceStateCallback(@NonNull Consumer<DeviceState> callback,
@NonNull @CallbackExecutor Executor executor) {
synchronized (mWmLock) {
mDeviceStateCallbacks.put(callback, executor);
}
}
void unregisterDeviceStateCallback(@NonNull Consumer<DeviceState> callback) {
synchronized (mWmLock) {
mDeviceStateCallbacks.remove(callback);
}
}
/**
* @return true if the rotation direction on the Z axis should be reversed.
*/
boolean shouldReverseRotationDirectionAroundZAxis() {
return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState);
}
/**
* @return true if non-default built-in displays should match the default display's rotation.
*/
boolean shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay() {
// TODO(b/265991392): This should come from display_settings.xml once it's easier to
// extend with complex configurations.
return mMatchBuiltInDisplayOrientationToDefaultDisplay;
}
@Override
public void onStateChanged(int state) {
mCurrentState = state;
final DeviceState deviceState;
if (ArrayUtils.contains(mHalfFoldedDeviceStates, state)) {
deviceState = DeviceState.HALF_FOLDED;
} else if (ArrayUtils.contains(mFoldedDeviceStates, state)) {
deviceState = DeviceState.FOLDED;
} else if (ArrayUtils.contains(mRearDisplayDeviceStates, state)) {
deviceState = DeviceState.REAR;
} else if (ArrayUtils.contains(mOpenDeviceStates, state)) {
deviceState = DeviceState.OPEN;
} else if (state == mConcurrentDisplayDeviceState) {
deviceState = DeviceState.CONCURRENT;
} else {
deviceState = DeviceState.UNKNOWN;
}
if (mCurrentDeviceState == null || !mCurrentDeviceState.equals(deviceState)) {
mCurrentDeviceState = deviceState;
// Make a copy here because it's possible that the consumer tries to remove a callback
// while we're still iterating through the list, which would end up in a
// ConcurrentModificationException.
final List<Map.Entry<Consumer<DeviceState>, Executor>> entries = new ArrayList<>();
synchronized (mWmLock) {
for (Map.Entry<Consumer<DeviceState>, Executor> entry
: mDeviceStateCallbacks.entrySet()) {
entries.add(entry);
}
}
for (int i = 0; i < entries.size(); i++) {
Map.Entry<Consumer<DeviceState>, Executor> entry = entries.get(i);
entry.getValue().execute(() -> entry.getKey().accept(mCurrentDeviceState));
}
}
}
}