Add APIs to support per keyboard glyph maps

DD: go/pk_glyph_map
PRD doc: go/pk_glyph_map_prd
This CL only contains the shell of the APIs, the implementation
will follow in subsequent CLs.

Test: None
Bug: 345440920
Flag: com.android.hardware.input.keyboard_glyph_map
Change-Id: I09e32121b8ed97dd5267b06d1ad86bb242bba880
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 40d4fb6..1767d64 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -32,6 +32,7 @@
 import android.os.CombinedVibration;
 import android.hardware.input.IInputSensorEventListener;
 import android.hardware.input.InputSensorInfo;
+import android.hardware.input.KeyGlyphMap;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
 import android.os.IBinder;
@@ -236,4 +237,6 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)")
     void unregisterStickyModifierStateListener(IStickyModifierStateListener listener);
+
+    KeyGlyphMap getKeyGlyphMap(int deviceId);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 21c9002..5e46ae0 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,6 +19,7 @@
 import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
 import static com.android.input.flags.Flags.FLAG_DEVICE_ASSOCIATIONS;
 import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
+import static com.android.hardware.input.Flags.keyboardGlyphMap;
 
 import android.Manifest;
 import android.annotation.FlaggedApi;
@@ -898,6 +899,23 @@
     }
 
     /**
+     * Provides associated glyph map for the keyboard device (if available)
+     *
+     * @hide
+     */
+    @Nullable
+    public KeyGlyphMap getKeyGlyphMap(int deviceId) {
+        if (!keyboardGlyphMap()) {
+            return null;
+        }
+        try {
+            return mIm.getKeyGlyphMap(deviceId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Injects an input event into the event system, targeting windows owned by the provided uid.
      *
      * If a valid targetUid is provided, the system will only consider injecting the input event
diff --git a/core/java/android/hardware/input/KeyGlyphMap.aidl b/core/java/android/hardware/input/KeyGlyphMap.aidl
new file mode 100644
index 0000000..104f1e4
--- /dev/null
+++ b/core/java/android/hardware/input/KeyGlyphMap.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.hardware.input;
+
+parcelable KeyGlyphMap;
\ No newline at end of file
diff --git a/core/java/android/hardware/input/KeyGlyphMap.java b/core/java/android/hardware/input/KeyGlyphMap.java
new file mode 100644
index 0000000..49c47a2
--- /dev/null
+++ b/core/java/android/hardware/input/KeyGlyphMap.java
@@ -0,0 +1,187 @@
+/*
+ * 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 android.hardware.input;
+
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.KeyEvent;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class provides access to device specific key glyphs, modifier glyphs and device specific
+ * shortcuts and keys
+ *
+ * @hide
+ */
+public final class KeyGlyphMap implements Parcelable {
+    private static final String TAG = "KeyGlyphMap";
+
+    @NonNull
+    private final ComponentName mComponentName;
+    @NonNull
+    private final SparseIntArray mKeyGlyphs;
+    @NonNull
+    private final SparseIntArray mModifierGlyphs;
+    @NonNull
+    private final int[] mFunctionRowKeys;
+    @NonNull
+    private final Map<KeyCombination, Integer> mHardwareShortcuts;
+
+    public static final @NonNull Parcelable.Creator<KeyGlyphMap> CREATOR =
+            new Parcelable.Creator<>() {
+                public KeyGlyphMap createFromParcel(Parcel in) {
+                    return new KeyGlyphMap(in);
+                }
+
+                public KeyGlyphMap[] newArray(int size) {
+                    return new KeyGlyphMap[size];
+                }
+            };
+
+    public KeyGlyphMap(@NonNull ComponentName componentName,
+            @NonNull SparseIntArray keyGlyphs, @NonNull SparseIntArray modifierGlyphs,
+            @NonNull int[] functionRowKeys,
+            @NonNull Map<KeyCombination, Integer> hardwareShortcuts) {
+        mComponentName = componentName;
+        mKeyGlyphs = keyGlyphs;
+        mModifierGlyphs = modifierGlyphs;
+        mFunctionRowKeys = functionRowKeys;
+        mHardwareShortcuts = hardwareShortcuts;
+    }
+
+    public KeyGlyphMap(Parcel in) {
+        mComponentName = in.readParcelable(getClass().getClassLoader(), ComponentName.class);
+        mKeyGlyphs = in.readSparseIntArray();
+        mModifierGlyphs = in.readSparseIntArray();
+        mFunctionRowKeys = new int[in.readInt()];
+        in.readIntArray(mFunctionRowKeys);
+        mHardwareShortcuts = new HashMap<>(in.readInt());
+        in.readMap(mHardwareShortcuts, getClass().getClassLoader(), KeyCombination.class,
+                Integer.class);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mComponentName, 0);
+        dest.writeSparseIntArray(mKeyGlyphs);
+        dest.writeSparseIntArray(mModifierGlyphs);
+        dest.writeInt(mFunctionRowKeys.length);
+        dest.writeIntArray(mFunctionRowKeys);
+        dest.writeInt(mHardwareShortcuts.size());
+        dest.writeMap(mHardwareShortcuts);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Defines a key combination that includes a keycode and modifier state.
+     */
+    public record KeyCombination(int modifierState, int keycode) {}
+
+    /**
+     * Returns keycodes generated from the functional row defined for the keyboard.
+     */
+    public int[] getFunctionRowKeys() {
+        return mFunctionRowKeys;
+    }
+
+    /**
+     * Returns hardware defined shortcuts that are handled in the firmware of a particular
+     * keyboard (e.g. Fn+Backspace = Back, etc.)
+     *
+     * @return a map of (modifier + key) combinations to keycode mappings that are handled by the
+     * device hardware/firmware.
+     */
+    public Map<KeyCombination, Integer> getHardwareShortcuts() {
+        return mHardwareShortcuts;
+    }
+
+    /**
+     * Provides the drawable resource for the glyph for a keycode.
+     * Returns null if not available.
+     */
+    @Nullable
+    public Drawable getDrawableForKeycode(Context context, int keycode) {
+        return getDrawable(context, mKeyGlyphs.get(keycode, 0));
+    }
+
+    /**
+     * Provides the drawable resource for the glyph for a modifier key.
+     * Returns null if not available.
+     */
+    @Nullable
+    public Drawable getDrawableForModifier(Context context, int modifierKeycode) {
+        int modifier = switch (modifierKeycode) {
+            case KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT -> KeyEvent.META_META_ON;
+            case KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT -> KeyEvent.META_CTRL_ON;
+            case KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT -> KeyEvent.META_ALT_ON;
+            case KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT ->
+                    KeyEvent.META_SHIFT_ON;
+            case KeyEvent.KEYCODE_FUNCTION -> KeyEvent.META_FUNCTION_ON;
+            case KeyEvent.KEYCODE_SYM -> KeyEvent.META_SYM_ON;
+            case KeyEvent.KEYCODE_CAPS_LOCK -> KeyEvent.META_CAPS_LOCK_ON;
+            case KeyEvent.KEYCODE_NUM_LOCK -> KeyEvent.META_NUM_LOCK_ON;
+            case KeyEvent.KEYCODE_SCROLL_LOCK -> KeyEvent.META_SCROLL_LOCK_ON;
+            default -> 0;
+        };
+        return getDrawable(context, mModifierGlyphs.get(modifier, 0));
+    }
+
+    @Nullable
+    private Drawable getDrawable(Context context, @DrawableRes int drawableRes) {
+        PackageManager pm = context.getPackageManager();
+        try {
+            ActivityInfo receiver = pm.getReceiverInfo(mComponentName,
+                    PackageManager.GET_META_DATA
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+            Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
+            return resources.getDrawable(drawableRes, null);
+        } catch (PackageManager.NameNotFoundException ignored) {
+            Log.e(TAG, "Package name not found for " + mComponentName);
+        }
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return "KeyGlyphMap{"
+                + "mComponentName=" + mComponentName
+                + ", mKeyGlyphs=" + mKeyGlyphs
+                + ", mModifierGlyphs=" + mModifierGlyphs
+                + ", mFunctionRowKeys=" + Arrays.toString(mFunctionRowKeys)
+                + ", mHardwareShortcuts=" + mHardwareShortcuts
+                + '}';
+    }
+}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index ed536ce..acd0d00f 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -53,4 +53,11 @@
     name: "touchpad_tap_dragging"
     description: "Offers a setting to enable touchpad tap dragging"
     bug: "321978150"
-}
\ No newline at end of file
+}
+
+flag {
+    namespace: "input_native"
+    name: "keyboard_glyph_map"
+    description: "Allows system to provide keyboard specific key drawables and shortcuts via config files"
+    bug: "345440920"
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index e5dbce9..b23546c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -53,6 +53,7 @@
 import android.hardware.input.InputManager;
 import android.hardware.input.InputSensorInfo;
 import android.hardware.input.InputSettings;
+import android.hardware.input.KeyGlyphMap;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.KeyboardLayoutSelectionResult;
 import android.hardware.input.TouchCalibration;
@@ -1208,6 +1209,12 @@
                 imeInfo, imeSubtype);
     }
 
+    @Override // Binder call
+    public KeyGlyphMap getKeyGlyphMap(int deviceId) {
+        // TODO(b/345440920): Implementation
+        return null;
+    }
+
     public void setFocusedApplication(int displayId, InputApplicationHandle application) {
         mNative.setFocusedApplication(displayId, application);
     }