Request key maps from input manager service.

Instead of each application loading the KeyCharacterMap from
the file system, get them from the input manager service as
part of the InputDevice object.

Refactored InputManager to be a proper singleton instead of
having a bunch of static methods.

InputManager now maintains a cache of all InputDevice objects
that it has loaded.  Currently we never invalidate the cache
which can cause InputDevice to return stale motion ranges if
the device is reconfigured.  This will be fixed in a future change.

Added a fake InputDevice with ID -1 to represent the virtual keyboard.

Change-Id: If7a695839ad0972317a5aab89e9d1e42ace28eb7
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index c5d7b91..138a88f 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -325,9 +325,9 @@
                     return createDropBoxManager();
                 }});
 
-        registerService(INPUT_SERVICE, new ServiceFetcher() {
-                public Object createService(ContextImpl ctx) {
-                    return new InputManager(ctx);
+        registerService(INPUT_SERVICE, new StaticServiceFetcher() {
+                public Object createStaticService() {
+                    return InputManager.getInstance();
                 }});
 
         registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() {
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index f955713..75c6e11 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -883,7 +883,7 @@
         }
         KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState,
                 deviceId, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM, source);
-        InputManager.injectInputEvent(newEvent,
+        InputManager.getInstance().injectInputEvent(newEvent,
                 InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
     }
     
@@ -926,7 +926,8 @@
         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
             event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         }
-        InputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+        InputManager.getInstance().injectInputEvent(event,
+                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
     }
 
     /**
@@ -945,7 +946,8 @@
         if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
             event.setSource(InputDevice.SOURCE_TRACKBALL);
         }
-        InputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+        InputManager.getInstance().injectInputEvent(event,
+                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
     }
 
     /**
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index c2abce5..47e0d1e 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -16,6 +16,7 @@
 
 package android.hardware.input;
 
+import android.hardware.input.KeyboardLayout;
 import android.view.InputDevice;
 import android.view.InputEvent;
 
@@ -34,4 +35,11 @@
     // Injects an input event into the system.  To inject into windows owned by other
     // applications, the caller must have the INJECT_EVENTS permission.
     boolean injectInputEvent(in InputEvent ev, int mode);
+
+    // Keyboard layouts configuration.
+    KeyboardLayout[] getKeyboardLayouts();
+    KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
+    String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
+    void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 5ead1f4..3b3c237 100755
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,37 +16,18 @@
 
 package android.hardware.input;
 
-import com.android.internal.util.XmlUtils;
-
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.os.Bundle;
 import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.InputDevice;
 import android.view.InputEvent;
-import android.view.KeyCharacterMap;
-import android.view.KeyCharacterMap.UnavailableException;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
 
 /**
  * Provides information about input devices and available key layouts.
@@ -60,13 +41,10 @@
 public final class InputManager {
     private static final String TAG = "InputManager";
 
-    private static final IInputManager sIm;
+    private static InputManager sInstance;
 
-    private final Context mContext;
-
-    // Used to simulate a persistent data store.
-    // TODO: Replace with the real thing.
-    private static final HashMap<String, String> mFakeRegistry = new HashMap<String, String>();
+    private final IInputManager mIm;
+    private final SparseArray<InputDevice> mInputDevices = new SparseArray<InputDevice>();
 
     /**
      * Broadcast Action: Query available keyboard layouts.
@@ -169,14 +147,25 @@
      */
     public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;  // see InputDispatcher.h
 
-    static {
-        IBinder b = ServiceManager.getService(Context.INPUT_SERVICE);
-        sIm = IInputManager.Stub.asInterface(b);
+    private InputManager(IInputManager im) {
+        mIm = im;
     }
 
-    /** @hide */
-    public InputManager(Context context) {
-        mContext = context;
+    /**
+     * Gets an instance of the input manager.
+     *
+     * @return The input manager instance.
+     *
+     * @hide
+     */
+    public static InputManager getInstance() {
+        synchronized (InputManager.class) {
+            if (sInstance == null) {
+                IBinder b = ServiceManager.getService(Context.INPUT_SERVICE);
+                sInstance = new InputManager(IInputManager.Stub.asInterface(b));
+            }
+            return sInstance;
+        }
     }
 
     /**
@@ -188,18 +177,16 @@
      * </p>
      *
      * @return A list of all supported keyboard layouts.
+     *
      * @hide
      */
-    public List<KeyboardLayout> getKeyboardLayouts() {
-        ArrayList<KeyboardLayout> list = new ArrayList<KeyboardLayout>();
-
-        final PackageManager pm = mContext.getPackageManager();
-        Intent intent = new Intent(ACTION_QUERY_KEYBOARD_LAYOUTS);
-        for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent,
-                PackageManager.GET_META_DATA)) {
-            loadKeyboardLayouts(pm, resolveInfo.activityInfo, list, null);
+    public KeyboardLayout[] getKeyboardLayouts() {
+        try {
+            return mIm.getKeyboardLayouts();
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not get list of keyboard layout informations.", ex);
+            return new KeyboardLayout[0];
         }
-        return list;
     }
 
     /**
@@ -216,20 +203,10 @@
             throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
         }
 
-        KeyboardLayoutDescriptor d = parseKeyboardLayoutDescriptor(keyboardLayoutDescriptor);
-        if (d == null) {
-            return null;
-        }
-
-        final PackageManager pm = mContext.getPackageManager();
         try {
-            ActivityInfo receiver = pm.getReceiverInfo(
-                    new ComponentName(d.packageName, d.receiverName),
-                    PackageManager.GET_META_DATA);
-            return loadKeyboardLayouts(pm, receiver, null, d.keyboardLayoutName);
-        } catch (NameNotFoundException ex) {
-            Log.w(TAG, "Could not load keyboard layout '" + d.keyboardLayoutName
-                    + "' from receiver " + d.packageName + "/" + d.receiverName, ex);
+            return mIm.getKeyboardLayout(keyboardLayoutDescriptor);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not get keyboard layout information.", ex);
             return null;
         }
     }
@@ -243,12 +220,17 @@
      *
      * @hide
      */
-    public String getInputDeviceKeyboardLayoutDescriptor(String inputDeviceDescriptor) {
+    public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
         if (inputDeviceDescriptor == null) {
             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
         }
 
-        return mFakeRegistry.get(inputDeviceDescriptor);
+        try {
+            return mIm.getKeyboardLayoutForInputDevice(inputDeviceDescriptor);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not get keyboard layout for input device.", ex);
+            return null;
+        }
     }
 
     /**
@@ -264,92 +246,17 @@
      *
      * @hide
      */
-    public void setInputDeviceKeyboardLayoutDescriptor(String inputDeviceDescriptor,
+    public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
             String keyboardLayoutDescriptor) {
         if (inputDeviceDescriptor == null) {
             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
         }
 
-        mFakeRegistry.put(inputDeviceDescriptor, keyboardLayoutDescriptor);
-    }
-
-    private KeyboardLayout loadKeyboardLayouts(
-            PackageManager pm, ActivityInfo receiver,
-            List<KeyboardLayout> list, String keyboardName) {
-        Bundle metaData = receiver.metaData;
-        if (metaData == null) {
-            return null;
-        }
-
-        int configResId = metaData.getInt(META_DATA_KEYBOARD_LAYOUTS);
-        if (configResId == 0) {
-            Log.w(TAG, "Missing meta-data '" + META_DATA_KEYBOARD_LAYOUTS + "' on receiver "
-                    + receiver.packageName + "/" + receiver.name);
-            return null;
-        }
-
         try {
-            Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
-            XmlResourceParser parser = resources.getXml(configResId);
-            try {
-                XmlUtils.beginDocument(parser, "keyboard-layouts");
-
-                for (;;) {
-                    XmlUtils.nextElement(parser);
-                    String element = parser.getName();
-                    if (element == null) {
-                        break;
-                    }
-                    if (element.equals("keyboard-layout")) {
-                        TypedArray a = resources.obtainAttributes(
-                                parser, com.android.internal.R.styleable.KeyboardLayout);
-                        try {
-                            String name = a.getString(
-                                    com.android.internal.R.styleable.KeyboardLayout_name);
-                            String label = a.getString(
-                                    com.android.internal.R.styleable.KeyboardLayout_label);
-                            int kcmResId = a.getResourceId(
-                                     com.android.internal.R.styleable.KeyboardLayout_kcm, 0);
-                            if (name == null || label == null || kcmResId == 0) {
-                                Log.w(TAG, "Missing required 'name', 'label' or 'kcm' "
-                                        + "attributes in keyboard layout "
-                                        + "resource from receiver "
-                                        + receiver.packageName + "/" + receiver.name);
-                            } else {
-                                String descriptor = makeKeyboardLayoutDescriptor(
-                                        receiver.packageName, receiver.name, name);
-                                KeyboardLayout c = new KeyboardLayout(
-                                        descriptor, label, kcmResId);
-                                if (keyboardName != null && name.equals(keyboardName)) {
-                                    return c;
-                                }
-                                if (list != null) {
-                                    list.add(c);
-                                }
-                            }
-                        } finally {
-                            a.recycle();
-                        }
-                    } else {
-                        Log.w(TAG, "Skipping unrecognized element '" + element
-                                + "' in keyboard layout resource from receiver "
-                                + receiver.packageName + "/" + receiver.name);
-                    }
-                }
-            } finally {
-                parser.close();
-            }
-        } catch (Exception ex) {
-            Log.w(TAG, "Could not load keyboard layout resource from receiver "
-                    + receiver.packageName + "/" + receiver.name, ex);
-            return null;
+            mIm.setKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not set keyboard layout for input device.", ex);
         }
-        if (keyboardName != null) {
-            Log.w(TAG, "Could not load keyboard layout '" + keyboardName
-                    + "' from receiver " + receiver.packageName + "/" + receiver.name
-                    + " because it was not declared in the keyboard layout resource.");
-        }
-        return null;
     }
 
     /**
@@ -359,15 +266,16 @@
      * speed set by {@link #tryPointerSpeed}.
      * </p>
      *
+     * @param context The application context.
      * @return The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
      * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
      *
      * @hide
      */
-    public int getPointerSpeed() {
+    public int getPointerSpeed(Context context) {
         int speed = DEFAULT_POINTER_SPEED;
         try {
-            speed = Settings.System.getInt(mContext.getContentResolver(),
+            speed = Settings.System.getInt(context.getContentResolver(),
                     Settings.System.POINTER_SPEED);
         } catch (SettingNotFoundException snfe) {
         }
@@ -380,17 +288,18 @@
      * Requires {@link android.Manifest.permissions.WRITE_SETTINGS}.
      * </p>
      *
+     * @param context The application context.
      * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
      * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
      *
      * @hide
      */
-    public void setPointerSpeed(int speed) {
+    public void setPointerSpeed(Context context, int speed) {
         if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
             throw new IllegalArgumentException("speed out of range");
         }
 
-        Settings.System.putInt(mContext.getContentResolver(),
+        Settings.System.putInt(context.getContentResolver(),
                 Settings.System.POINTER_SPEED, speed);
     }
 
@@ -411,7 +320,7 @@
         }
 
         try {
-            sIm.tryPointerSpeed(speed);
+            mIm.tryPointerSpeed(speed);
         } catch (RemoteException ex) {
             Log.w(TAG, "Could not set temporary pointer speed.", ex);
         }
@@ -424,12 +333,27 @@
      *
      * @hide
      */
-    public static InputDevice getInputDevice(int id) {
+    public InputDevice getInputDevice(int id) {
+        synchronized (mInputDevices) {
+            InputDevice inputDevice = mInputDevices.get(id);
+            if (inputDevice != null) {
+                return inputDevice;
+            }
+        }
+        final InputDevice newInputDevice;
         try {
-            return sIm.getInputDevice(id);
+            newInputDevice = mIm.getInputDevice(id);
         } catch (RemoteException ex) {
             throw new RuntimeException("Could not get input device information.", ex);
         }
+        synchronized (mInputDevices) {
+            InputDevice inputDevice = mInputDevices.get(id);
+            if (inputDevice != null) {
+                return inputDevice;
+            }
+            mInputDevices.put(id, newInputDevice);
+            return newInputDevice;
+        }
     }
 
     /**
@@ -438,9 +362,9 @@
      *
      * @hide
      */
-    public static int[] getInputDeviceIds() {
+    public int[] getInputDeviceIds() {
         try {
-            return sIm.getInputDeviceIds();
+            return mIm.getInputDeviceIds();
         } catch (RemoteException ex) {
             throw new RuntimeException("Could not get input device ids.", ex);
         }
@@ -458,10 +382,10 @@
      *
      * @hide
      */
-    public static boolean[] deviceHasKeys(int[] keyCodes) {
+    public boolean[] deviceHasKeys(int[] keyCodes) {
         boolean[] ret = new boolean[keyCodes.length];
         try {
-            sIm.hasKeys(-1, InputDevice.SOURCE_ANY, keyCodes, ret);
+            mIm.hasKeys(-1, InputDevice.SOURCE_ANY, keyCodes, ret);
         } catch (RemoteException e) {
             // no fallback; just return the empty array
         }
@@ -489,7 +413,7 @@
      *
      * @hide
      */
-    public static boolean injectInputEvent(InputEvent event, int mode) {
+    public boolean injectInputEvent(InputEvent event, int mode) {
         if (event == null) {
             throw new IllegalArgumentException("event must not be null");
         }
@@ -500,152 +424,9 @@
         }
 
         try {
-            return sIm.injectInputEvent(event, mode);
+            return mIm.injectInputEvent(event, mode);
         } catch (RemoteException ex) {
             return false;
         }
     }
-
-    private static String makeKeyboardLayoutDescriptor(String packageName,
-            String receiverName, String keyboardName) {
-        return packageName + "/" + receiverName + "/" + keyboardName;
-    }
-
-    private static KeyboardLayoutDescriptor parseKeyboardLayoutDescriptor(String descriptor) {
-        int pos = descriptor.indexOf('/');
-        if (pos < 0 || pos + 1 == descriptor.length()) {
-            return null;
-        }
-        int pos2 = descriptor.indexOf('/', pos + 1);
-        if (pos2 < pos + 2 || pos2 + 1 == descriptor.length()) {
-            return null;
-        }
-
-        KeyboardLayoutDescriptor result = new KeyboardLayoutDescriptor();
-        result.packageName = descriptor.substring(0, pos);
-        result.receiverName = descriptor.substring(pos + 1, pos2);
-        result.keyboardLayoutName = descriptor.substring(pos2 + 1);
-        return result;
-    }
-
-    /**
-     * Describes a keyboard layout.
-     *
-     * @hide
-     */
-    public static final class KeyboardLayout implements Parcelable,
-            Comparable<KeyboardLayout> {
-        private final String mDescriptor;
-        private final String mLabel;
-        private final int mKeyCharacterMapResId;
-
-        private KeyCharacterMap mKeyCharacterMap;
-
-        public static final Parcelable.Creator<KeyboardLayout> CREATOR =
-                new Parcelable.Creator<KeyboardLayout>() {
-            public KeyboardLayout createFromParcel(Parcel source) {
-                return new KeyboardLayout(source);
-            }
-            public KeyboardLayout[] newArray(int size) {
-                return new KeyboardLayout[size];
-            }
-        };
-
-        private KeyboardLayout(String descriptor,
-                String label, int keyCharacterMapResId) {
-            mDescriptor = descriptor;
-            mLabel = label;
-            mKeyCharacterMapResId = keyCharacterMapResId;
-        }
-
-        private KeyboardLayout(Parcel source) {
-            mDescriptor = source.readString();
-            mLabel = source.readString();
-            mKeyCharacterMapResId = source.readInt();
-        }
-
-        /**
-         * Gets the keyboard layout descriptor, which can be used to retrieve
-         * the keyboard layout again later using
-         * {@link InputManager#getKeyboardLayout(String)}.
-         *
-         * @return The keyboard layout descriptor.
-         */
-        public String getDescriptor() {
-            return mDescriptor;
-        }
-
-        /**
-         * Gets the keyboard layout descriptive label to show in the user interface.
-         * @return The keyboard layout descriptive label.
-         */
-        public String getLabel() {
-            return mLabel;
-        }
-
-        /**
-         * Loads the key character map associated with the keyboard layout.
-         *
-         * @param pm The package manager.
-         * @return The key character map, or null if it could not be loaded for any reason.
-         */
-        public KeyCharacterMap loadKeyCharacterMap(PackageManager pm) {
-            if (pm == null) {
-                throw new IllegalArgumentException("pm must not be null");
-            }
-
-            if (mKeyCharacterMap == null) {
-                KeyboardLayoutDescriptor d = parseKeyboardLayoutDescriptor(mDescriptor);
-                if (d == null) {
-                    Log.e(TAG, "Could not load key character map '" + mDescriptor
-                            + "' because the descriptor could not be parsed.");
-                    return null;
-                }
-
-                CharSequence cs = pm.getText(d.packageName, mKeyCharacterMapResId, null);
-                if (cs == null) {
-                    Log.e(TAG, "Could not load key character map '" + mDescriptor
-                            + "' because its associated resource could not be loaded.");
-                    return null;
-                }
-
-                try {
-                    mKeyCharacterMap = KeyCharacterMap.load(cs);
-                } catch (UnavailableException ex) {
-                    Log.e(TAG, "Could not load key character map '" + mDescriptor
-                            + "' due to an error while parsing.", ex);
-                    return null;
-                }
-            }
-            return mKeyCharacterMap;
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeString(mDescriptor);
-            dest.writeString(mLabel);
-            dest.writeInt(mKeyCharacterMapResId);
-        }
-
-        @Override
-        public int compareTo(KeyboardLayout another) {
-            return mLabel.compareToIgnoreCase(another.mLabel);
-        }
-
-        @Override
-        public String toString() {
-            return mLabel;
-        }
-    }
-
-    private static final class KeyboardLayoutDescriptor {
-        public String packageName;
-        public String receiverName;
-        public String keyboardLayoutName;
-    }
 }
diff --git a/core/java/android/hardware/input/KeyboardLayout.aidl b/core/java/android/hardware/input/KeyboardLayout.aidl
new file mode 100644
index 0000000..226e384
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayout.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 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 KeyboardLayout;
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
new file mode 100644
index 0000000..e75a6dc
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2012 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Describes a keyboard layout.
+ *
+ * @hide
+ */
+public final class KeyboardLayout implements Parcelable,
+        Comparable<KeyboardLayout> {
+    private final String mDescriptor;
+    private final String mLabel;
+
+    public static final Parcelable.Creator<KeyboardLayout> CREATOR =
+            new Parcelable.Creator<KeyboardLayout>() {
+        public KeyboardLayout createFromParcel(Parcel source) {
+            return new KeyboardLayout(source);
+        }
+        public KeyboardLayout[] newArray(int size) {
+            return new KeyboardLayout[size];
+        }
+    };
+
+    public KeyboardLayout(String descriptor, String label) {
+        mDescriptor = descriptor;
+        mLabel = label;
+    }
+
+    private KeyboardLayout(Parcel source) {
+        mDescriptor = source.readString();
+        mLabel = source.readString();
+    }
+
+    /**
+     * Gets the keyboard layout descriptor, which can be used to retrieve
+     * the keyboard layout again later using
+     * {@link InputManager#getKeyboardLayout(String)}.
+     *
+     * @return The keyboard layout descriptor.
+     */
+    public String getDescriptor() {
+        return mDescriptor;
+    }
+
+    /**
+     * Gets the keyboard layout descriptive label to show in the user interface.
+     * @return The keyboard layout descriptive label.
+     */
+    public String getLabel() {
+        return mLabel;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mDescriptor);
+        dest.writeString(mLabel);
+    }
+
+    @Override
+    public int compareTo(KeyboardLayout another) {
+        return mLabel.compareToIgnoreCase(another.mLabel);
+    }
+
+    @Override
+    public String toString() {
+        return mLabel;
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 6f8d09b..75b2c746 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -39,13 +39,12 @@
  * </p>
  */
 public final class InputDevice implements Parcelable {
-    private int mId;
-    private String mName;
-    private String mDescriptor;
-    private int mSources;
-    private int mKeyboardType;
-    private String mKeyCharacterMapFile;
-
+    private final int mId;
+    private final String mName;
+    private final String mDescriptor;
+    private final int mSources;
+    private final int mKeyboardType;
+    private final KeyCharacterMap mKeyCharacterMap;
     private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
 
     /**
@@ -292,8 +291,43 @@
      */
     public static final int KEYBOARD_TYPE_ALPHABETIC = 2;
 
+    public static final Parcelable.Creator<InputDevice> CREATOR =
+            new Parcelable.Creator<InputDevice>() {
+        public InputDevice createFromParcel(Parcel in) {
+            return new InputDevice(in);
+        }
+        public InputDevice[] newArray(int size) {
+            return new InputDevice[size];
+        }
+    };
+
     // Called by native code.
-    private InputDevice() {
+    private InputDevice(int id, String name, String descriptor, int sources,
+            int keyboardType, KeyCharacterMap keyCharacterMap) {
+        mId = id;
+        mName = name;
+        mDescriptor = descriptor;
+        mSources = sources;
+        mKeyboardType = keyboardType;
+        mKeyCharacterMap = keyCharacterMap;
+    }
+
+    private InputDevice(Parcel in) {
+        mId = in.readInt();
+        mName = in.readString();
+        mDescriptor = in.readString();
+        mSources = in.readInt();
+        mKeyboardType = in.readInt();
+        mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in);
+
+        for (;;) {
+            int axis = in.readInt();
+            if (axis < 0) {
+                break;
+            }
+            addMotionRange(axis, in.readInt(),
+                    in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());
+        }
     }
 
     /**
@@ -302,7 +336,7 @@
      * @return The input device or null if not found.
      */
     public static InputDevice getDevice(int id) {
-        return InputManager.getInputDevice(id);
+        return InputManager.getInstance().getInputDevice(id);
     }
     
     /**
@@ -310,7 +344,7 @@
      * @return The input device ids.
      */
     public static int[] getDeviceIds() {
-        return InputManager.getInputDeviceIds();
+        return InputManager.getInstance().getInputDeviceIds();
     }
 
     /**
@@ -356,6 +390,22 @@
     }
 
     /**
+     * Returns true if the device is a virtual input device rather than a real one,
+     * such as the virtual keyboard (see {@link KeyCharacterMap#VIRTUAL_KEYBOARD}).
+     * <p>
+     * Virtual input devices are provided to implement system-level functionality
+     * and should not be seen or configured by users.
+     * </p>
+     *
+     * @return True if the device is virtual.
+     *
+     * @see KeyCharacterMap#VIRTUAL_KEYBOARD
+     */
+    public boolean isVirtual() {
+        return mId < 0;
+    }
+
+    /**
      * Gets the name of this input device.
      * @return The input device name.
      */
@@ -384,11 +434,7 @@
      * @return The key character map.
      */
     public KeyCharacterMap getKeyCharacterMap() {
-        return KeyCharacterMap.load(mId);
-    }
-
-    String getKeyCharacterMapFile() {
-        return mKeyCharacterMapFile;
+        return mKeyCharacterMap;
     }
 
     /**
@@ -453,6 +499,7 @@
         return mMotionRanges;
     }
 
+    // Called from native code.
     private void addMotionRange(int axis, int source,
             float min, float max, float flat, float fuzz) {
         mMotionRanges.add(new MotionRange(axis, source, min, max, flat, fuzz));
@@ -545,37 +592,6 @@
         }
     }
 
-    public static final Parcelable.Creator<InputDevice> CREATOR
-            = new Parcelable.Creator<InputDevice>() {
-        public InputDevice createFromParcel(Parcel in) {
-            InputDevice result = new InputDevice();
-            result.readFromParcel(in);
-            return result;
-        }
-        
-        public InputDevice[] newArray(int size) {
-            return new InputDevice[size];
-        }
-    };
-    
-    private void readFromParcel(Parcel in) {
-        mId = in.readInt();
-        mName = in.readString();
-        mDescriptor = in.readString();
-        mSources = in.readInt();
-        mKeyboardType = in.readInt();
-        mKeyCharacterMapFile = in.readString();
-
-        for (;;) {
-            int axis = in.readInt();
-            if (axis < 0) {
-                break;
-            }
-            addMotionRange(axis, in.readInt(),
-                    in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());
-        }
-    }
-
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mId);
@@ -583,7 +599,7 @@
         out.writeString(mDescriptor);
         out.writeInt(mSources);
         out.writeInt(mKeyboardType);
-        out.writeString(mKeyCharacterMapFile);
+        mKeyCharacterMap.writeToParcel(out, flags);
 
         final int numRanges = mMotionRanges.size();
         for (int i = 0; i < numRanges; i++) {
@@ -623,8 +639,6 @@
         }
         description.append("\n");
 
-        description.append("  Key Character Map: ").append(mKeyCharacterMapFile).append("\n");
-
         description.append("  Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
         appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
         appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index b03f086..3d165ea 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -16,18 +16,21 @@
 
 package android.view;
 
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.text.method.MetaKeyKeyListener;
 import android.util.AndroidRuntimeException;
 import android.util.SparseIntArray;
 import android.hardware.input.InputManager;
 import android.util.SparseArray;
+import android.view.InputDevice.MotionRange;
 
 import java.lang.Character;
 
 /**
  * Describes the keys provided by a keyboard device and their associated labels.
  */
-public class KeyCharacterMap {
+public class KeyCharacterMap implements Parcelable {
     /**
      * The id of the device's primary built in keyboard is always 0.
      *
@@ -134,12 +137,20 @@
      */
     public static final int MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED = 1;
 
-    private static SparseArray<KeyCharacterMap> sInstances = new SparseArray<KeyCharacterMap>();
+    public static final Parcelable.Creator<KeyCharacterMap> CREATOR =
+            new Parcelable.Creator<KeyCharacterMap>() {
+        public KeyCharacterMap createFromParcel(Parcel in) {
+            return new KeyCharacterMap(in);
+        }
+        public KeyCharacterMap[] newArray(int size) {
+            return new KeyCharacterMap[size];
+        }
+    };
 
-    private final int mDeviceId;
     private int mPtr;
 
-    private static native int nativeLoad(String file);
+    private static native int nativeReadFromParcel(Parcel in);
+    private static native void nativeWriteToParcel(int ptr, Parcel out);
     private static native void nativeDispose(int ptr);
 
     private static native char nativeGetCharacter(int ptr, int keyCode, int metaState);
@@ -149,10 +160,20 @@
     private static native char nativeGetMatch(int ptr, int keyCode, char[] chars, int metaState);
     private static native char nativeGetDisplayLabel(int ptr, int keyCode);
     private static native int nativeGetKeyboardType(int ptr);
-    private static native KeyEvent[] nativeGetEvents(int ptr, int deviceId, char[] chars);
+    private static native KeyEvent[] nativeGetEvents(int ptr, char[] chars);
 
-    private KeyCharacterMap(int deviceId, int ptr) {
-        mDeviceId = deviceId;
+    private KeyCharacterMap(Parcel in) {
+        if (in == null) {
+            throw new IllegalArgumentException("parcel must not be null");
+        }
+        mPtr = nativeReadFromParcel(in);
+        if (mPtr == 0) {
+            throw new RuntimeException("Could not read KeyCharacterMap from parcel.");
+        }
+    }
+
+    // Called from native
+    private KeyCharacterMap(int ptr) {
         mPtr = ptr;
     }
 
@@ -174,33 +195,16 @@
      * is missing from the system.
      */
     public static KeyCharacterMap load(int deviceId) {
-        synchronized (sInstances) {
-            KeyCharacterMap map = sInstances.get(deviceId);
-            if (map == null) {
-                String kcm = null;
-                if (deviceId != VIRTUAL_KEYBOARD) {
-                    InputDevice device = InputDevice.getDevice(deviceId);
-                    if (device != null) {
-                        kcm = device.getKeyCharacterMapFile();
-                    }
-                }
-                if (kcm == null || kcm.length() == 0) {
-                    kcm = "/system/usr/keychars/Virtual.kcm";
-                }
-                int ptr = nativeLoad(kcm); // might throw
-                map = new KeyCharacterMap(deviceId, ptr);
-                sInstances.put(deviceId, map);
+        final InputManager im = InputManager.getInstance();
+        InputDevice inputDevice = im.getInputDevice(deviceId);
+        if (inputDevice == null) {
+            inputDevice = im.getInputDevice(VIRTUAL_KEYBOARD);
+            if (inputDevice == null) {
+                throw new UnavailableException(
+                        "Could not load key character map for device " + deviceId);
             }
-            return map;
         }
-    }
-
-    /**
-     * TODO implement this
-     * @hide
-     */
-    public static KeyCharacterMap load(CharSequence contents) {
-        return null;
+        return inputDevice.getKeyCharacterMap();
     }
 
     /**
@@ -437,7 +441,7 @@
         if (chars == null) {
             throw new IllegalArgumentException("chars must not be null.");
         }
-        return nativeGetEvents(mPtr, mDeviceId, chars);
+        return nativeGetEvents(mPtr, chars);
     }
 
     /**
@@ -527,7 +531,7 @@
      * @return True if at least one attached keyboard supports the specified key code.
      */
     public static boolean deviceHasKey(int keyCode) {
-        return InputManager.deviceHasKeys(new int[] { keyCode })[0];
+        return InputManager.getInstance().deviceHasKeys(new int[] { keyCode })[0];
     }
 
     /**
@@ -541,7 +545,20 @@
      * at the same index in the key codes array.
      */
     public static boolean[] deviceHasKeys(int[] keyCodes) {
-        return InputManager.deviceHasKeys(keyCodes);
+        return InputManager.getInstance().deviceHasKeys(keyCodes);
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        if (out == null) {
+            throw new IllegalArgumentException("parcel must not be null");
+        }
+        nativeWriteToParcel(mPtr, out);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
     }
 
     /**
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ec0fe00..523b2d5 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -48,6 +48,7 @@
 	android_view_Surface.cpp \
 	android_view_TextureView.cpp \
 	android_view_InputChannel.cpp \
+	android_view_InputDevice.cpp \
 	android_view_InputEventReceiver.cpp \
 	android_view_KeyEvent.cpp \
 	android_view_KeyCharacterMap.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index e705c47..879b9d2 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -148,7 +148,6 @@
 extern int register_android_net_wifi_WifiManager(JNIEnv* env);
 extern int register_android_text_AndroidCharacter(JNIEnv *env);
 extern int register_android_text_AndroidBidi(JNIEnv *env);
-extern int register_android_text_KeyCharacterMap(JNIEnv *env);
 extern int register_android_opengl_classes(JNIEnv *env);
 extern int register_android_bluetooth_HeadsetBase(JNIEnv* env);
 extern int register_android_bluetooth_BluetoothAudioGateway(JNIEnv* env);
@@ -168,7 +167,9 @@
 extern int register_android_app_ActivityThread(JNIEnv *env);
 extern int register_android_app_NativeActivity(JNIEnv *env);
 extern int register_android_view_InputChannel(JNIEnv* env);
+extern int register_android_view_InputDevice(JNIEnv* env);
 extern int register_android_view_InputEventReceiver(JNIEnv* env);
+extern int register_android_view_KeyCharacterMap(JNIEnv *env);
 extern int register_android_view_KeyEvent(JNIEnv* env);
 extern int register_android_view_MotionEvent(JNIEnv* env);
 extern int register_android_view_PointerIcon(JNIEnv* env);
@@ -1082,7 +1083,8 @@
     REG_JNI(register_android_emoji_EmojiFactory),
     REG_JNI(register_android_text_AndroidCharacter),
     REG_JNI(register_android_text_AndroidBidi),
-    REG_JNI(register_android_text_KeyCharacterMap),
+    REG_JNI(register_android_view_InputDevice),
+    REG_JNI(register_android_view_KeyCharacterMap),
     REG_JNI(register_android_os_Process),
     REG_JNI(register_android_os_SystemProperties),
     REG_JNI(register_android_os_Binder),
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
new file mode 100644
index 0000000..d7d476a
--- /dev/null
+++ b/core/jni/android_view_InputDevice.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <androidfw/Input.h>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <nativehelper/jni.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <ScopedLocalRef.h>
+
+#include "android_view_InputDevice.h"
+#include "android_view_KeyCharacterMap.h"
+
+namespace android {
+
+static struct {
+    jclass clazz;
+
+    jmethodID ctor;
+    jmethodID addMotionRange;
+} gInputDeviceClassInfo;
+
+jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& deviceInfo) {
+    ScopedLocalRef<jstring> nameObj(env, env->NewStringUTF(deviceInfo.getName().string()));
+    if (!nameObj.get()) {
+        return NULL;
+    }
+
+    ScopedLocalRef<jstring> descriptorObj(env,
+            env->NewStringUTF(deviceInfo.getDescriptor().string()));
+    if (!descriptorObj.get()) {
+        return NULL;
+    }
+
+    ScopedLocalRef<jobject> kcmObj(env,
+            android_view_KeyCharacterMap_create(env, deviceInfo.getId(),
+            deviceInfo.getKeyCharacterMap()));
+    if (!kcmObj.get()) {
+        return NULL;
+    }
+
+    ScopedLocalRef<jobject> inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz,
+            gInputDeviceClassInfo.ctor, deviceInfo.getId(), nameObj.get(),
+            descriptorObj.get(), deviceInfo.getSources(), deviceInfo.getKeyboardType(),
+            kcmObj.get()));
+
+    const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
+    for (size_t i = 0; i < ranges.size(); i++) {
+        const InputDeviceInfo::MotionRange& range = ranges.itemAt(i);
+        env->CallVoidMethod(inputDeviceObj.get(), gInputDeviceClassInfo.addMotionRange,
+                range.axis, range.source, range.min, range.max, range.flat, range.fuzz);
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+    }
+
+    return env->NewLocalRef(inputDeviceObj.get());
+}
+
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className);
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+int register_android_view_InputDevice(JNIEnv* env)
+{
+    FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
+    gInputDeviceClassInfo.clazz = jclass(env->NewGlobalRef(gInputDeviceClassInfo.clazz));
+
+    GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz,
+            "<init>", "(ILjava/lang/String;Ljava/lang/String;IILandroid/view/KeyCharacterMap;)V");
+
+    GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz,
+            "addMotionRange", "(IIFFFF)V");
+
+    return 0;
+}
+
+}; // namespace android
diff --git a/core/jni/android_view_InputDevice.h b/core/jni/android_view_InputDevice.h
new file mode 100644
index 0000000..78651ba
--- /dev/null
+++ b/core/jni/android_view_InputDevice.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef _ANDROID_VIEW_INPUTDEVICE_H
+#define _ANDROID_VIEW_INPUTDEVICE_H
+
+#include "jni.h"
+
+#include <androidfw/InputDevice.h>
+
+namespace android {
+
+/* Creates an InputDevice object from the given information. */
+extern jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& deviceInfo);
+
+} // namespace android
+
+#endif // _ANDROID_VIEW_INPUTDEVICE_H
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index 7245d9d..3e56a89 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -14,19 +14,27 @@
  * limitations under the License.
 */
 
+#include <android_runtime/AndroidRuntime.h>
+
 #include <androidfw/KeyCharacterMap.h>
 #include <androidfw/Input.h>
+#include <binder/Parcel.h>
 
-#include <android_runtime/AndroidRuntime.h>
 #include <nativehelper/jni.h>
 #include <nativehelper/JNIHelp.h>
 
+#include "android_os_Parcel.h"
 #include "android_view_KeyEvent.h"
 
 namespace android {
 
 static struct {
     jclass clazz;
+    jmethodID ctor;
+} gKeyCharacterMapClassInfo;
+
+static struct {
+    jclass clazz;
 } gKeyEventClassInfo;
 
 static struct {
@@ -35,44 +43,87 @@
 } gFallbackActionClassInfo;
 
 
-static jint nativeLoad(JNIEnv *env, jobject clazz, jstring fileStr) {
-    const char* file = env->GetStringUTFChars(fileStr, NULL);
-
-    KeyCharacterMap* map;
-    status_t status = KeyCharacterMap::load(String8(file), &map);
-    jint result;
-    if (status) {
-        String8 msg;
-        msg.appendFormat("Could not load key character map '%s' due to error %d.  "
-                "Refer to the log for details.", file, status);
-        jniThrowException(env, "android/view/KeyCharacterMap$KeyCharacterMapUnavailableException",
-                msg.string());
-        result = 0;
-    } else {
-        result = reinterpret_cast<jint>(map);
+class NativeKeyCharacterMap {
+public:
+    NativeKeyCharacterMap(int32_t deviceId, const sp<KeyCharacterMap>& map) :
+        mDeviceId(deviceId), mMap(map) {
     }
 
-    env->ReleaseStringUTFChars(fileStr, file);
-    return result;
+    ~NativeKeyCharacterMap() {
+    }
+
+    inline int32_t getDeviceId() const {
+        return mDeviceId;
+    }
+
+    inline const sp<KeyCharacterMap>& getMap() const {
+        return mMap;
+    }
+
+private:
+    int32_t mDeviceId;
+    sp<KeyCharacterMap> mMap;
+};
+
+
+jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId,
+        const sp<KeyCharacterMap>& kcm) {
+    NativeKeyCharacterMap* map = new NativeKeyCharacterMap(deviceId,
+            kcm.get() ? kcm : KeyCharacterMap::empty());
+    if (!map) {
+        return NULL;
+    }
+
+    return env->NewObject(gKeyCharacterMapClassInfo.clazz, gKeyCharacterMapClassInfo.ctor,
+            reinterpret_cast<jint>(map));
+}
+
+static jint nativeReadFromParcel(JNIEnv *env, jobject clazz, jobject parcelObj) {
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    if (!parcel) {
+        return 0;
+    }
+
+    int32_t deviceId = parcel->readInt32();
+    if (parcel->errorCheck()) {
+        return 0;
+    }
+
+    sp<KeyCharacterMap> kcm = KeyCharacterMap::readFromParcel(parcel);
+    if (!kcm.get()) {
+        return 0;
+    }
+
+    NativeKeyCharacterMap* map = new NativeKeyCharacterMap(deviceId, kcm);
+    return reinterpret_cast<jint>(map);
+}
+
+static void nativeWriteToParcel(JNIEnv* env, jobject clazz, jint ptr, jobject parcelObj) {
+    NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    if (parcel) {
+        parcel->writeInt32(map->getDeviceId());
+        map->getMap()->writeToParcel(parcel);
+    }
 }
 
 static void nativeDispose(JNIEnv *env, jobject clazz, jint ptr) {
-    KeyCharacterMap* map = reinterpret_cast<KeyCharacterMap*>(ptr);
+    NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
     delete map;
 }
 
 static jchar nativeGetCharacter(JNIEnv *env, jobject clazz, jint ptr,
         jint keyCode, jint metaState) {
-    KeyCharacterMap* map = reinterpret_cast<KeyCharacterMap*>(ptr);
-    return map->getCharacter(keyCode, metaState);
+    NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
+    return map->getMap()->getCharacter(keyCode, metaState);
 }
 
 static jboolean nativeGetFallbackAction(JNIEnv *env, jobject clazz, jint ptr, jint keyCode,
         jint metaState, jobject fallbackActionObj) {
-    KeyCharacterMap* map = reinterpret_cast<KeyCharacterMap*>(ptr);
+    NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
     KeyCharacterMap::FallbackAction fallbackAction;
 
-    bool result = map->getFallbackAction(keyCode, metaState, &fallbackAction);
+    bool result = map->getMap()->getFallbackAction(keyCode, metaState, &fallbackAction);
     if (result) {
         env->SetIntField(fallbackActionObj, gFallbackActionClassInfo.keyCode,
                 fallbackAction.keyCode);
@@ -83,13 +134,13 @@
 }
 
 static jchar nativeGetNumber(JNIEnv *env, jobject clazz, jint ptr, jint keyCode) {
-    KeyCharacterMap* map = reinterpret_cast<KeyCharacterMap*>(ptr);
-    return map->getNumber(keyCode);
+    NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
+    return map->getMap()->getNumber(keyCode);
 }
 
 static jchar nativeGetMatch(JNIEnv *env, jobject clazz, jint ptr, jint keyCode,
         jcharArray charsArray, jint metaState) {
-    KeyCharacterMap* map = reinterpret_cast<KeyCharacterMap*>(ptr);
+    NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
 
     jsize numChars = env->GetArrayLength(charsArray);
     jchar* chars = static_cast<jchar*>(env->GetPrimitiveArrayCritical(charsArray, NULL));
@@ -97,25 +148,25 @@
         return 0;
     }
 
-    char16_t result = map->getMatch(keyCode, chars, size_t(numChars), metaState);
+    char16_t result = map->getMap()->getMatch(keyCode, chars, size_t(numChars), metaState);
 
     env->ReleasePrimitiveArrayCritical(charsArray, chars, JNI_ABORT);
     return result;
 }
 
 static jchar nativeGetDisplayLabel(JNIEnv *env, jobject clazz, jint ptr, jint keyCode) {
-    KeyCharacterMap* map = reinterpret_cast<KeyCharacterMap*>(ptr);
-    return map->getDisplayLabel(keyCode);
+    NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
+    return map->getMap()->getDisplayLabel(keyCode);
 }
 
 static jint nativeGetKeyboardType(JNIEnv *env, jobject clazz, jint ptr) {
-    KeyCharacterMap* map = reinterpret_cast<KeyCharacterMap*>(ptr);
-    return map->getKeyboardType();
+    NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
+    return map->getMap()->getKeyboardType();
 }
 
-static jobjectArray nativeGetEvents(JNIEnv *env, jobject clazz, jint ptr, jint deviceId,
+static jobjectArray nativeGetEvents(JNIEnv *env, jobject clazz, jint ptr,
         jcharArray charsArray) {
-    KeyCharacterMap* map = reinterpret_cast<KeyCharacterMap*>(ptr);
+    NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
 
     jchar* chars = env->GetCharArrayElements(charsArray, NULL);
     if (!chars) {
@@ -125,7 +176,7 @@
 
     Vector<KeyEvent> events;
     jobjectArray result = NULL;
-    if (map->getEvents(deviceId, chars, size_t(numChars), events)) {
+    if (map->getMap()->getEvents(map->getDeviceId(), chars, size_t(numChars), events)) {
         result = env->NewObjectArray(jsize(events.size()), gKeyEventClassInfo.clazz, NULL);
         if (result) {
             for (size_t i = 0; i < events.size(); i++) {
@@ -148,8 +199,10 @@
 
 static JNINativeMethod g_methods[] = {
     /* name, signature, funcPtr */
-    { "nativeLoad", "(Ljava/lang/String;)I",
-            (void*)nativeLoad },
+    { "nativeReadFromParcel", "(Landroid/os/Parcel;)I",
+            (void*)nativeReadFromParcel },
+    { "nativeWriteToParcel", "(ILandroid/os/Parcel;)V",
+            (void*)nativeWriteToParcel },
     { "nativeDispose", "(I)V",
             (void*)nativeDispose },
     { "nativeGetCharacter", "(III)C",
@@ -164,7 +217,7 @@
             (void*)nativeGetDisplayLabel },
     { "nativeGetKeyboardType", "(I)I",
             (void*)nativeGetKeyboardType },
-    { "nativeGetEvents", "(II[C)[Landroid/view/KeyEvent;",
+    { "nativeGetEvents", "(I[C)[Landroid/view/KeyEvent;",
             (void*)nativeGetEvents },
 };
 
@@ -172,12 +225,22 @@
         var = env->FindClass(className); \
         LOG_FATAL_IF(! var, "Unable to find class " className);
 
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
 #define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
         var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
         LOG_FATAL_IF(! var, "Unable to find field " fieldName);
 
-int register_android_text_KeyCharacterMap(JNIEnv* env)
+int register_android_view_KeyCharacterMap(JNIEnv* env)
 {
+    FIND_CLASS(gKeyCharacterMapClassInfo.clazz, "android/view/KeyCharacterMap");
+    gKeyCharacterMapClassInfo.clazz = jclass(env->NewGlobalRef(gKeyCharacterMapClassInfo.clazz));
+
+    GET_METHOD_ID(gKeyCharacterMapClassInfo.ctor, gKeyCharacterMapClassInfo.clazz,
+            "<init>", "(I)V");
+
     FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent");
     gKeyEventClassInfo.clazz = jclass(env->NewGlobalRef(gKeyEventClassInfo.clazz));
 
diff --git a/core/jni/android_view_KeyCharacterMap.h b/core/jni/android_view_KeyCharacterMap.h
new file mode 100644
index 0000000..04024f6
--- /dev/null
+++ b/core/jni/android_view_KeyCharacterMap.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef _ANDROID_VIEW_KEY_CHARACTER_MAP_H
+#define _ANDROID_VIEW_KEY_CHARACTER_MAP_H
+
+#include "jni.h"
+
+#include <androidfw/KeyCharacterMap.h>
+
+namespace android {
+
+/* Creates a KeyCharacterMap object from the given information. */
+extern jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId,
+        const sp<KeyCharacterMap>& map);
+
+} // namespace android
+
+#endif // _ANDROID_VIEW_KEY_CHARACTER_MAP_H
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3ee2377..f5c0f8f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1256,6 +1256,13 @@
         android:description="@string/permdesc_setPointerSpeed"
         android:protectionLevel="signature" />
 
+    <!-- Allows low-level access to setting the keyboard layout.
+         Not for use by normal applications. -->
+    <permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
+        android:label="@string/permlab_setKeyboardLayout"
+        android:description="@string/permdesc_setKeyboardLayout"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to install packages. -->
     <permission android:name="android.permission.INSTALL_PACKAGES"
         android:label="@string/permlab_installPackages"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 44f2ade..86cece4 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -768,6 +768,12 @@
         the mouse or trackpad pointer speed at any time. Should never be needed for
         normal apps.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] -->
+    <string name="permlab_setKeyboardLayout">change keyboard layout</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_setKeyboardLayout">Allows the app to change
+        the keyboard layout. Should never be needed for normal apps.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_signalPersistentProcesses">send Linux signals to apps</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->