Add mic mute keyboard led. (1/2)
Change-Id: Ie3ddd04853bb3471bb60cefd037d5244733046c4
Test: Built, presubmit
diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java
index 18d0b09..163f9fa 100644
--- a/core/java/android/hardware/lights/Light.java
+++ b/core/java/android/hardware/lights/Light.java
@@ -65,6 +65,12 @@
public static final int LIGHT_TYPE_KEYBOARD_BACKLIGHT = 10003;
/**
+ * Type for keyboard microphone mute light.
+ * @hide
+ */
+ public static final int LIGHT_TYPE_KEYBOARD_MIC_MUTE = 10004;
+
+ /**
* Capability for lights that could adjust its LED brightness. If the capability is not present
* the LED can only be turned either on or off.
*/
@@ -92,6 +98,7 @@
LIGHT_TYPE_INPUT,
LIGHT_TYPE_PLAYER_ID,
LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+ LIGHT_TYPE_KEYBOARD_MIC_MUTE,
})
public @interface LightType {}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f32c11d..2ccc451 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -319,6 +319,9 @@
// Manages Sticky modifier state
private final StickyModifierStateController mStickyModifierStateController;
+ // Manages Keyboard microphone mute led
+ private final KeyboardLedController mKeyboardLedController;
+
// Manages Keyboard modifier keys remapping
private final KeyRemapper mKeyRemapper;
@@ -468,6 +471,8 @@
injector.getLooper(), injector.getUEventManager())
: new KeyboardBacklightControllerInterface() {};
mStickyModifierStateController = new StickyModifierStateController();
+ mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
+ mNative);
mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
mPointerIconCache = new PointerIconCache(mContext, mNative);
@@ -582,6 +587,7 @@
mKeyboardLayoutManager.systemRunning();
mBatteryController.systemRunning();
mKeyboardBacklightController.systemRunning();
+ mKeyboardLedController.systemRunning();
mKeyRemapper.systemRunning();
mPointerIconCache.systemRunning();
}
@@ -2164,6 +2170,7 @@
dumpDisplayInputPropertiesValues(ipw);
mBatteryController.dump(ipw);
mKeyboardBacklightController.dump(ipw);
+ mKeyboardLedController.dump(ipw);
}
private void dumpAssociations(IndentingPrintWriter pw) {
diff --git a/services/core/java/com/android/server/input/KeyboardLedController.java b/services/core/java/com/android/server/input/KeyboardLedController.java
new file mode 100644
index 0000000..5c404a2
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyboardLedController.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2024 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.input;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.SensorPrivacyManager.Sensors;
+import android.hardware.input.InputManager;
+import android.hardware.lights.Light;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+import android.util.SparseArray;
+import android.view.InputDevice;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * This class is used to control the light of keyboard.
+ */
+public final class KeyboardLedController implements InputManager.InputDeviceListener {
+
+ private static final String TAG = KeyboardLedController.class.getSimpleName();
+ private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
+ private static final int MSG_UPDATE_MIC_MUTE_LED_STATE = 2;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final NativeInputManagerService mNative;
+ private final SparseArray<InputDevice> mKeyboardsWithMicMuteLed = new SparseArray<>();
+ @NonNull
+ private InputManager mInputManager;
+ @NonNull
+ private SensorPrivacyManager mSensorPrivacyManager;
+ @NonNull
+ private AudioManager mAudioManager;
+ private BroadcastReceiver mMicrophoneMuteChangedIntentReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Message msg = Message.obtain(mHandler, MSG_UPDATE_MIC_MUTE_LED_STATE);
+ mHandler.sendMessage(msg);
+ }
+ };
+
+ KeyboardLedController(Context context, Looper looper,
+ NativeInputManagerService nativeService) {
+ mContext = context;
+ mNative = nativeService;
+ mHandler = new Handler(looper, this::handleMessage);
+ }
+
+ private boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_EXISTING_DEVICES:
+ for (int deviceId : (int[]) msg.obj) {
+ onInputDeviceAdded(deviceId);
+ }
+ return true;
+ case MSG_UPDATE_MIC_MUTE_LED_STATE:
+ updateMicMuteLedState();
+ return true;
+ }
+ return false;
+ }
+
+ private void updateMicMuteLedState() {
+ // We determine if the microphone is muted by querying both the hardware state of the
+ // microphone and the microphone sensor privacy hardware and sensor toggles
+ boolean isMicrophoneMute = mAudioManager.isMicrophoneMute()
+ || mSensorPrivacyManager.areAnySensorPrivacyTogglesEnabled(Sensors.MICROPHONE);
+ int color = isMicrophoneMute ? Color.WHITE : Color.TRANSPARENT;
+ for (int i = 0; i < mKeyboardsWithMicMuteLed.size(); i++) {
+ InputDevice device = mKeyboardsWithMicMuteLed.valueAt(i);
+ if (device != null) {
+ int deviceId = device.getId();
+ Light light = getKeyboardMicMuteLight(device);
+ if (light != null) {
+ mNative.setLightColor(deviceId, light.getId(), color);
+ }
+ }
+ }
+ }
+
+ private Light getKeyboardMicMuteLight(InputDevice device) {
+ for (Light light : device.getLightsManager().getLights()) {
+ if (light.getType() == Light.LIGHT_TYPE_KEYBOARD_MIC_MUTE
+ && light.hasBrightnessControl()) {
+ return light;
+ }
+ }
+ return null;
+ }
+
+ /** Called when the system is ready for us to start third-party code. */
+ public void systemRunning() {
+ mSensorPrivacyManager = Objects.requireNonNull(
+ mContext.getSystemService(SensorPrivacyManager.class));
+ mInputManager = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+ mAudioManager = Objects.requireNonNull(mContext.getSystemService(AudioManager.class));
+ mInputManager.registerInputDeviceListener(this, mHandler);
+ Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES,
+ mInputManager.getInputDeviceIds());
+ mHandler.sendMessage(msg);
+ mContext.registerReceiverAsUser(
+ mMicrophoneMuteChangedIntentReceiver,
+ UserHandle.ALL,
+ new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED),
+ null,
+ mHandler);
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ onInputDeviceChanged(deviceId);
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ mKeyboardsWithMicMuteLed.remove(deviceId);
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
+ if (inputDevice == null) {
+ return;
+ }
+ if (getKeyboardMicMuteLight(inputDevice) != null) {
+ mKeyboardsWithMicMuteLed.put(deviceId, inputDevice);
+ Message msg = Message.obtain(mHandler, MSG_UPDATE_MIC_MUTE_LED_STATE);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /** Dump the diagnostic information */
+ public void dump(PrintWriter pw) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.println(TAG + ": " + mKeyboardsWithMicMuteLed.size() + " keyboard mic mute lights");
+ ipw.increaseIndent();
+ for (int i = 0; i < mKeyboardsWithMicMuteLed.size(); i++) {
+ InputDevice inputDevice = mKeyboardsWithMicMuteLed.valueAt(i);
+ ipw.println(i + " " + inputDevice.getName() + ": "
+ + getKeyboardMicMuteLight(inputDevice).toString());
+ }
+ ipw.decreaseIndent();
+ }
+}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 88c47f3..5b4f47e 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -178,6 +178,7 @@
jfieldID lightTypeInput;
jfieldID lightTypePlayerId;
jfieldID lightTypeKeyboardBacklight;
+ jfieldID lightTypeKeyboardMicMute;
jfieldID lightCapabilityBrightness;
jfieldID lightCapabilityColorRgb;
} gLightClassInfo;
@@ -2444,6 +2445,9 @@
} else if (lightInfo.type == InputDeviceLightType::KEYBOARD_BACKLIGHT) {
jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
gLightClassInfo.lightTypeKeyboardBacklight);
+ } else if (lightInfo.type == InputDeviceLightType::KEYBOARD_MIC_MUTE) {
+ jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
+ gLightClassInfo.lightTypeKeyboardMicMute);
} else {
ALOGW("Unknown light type %d", lightInfo.type);
continue;
@@ -3176,6 +3180,8 @@
env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_PLAYER_ID", "I");
gLightClassInfo.lightTypeKeyboardBacklight =
env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_KEYBOARD_BACKLIGHT", "I");
+ gLightClassInfo.lightTypeKeyboardMicMute =
+ env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_KEYBOARD_MIC_MUTE", "I");
gLightClassInfo.lightCapabilityBrightness =
env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_BRIGHTNESS", "I");
gLightClassInfo.lightCapabilityColorRgb =
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index d17cd1f..a85d809 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -30,7 +30,7 @@
"androidx.test.rules",
"androidx.test.runner",
"androidx.test.uiautomator_uiautomator",
- "servicestests-utils",
+ "compatibility-device-util-axt",
"flag-junit",
"frameworks-base-testutils",
"hamcrest-library",
@@ -38,6 +38,7 @@
"mockito-target-minus-junit4",
"platform-test-annotations",
"services.core.unboosted",
+ "servicestests-utils",
"testables",
"testng",
"truth",
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index f6f766a..2c53f8c 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -33,6 +33,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.util.test.FakeSettingsProvider
import com.google.common.truth.Truth.assertThat
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -141,7 +142,9 @@
fun testInputSettingsUpdatedOnSystemRunning() {
verifyZeroInteractions(native)
- service.systemRunning()
+ runWithShellPermissionIdentity {
+ service.systemRunning()
+ }
verify(native).setPointerSpeed(anyInt())
verify(native).setTouchpadPointerSpeed(anyInt())