Merge "Check for existing dimming before stop dimming."
diff --git a/api/current.txt b/api/current.txt
index 180ac91..35ae56b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -102,6 +102,7 @@
     field public static final java.lang.String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
     field public static final java.lang.String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
     field public static final java.lang.String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
+    field public static final java.lang.String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
     field public static final java.lang.String SET_ORIENTATION = "android.permission.SET_ORIENTATION";
     field public static final java.lang.String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
     field public static final deprecated java.lang.String SET_PREFERRED_APPLICATIONS = "android.permission.SET_PREFERRED_APPLICATIONS";
@@ -2881,14 +2882,10 @@
   public class ActivityOptions {
     method public void join(android.app.ActivityOptions);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
-    method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int, android.app.ActivityOptions.OnAnimationStartedListener);
+    method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
     method public android.os.Bundle toBundle();
   }
 
-  public static abstract interface ActivityOptions.OnAnimationStartedListener {
-    method public abstract void onAnimationStarted();
-  }
-
   public class AlarmManager {
     method public void cancel(android.app.PendingIntent);
     method public void set(int, long, android.app.PendingIntent);
@@ -22272,6 +22269,7 @@
     method public java.util.List<android.view.InputDevice.MotionRange> getMotionRanges();
     method public java.lang.String getName();
     method public int getSources();
+    method public boolean isVirtual();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int KEYBOARD_TYPE_ALPHABETIC = 2; // 0x2
@@ -22331,7 +22329,8 @@
     method public abstract void onInputQueueDestroyed(android.view.InputQueue);
   }
 
-  public class KeyCharacterMap {
+  public class KeyCharacterMap implements android.os.Parcelable {
+    method public int describeContents();
     method public static boolean deviceHasKey(int);
     method public static boolean[] deviceHasKeys(int[]);
     method public int get(int, int);
@@ -22346,10 +22345,12 @@
     method public char getNumber(int);
     method public boolean isPrintingKey(int);
     method public static android.view.KeyCharacterMap load(int);
+    method public void writeToParcel(android.os.Parcel, int);
     field public static final int ALPHA = 3; // 0x3
     field public static final deprecated int BUILT_IN_KEYBOARD = 0; // 0x0
     field public static final int COMBINING_ACCENT = -2147483648; // 0x80000000
     field public static final int COMBINING_ACCENT_MASK = 2147483647; // 0x7fffffff
+    field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int FULL = 4; // 0x4
     field public static final char HEX_INPUT = 61184; // 0xef00 '\uef00'
     field public static final int MODIFIER_BEHAVIOR_CHORDED = 0; // 0x0
diff --git a/cmds/input/src/com/android/commands/input/Input.java b/cmds/input/src/com/android/commands/input/Input.java
index 3037881..fcf3c7e 100755
--- a/cmds/input/src/com/android/commands/input/Input.java
+++ b/cmds/input/src/com/android/commands/input/Input.java
@@ -145,13 +145,15 @@
 
     private void injectKeyEvent(KeyEvent event) {
         Log.i(TAG, "InjectKeyEvent: " + event);
-        InputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+        InputManager.getInstance().injectInputEvent(event,
+                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
     }
 
     private void injectPointerEvent(MotionEvent event) {
         event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         Log.i("Input", "InjectPointerEvent: " + event);
-        InputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+        InputManager.getInstance().injectInputEvent(event,
+                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
     }
 
     private static final float lerp(float a, float b, float alpha) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 7207e29..227900e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -55,6 +55,7 @@
 import android.util.AttributeSet;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.ActionMode;
 import android.view.ContextMenu;
@@ -642,6 +643,7 @@
         Window.Callback, KeyEvent.Callback,
         OnCreateContextMenuListener, ComponentCallbacks2 {
     private static final String TAG = "Activity";
+    private static final boolean DEBUG_LIFECYCLE = false;
 
     /** Standard activity result: operation canceled. */
     public static final int RESULT_CANCELED    = 0;
@@ -865,6 +867,7 @@
      * @see #onPostCreate
      */
     protected void onCreate(Bundle savedInstanceState) {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
         if (mLastNonConfigurationInstances != null) {
             mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
         }
@@ -1013,6 +1016,7 @@
      * @see #onResume
      */
     protected void onStart() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
         mCalled = true;
         
         if (!mLoadersStarted) {
@@ -1073,6 +1077,7 @@
      * @see #onPause
      */
     protected void onResume() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
         getApplication().dispatchActivityResumed(this);
         mCalled = true;
     }
@@ -1131,6 +1136,7 @@
     final void performSaveInstanceState(Bundle outState) {
         onSaveInstanceState(outState);
         saveManagedDialogs(outState);
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
     }
 
     /**
@@ -1261,6 +1267,7 @@
      * @see #onStop
      */
     protected void onPause() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);
         getApplication().dispatchActivityPaused(this);
         mCalled = true;
     }
@@ -1347,6 +1354,7 @@
      * @see #onDestroy
      */
     protected void onStop() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
         getApplication().dispatchActivityStopped(this);
         mCalled = true;
@@ -1381,6 +1389,7 @@
      * @see #isFinishing
      */
     protected void onDestroy() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
         mCalled = true;
 
         // dismiss any dialogs we are managing.
@@ -1432,6 +1441,7 @@
      * @param newConfig The new device configuration.
      */
     public void onConfigurationChanged(Configuration newConfig) {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onConfigurationChanged " + this + ": " + newConfig);
         mCalled = true;
 
         mFragments.dispatchConfigurationChanged(newConfig);
@@ -1613,11 +1623,13 @@
     }
 
     public void onLowMemory() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this);
         mCalled = true;
         mFragments.dispatchLowMemory();
     }
 
     public void onTrimMemory(int level) {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level);
         mCalled = true;
         mFragments.dispatchTrimMemory(level);
     }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index c637df0..c3cceaf 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -21,7 +21,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IRemoteCallback;
-import android.os.Message;
 import android.os.RemoteException;
 import android.view.View;
 
@@ -121,6 +120,7 @@
     /**
      * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation}
      * to find out when the given animation has started running.
+     * @hide
      */
     public interface OnAnimationStartedListener {
         void onAnimationStarted();
@@ -137,11 +137,31 @@
      * of the animation.
      * @param startX The x starting location of the bitmap, in screen coordiantes.
      * @param startY The y starting location of the bitmap, in screen coordinates.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     */
+    public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
+            Bitmap thumbnail, int startX, int startY) {
+        return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, null);
+    }
+
+    /**
+     * Create an ActivityOptions specifying an animation where a thumbnail
+     * is scaled from a given position to the new activity window that is
+     * being started.
+     *
+     * @param source The View that this thumbnail is animating from.  This
+     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+     * @param thumbnail The bitmap that will be shown as the initial thumbnail
+     * of the animation.
+     * @param startX The x starting location of the bitmap, in screen coordiantes.
+     * @param startY The y starting location of the bitmap, in screen coordinates.
      * @param listener Optional OnAnimationStartedListener to find out when the
      * requested animation has started running.  If for some reason the animation
      * is not executed, the callback will happen immediately.
      * @return Returns a new ActivityOptions object that you can use to
      * supply these options as the options Bundle when starting an activity.
+     * @hide
      */
     public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
             Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 98c4e10..1489b2c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -138,6 +138,7 @@
     private static final boolean DEBUG_BACKUP = true;
     private static final boolean DEBUG_CONFIGURATION = false;
     private static final boolean DEBUG_SERVICE = false;
+    private static final boolean DEBUG_MEMORY_TRIM = false;
     private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
     private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
     private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
@@ -2779,9 +2780,21 @@
         performStopActivityInner(r, null, false, saveState);
     }
 
-    private static class StopInfo {
+    private static class StopInfo implements Runnable {
+        ActivityClientRecord activity;
+        Bundle state;
         Bitmap thumbnail;
         CharSequence description;
+
+        @Override public void run() {
+            // Tell activity manager we have been stopped.
+            try {
+                if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + activity);
+                ActivityManagerNative.getDefault().activityStopped(
+                    activity.token, state, thumbnail, description);
+            } catch (RemoteException ex) {
+            }
+        }
     }
 
     private static final class ProviderRefCount {
@@ -2911,12 +2924,14 @@
             QueuedWork.waitToFinish();
         }
 
-        // Tell activity manager we have been stopped.
-        try {
-            ActivityManagerNative.getDefault().activityStopped(
-                r.token, r.state, info.thumbnail, info.description);
-        } catch (RemoteException ex) {
-        }
+        // Schedule the call to tell the activity manager we have
+        // stopped.  We don't do this immediately, because we want to
+        // have a chance for any other pending work (in particular memory
+        // trim requests) to complete before you tell the activity
+        // manager to proceed and allow us to go fully into the background.
+        info.activity = r;
+        info.state = r.state;
+        mH.post(info);
     }
 
     final void performRestartActivity(IBinder token) {
@@ -3749,6 +3764,7 @@
     }
 
     final void handleTrimMemory(int level) {
+        if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
         WindowManagerImpl.getDefault().trimMemory(level);
         ArrayList<ComponentCallbacks2> callbacks;
 
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 6f8d09b7..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/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index a45a87e..52bd860 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -432,23 +432,24 @@
      */
     public void trimMemory(int level) {
         if (HardwareRenderer.isAvailable()) {
-            // On low and medium end gfx devices
-            if (!ActivityManager.isHighEndGfx(getDefaultDisplay())) {
-                if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
-                    // Destroy all hardware surfaces and resources associated to
-                    // known windows
-                    synchronized (this) {
-                        if (mViews == null) return;
-                        int count = mViews.length;
-                        for (int i = 0; i < count; i++) {
-                            mRoots[i].terminateHardwareResources();
-                        }
+            // On low-end gfx devices we trim when memory is moderate;
+            // on high-end devices we do this when low.
+            if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE
+                    || (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE
+                            && !ActivityManager.isHighEndGfx(getDefaultDisplay()))) {
+                // Destroy all hardware surfaces and resources associated to
+                // known windows
+                synchronized (this) {
+                    if (mViews == null) return;
+                    int count = mViews.length;
+                    for (int i = 0; i < count; i++) {
+                        mRoots[i].terminateHardwareResources();
                     }
-                    // Force a full memory flush
-                    HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
-                    mNeedsEglTerminate = true;
-                    return;
                 }
+                // Force a full memory flush
+                HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
+                mNeedsEglTerminate = true;
+                return;
             }
             HardwareRenderer.trimMemory(level);
         }
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index f496c4e..88e1eb7 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -886,7 +886,9 @@
             // the existing GL resources for the html5 video will be destroyed
             // at native side.
             // Here we just need to clean up the Surface Texture which is static.
-            HTML5VideoInline.cleanupSurfaceTexture();
+            if (level >= TRIM_MEMORY_UI_HIDDEN) {
+                HTML5VideoInline.cleanupSurfaceTexture();
+            }
             WebViewClassic.nativeOnTrimMemory(level);
         }
 
@@ -2867,7 +2869,7 @@
                 if (rect.width() < mFocusedNode.mHitTestSlop) {
                     // ignore bounding boxes that are too small
                     continue;
-                } else if (left != NO_LEFTEDGE && rect.width() > readingWidth) {
+                } else if (rect.width() > readingWidth) {
                     // stop when bounding box doesn't fit the screen width
                     // at reading scale
                     break;
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 3bc4f7f..0b49404 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -37,9 +37,11 @@
 import android.graphics.drawable.shapes.Shape;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.SystemClock;
 import android.util.AttributeSet;
-import android.view.Choreographer;
+import android.util.Pool;
+import android.util.Poolable;
+import android.util.PoolableManager;
+import android.util.Pools;
 import android.view.Gravity;
 import android.view.RemotableViewMethod;
 import android.view.View;
@@ -55,6 +57,8 @@
 import android.view.animation.Transformation;
 import android.widget.RemoteViews.RemoteView;
 
+import java.util.ArrayList;
+
 
 /**
  * <p>
@@ -218,6 +222,10 @@
     private boolean mShouldStartAnimationDrawable;
 
     private boolean mInDrawing;
+    private boolean mAttached;
+    private boolean mRefreshIsPosted;
+
+    private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();
 
     private AccessibilityEventSender mAccessibilityEventSender;
 
@@ -558,29 +566,76 @@
     }
 
     private class RefreshProgressRunnable implements Runnable {
-
-        private int mId;
-        private int mProgress;
-        private boolean mFromUser;
-        
-        RefreshProgressRunnable(int id, int progress, boolean fromUser) {
-            mId = id;
-            mProgress = progress;
-            mFromUser = fromUser;
-        }
-        
         public void run() {
-            doRefreshProgress(mId, mProgress, mFromUser, true);
-            // Put ourselves back in the cache when we are done
-            mRefreshProgressRunnable = this;
+            synchronized (ProgressBar.this) {
+                final int count = mRefreshData.size();
+                for (int i = 0; i < count; i++) {
+                    final RefreshData rd = mRefreshData.get(i);
+                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
+                    rd.recycle();
+                }
+                mRefreshData.clear();
+                mRefreshIsPosted = false;
+            }
+        }
+    }
+
+    private static class RefreshData implements Poolable<RefreshData> {
+        public int id;
+        public int progress;
+        public boolean fromUser;
+        
+        private RefreshData mNext;
+        private boolean mIsPooled;
+        
+        private static final int POOL_MAX = 24;
+        private static final Pool<RefreshData> sPool = Pools.synchronizedPool(
+                Pools.finitePool(new PoolableManager<RefreshData>() {
+                    @Override
+                    public RefreshData newInstance() {
+                        return new RefreshData();
+                    }
+
+                    @Override
+                    public void onAcquired(RefreshData element) {
+                    }
+
+                    @Override
+                    public void onReleased(RefreshData element) {
+                    }
+                }, POOL_MAX));
+
+        public static RefreshData obtain(int id, int progress, boolean fromUser) {
+            RefreshData rd = sPool.acquire();
+            rd.id = id;
+            rd.progress = progress;
+            rd.fromUser = fromUser;
+            return rd;
         }
         
-        public void setup(int id, int progress, boolean fromUser) {
-            mId = id;
-            mProgress = progress;
-            mFromUser = fromUser;
+        public void recycle() {
+            sPool.release(this);
         }
-        
+
+        @Override
+        public void setNextPoolable(RefreshData element) {
+            mNext = element;
+        }
+
+        @Override
+        public RefreshData getNextPoolable() {
+            return mNext;
+        }
+
+        @Override
+        public boolean isPooled() {
+            return mIsPooled;
+        }
+
+        @Override
+        public void setPooled(boolean isPooled) {
+            mIsPooled = isPooled;
+        }
     }
     
     private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
@@ -619,14 +674,16 @@
             if (mRefreshProgressRunnable != null) {
                 // Use cached RefreshProgressRunnable if available
                 r = mRefreshProgressRunnable;
-                // Uncache it
-                mRefreshProgressRunnable = null;
-                r.setup(id, progress, fromUser);
             } else {
                 // Make a new one
-                r = new RefreshProgressRunnable(id, progress, fromUser);
+                r = new RefreshProgressRunnable();
             }
-            post(r);
+            final RefreshData rd = RefreshData.obtain(id, progress, fromUser);
+            mRefreshData.add(rd);
+            if (mAttached && !mRefreshIsPosted) {
+                post(r);
+                mRefreshIsPosted = true;
+            }
         }
     }
     
@@ -1092,6 +1149,18 @@
         if (mIndeterminate) {
             startAnimation();
         }
+        if (mRefreshData != null) {
+            synchronized (this) {
+                final int count = mRefreshData.size();
+                for (int i = 0; i < count; i++) {
+                    final RefreshData rd = mRefreshData.get(i);
+                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
+                    rd.recycle();
+                }
+                mRefreshData.clear();
+            }
+        }
+        mAttached = true;
     }
 
     @Override
@@ -1099,7 +1168,10 @@
         if (mIndeterminate) {
             stopAnimation();
         }
-        if(mRefreshProgressRunnable != null) {
+        if (mRefreshProgressRunnable != null) {
+            removeCallbacks(mRefreshProgressRunnable);
+        }
+        if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
             removeCallbacks(mRefreshProgressRunnable);
         }
         if (mAccessibilityEventSender != null) {
@@ -1108,6 +1180,7 @@
         // This should come after stopAnimation(), otherwise an invalidate message remains in the
         // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
         super.onDetachedFromWindow();
+        mAttached = false;
     }
 
     @Override
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/layout/input_method_switch_dialog_title.xml b/core/res/res/layout/input_method_switch_dialog_title.xml
new file mode 100644
index 0000000..7032bd3
--- /dev/null
+++ b/core/res/res/layout/input_method_switch_dialog_title.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="9dip"
+        android:layout_marginLeft="20dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginTop="6dip"
+        android:gravity="center_vertical"
+        android:orientation="vertical" >
+
+        <com.android.internal.widget.DialogTitle
+            android:id="@+id/alertTitle"
+            style="@android:style/DialogWindowTitle.Holo"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:singleLine="true"
+            android:text="@string/select_input_method" />
+    </LinearLayout>
+
+    <!-- Hard keyboard switch -->
+
+    <LinearLayout
+        android:id="@+id/hard_keyboard_section"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" >
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="2dip"
+            android:background="@android:color/holo_blue_light" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal" >
+
+            <LinearLayout
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="?android:attr/selectableItemBackground"
+                android:ellipsize="marquee"
+                android:gravity="center_vertical"
+                android:minHeight="?android:attr/listPreferredItemHeightSmall"
+                android:orientation="vertical"
+                android:paddingBottom="5dip"
+                android:paddingLeft="16dip"
+                android:paddingRight="0dip"
+                android:paddingTop="5dip" >
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:text="@string/hardware"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    android:textColor="?android:attr/textColorAlertDialogListItem" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:text="@string/use_physical_keyboard"
+                    android:textAppearance="?android:attr/textAppearanceSmall"
+                    android:textColor="?android:attr/textColorAlertDialogListItem" />
+            </LinearLayout>
+
+            <Switch
+                android:id="@+id/hard_keyboard_switch"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginRight="12dip" />
+        </LinearLayout>
+    </LinearLayout>
+
+    <View
+        android:id="@+id/titleDivider"
+        android:layout_width="match_parent"
+        android:layout_height="2dip"
+        android:background="@android:drawable/divider_horizontal_dark" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index cbfc1a4..aaef701 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -85,6 +85,8 @@
   <java-symbol type="id" name="fillInIntent" />
   <java-symbol type="id" name="find" />
   <java-symbol type="id" name="fullscreenArea" />
+  <java-symbol type="id" name="hard_keyboard_section" />
+  <java-symbol type="id" name="hard_keyboard_switch" />
   <java-symbol type="id" name="headers" />
   <java-symbol type="id" name="hour" />
   <java-symbol type="id" name="icon" />
@@ -1042,6 +1044,7 @@
   <java-symbol type="layout" name="icon_menu_layout" />
   <java-symbol type="layout" name="input_method" />
   <java-symbol type="layout" name="input_method_extract_view" />
+  <java-symbol type="layout" name="input_method_switch_dialog_title" />
   <java-symbol type="layout" name="js_prompt" />
   <java-symbol type="layout" name="list_content_simple" />
   <java-symbol type="layout" name="list_menu_item_checkbox" />
@@ -1449,6 +1452,7 @@
   <java-symbol type="string" name="factorytest_no_action" />
   <java-symbol type="string" name="factorytest_not_system" />
   <java-symbol type="string" name="factorytest_reboot" />
+  <java-symbol type="string" name="hardware" />
   <java-symbol type="string" name="heavy_weight_notification" />
   <java-symbol type="string" name="heavy_weight_notification_detail" />
   <java-symbol type="string" name="input_method_binding_label" />
@@ -1471,6 +1475,7 @@
   <java-symbol type="string" name="usb_cd_installer_notification_title" />
   <java-symbol type="string" name="usb_mtp_notification_title" />
   <java-symbol type="string" name="usb_notification_message" />
+  <java-symbol type="string" name="use_physical_keyboard" />
   <java-symbol type="string" name="usb_ptp_notification_title" />
   <java-symbol type="string" name="vpn_text" />
   <java-symbol type="string" name="vpn_text_long" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 44f2ade..0eb46bd 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. -->
@@ -2921,6 +2927,10 @@
     <string name="select_input_method">Choose input method</string>
     <!-- Title of a button to open the settings for input methods [CHAR LIMIT=30] -->
     <string name="configure_input_methods">Set up input methods</string>
+    <!-- Summary text of a toggle switch to enable/disable use of the physical keyboard in the input method selector [CHAR LIMIT=25] -->
+    <string name="use_physical_keyboard">Physical keyboard</string>
+    <!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=10] -->
+    <string name="hardware">Hardware</string>
 
     <string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
     <string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
diff --git a/docs/html/guide/topics/renderscript/index.jd b/docs/html/guide/topics/renderscript/index.jd
index 24b9750..b2d9f84 100644
--- a/docs/html/guide/topics/renderscript/index.jd
+++ b/docs/html/guide/topics/renderscript/index.jd
@@ -638,8 +638,10 @@
 <code>rs_program_fragment</code> and <code>rs_allocation</code>, you have to obtain an object of the
 corresponding Android framework class first and then call the <code>set</code> method for that
 structure to bind the memory to the Renderscript runtime. You cannot directly manipulate these structures
-at the Renderscript runtime layer. Keep in mind that user-defined structures
-cannot contain pointers, so this restriction only applies to certain structures that are provided by Renderscript. 
+at the Renderscript runtime layer. This restriction is not applicable to user-defined structures
+that contain pointers, because they cannot be exported to a reflected layer class
+in the first place. A compiler error is generated if you try to declare a non-static, global
+struct that contains a pointer.
 </p>
 
 <p>Renderscript also has support for pointers, but you must explicitly allocate the memory in your
diff --git a/docs/html/guide/topics/wireless/wifip2p.jd b/docs/html/guide/topics/wireless/wifip2p.jd
index ec8e71e..82c9abd 100644
--- a/docs/html/guide/topics/wireless/wifip2p.jd
+++ b/docs/html/guide/topics/wireless/wifip2p.jd
@@ -491,10 +491,10 @@
   </ol>
 
   <p>The following example, modified from the <a href=
-  "{@docRoot}resources/samples/WifiDirectDemo/index.html">Wi-Fi Direct Demo</a> sample, shows you how
+  "{@docRoot}resources/samples/WiFiDirectDemo/index.html">Wi-Fi Direct Demo</a> sample, shows you how
   to create this client-server socket communication and transfer JPEG images from a client
   to a server with a service. For a complete working example, compile and run the <a href=
-  "{@docRoot}resources/samples/WifiDirectDemo/index.html">Wi-Fi Direct Demo</a> sample.</p>
+  "{@docRoot}resources/samples/WiFiDirectDemo/index.html">Wi-Fi Direct Demo</a> sample.</p>
 <pre>
 public static class FileServerAsyncTask extends AsyncTask<Void, Void, String> {
 
diff --git a/docs/html/sdk/ndk/overview.jd b/docs/html/sdk/ndk/overview.jd
index c98e600..d2a9746 100644
--- a/docs/html/sdk/ndk/overview.jd
+++ b/docs/html/sdk/ndk/overview.jd
@@ -270,11 +270,11 @@
   <ul>
     <li>If you are developing in Eclipse with ADT, use the New Project Wizard to create a new
     Android project for each sample, using the "Import from Existing Source" option and importing
-    the source from <code>&lt;ndk&gt;/apps/&lt;app_name&gt;/project/</code>. Then, set up an AVD,
+    the source from <code>&lt;ndk&gt;/samples/&lt;name&gt;/</code>. Then, set up an AVD,
     if necessary, and build/run the application in the emulator.</li>
 
     <li>If you are developing with Ant, use the <code>android</code> tool to create the build file
-    for each of the sample projects at <code>&lt;ndk&gt;/apps/&lt;app_name&gt;/project/</code>.
+    for each of the sample projects at <code>&lt;ndk&gt;/samples/&lt;name&gt;/</code>.
     Then set up an AVD, if necessary, build your project in the usual way, and run it in the
     emulator.</li>    
     
diff --git a/include/androidfw/Input.h b/include/androidfw/Input.h
index f8cbdde..a98e1a2 100644
--- a/include/androidfw/Input.h
+++ b/include/androidfw/Input.h
@@ -811,117 +811,6 @@
     VelocityTracker mVelocityTracker;
 };
 
-/*
- * Identifies a device.
- */
-struct InputDeviceIdentifier {
-    inline InputDeviceIdentifier() :
-            bus(0), vendor(0), product(0), version(0) {
-    }
-
-    // Information provided by the kernel.
-    String8 name;
-    String8 location;
-    String8 uniqueId;
-    uint16_t bus;
-    uint16_t vendor;
-    uint16_t product;
-    uint16_t version;
-
-    // A composite input device descriptor string that uniquely identifies the device
-    // even across reboots or reconnections.  The value of this field is used by
-    // upper layers of the input system to associate settings with individual devices.
-    // It is hashed from whatever kernel provided information is available.
-    // Ideally, the way this value is computed should not change between Android releases
-    // because that would invalidate persistent settings that rely on it.
-    String8 descriptor;
-};
-
-/*
- * Describes the characteristics and capabilities of an input device.
- */
-class InputDeviceInfo {
-public:
-    InputDeviceInfo();
-    InputDeviceInfo(const InputDeviceInfo& other);
-    ~InputDeviceInfo();
-
-    struct MotionRange {
-        int32_t axis;
-        uint32_t source;
-        float min;
-        float max;
-        float flat;
-        float fuzz;
-    };
-
-    void initialize(int32_t id, const String8& name, const String8& descriptor);
-
-    inline int32_t getId() const { return mId; }
-    inline const String8 getName() const { return mName; }
-    inline const String8 getDescriptor() const { return mDescriptor; }
-    inline uint32_t getSources() const { return mSources; }
-
-    const MotionRange* getMotionRange(int32_t axis, uint32_t source) const;
-
-    void addSource(uint32_t source);
-    void addMotionRange(int32_t axis, uint32_t source,
-            float min, float max, float flat, float fuzz);
-    void addMotionRange(const MotionRange& range);
-
-    inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
-    inline int32_t getKeyboardType() const { return mKeyboardType; }
-
-    inline void setKeyCharacterMapFile(const String8& value) { mKeyCharacterMapFile = value; }
-    inline const String8& getKeyCharacterMapFile() const { return mKeyCharacterMapFile; }
-
-    inline const Vector<MotionRange>& getMotionRanges() const {
-        return mMotionRanges;
-    }
-
-private:
-    int32_t mId;
-    String8 mName;
-    String8 mDescriptor;
-    uint32_t mSources;
-    int32_t mKeyboardType;
-    String8 mKeyCharacterMapFile;
-
-    Vector<MotionRange> mMotionRanges;
-};
-
-/* Types of input device configuration files. */
-enum InputDeviceConfigurationFileType {
-    INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION = 0,     /* .idc file */
-    INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT = 1,        /* .kl file */
-    INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP = 2, /* .kcm file */
-};
-
-/*
- * Gets the path of an input device configuration file, if one is available.
- * Considers both system provided and user installed configuration files.
- *
- * The device identifier is used to construct several default configuration file
- * names to try based on the device name, vendor, product, and version.
- *
- * Returns an empty string if not found.
- */
-extern String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
-        const InputDeviceIdentifier& deviceIdentifier,
-        InputDeviceConfigurationFileType type);
-
-/*
- * Gets the path of an input device configuration file, if one is available.
- * Considers both system provided and user installed configuration files.
- *
- * The name is case-sensitive and is used to construct the filename to resolve.
- * All characters except 'a'-'z', 'A'-'Z', '0'-'9', '-', and '_' are replaced by underscores.
- *
- * Returns an empty string if not found.
- */
-extern String8 getInputDeviceConfigurationFilePathByName(
-        const String8& name, InputDeviceConfigurationFileType type);
-
 } // namespace android
 
 #endif // _ANDROIDFW_INPUT_H
diff --git a/include/androidfw/InputDevice.h b/include/androidfw/InputDevice.h
new file mode 100644
index 0000000..c9554dc
--- /dev/null
+++ b/include/androidfw/InputDevice.h
@@ -0,0 +1,143 @@
+/*
+ * 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 _ANDROIDFW_INPUT_DEVICE_H
+#define _ANDROIDFW_INPUT_DEVICE_H
+
+#include <androidfw/Input.h>
+#include <androidfw/KeyCharacterMap.h>
+
+namespace android {
+
+/*
+ * Identifies a device.
+ */
+struct InputDeviceIdentifier {
+    inline InputDeviceIdentifier() :
+            bus(0), vendor(0), product(0), version(0) {
+    }
+
+    // Information provided by the kernel.
+    String8 name;
+    String8 location;
+    String8 uniqueId;
+    uint16_t bus;
+    uint16_t vendor;
+    uint16_t product;
+    uint16_t version;
+
+    // A composite input device descriptor string that uniquely identifies the device
+    // even across reboots or reconnections.  The value of this field is used by
+    // upper layers of the input system to associate settings with individual devices.
+    // It is hashed from whatever kernel provided information is available.
+    // Ideally, the way this value is computed should not change between Android releases
+    // because that would invalidate persistent settings that rely on it.
+    String8 descriptor;
+};
+
+/*
+ * Describes the characteristics and capabilities of an input device.
+ */
+class InputDeviceInfo {
+public:
+    InputDeviceInfo();
+    InputDeviceInfo(const InputDeviceInfo& other);
+    ~InputDeviceInfo();
+
+    struct MotionRange {
+        int32_t axis;
+        uint32_t source;
+        float min;
+        float max;
+        float flat;
+        float fuzz;
+    };
+
+    void initialize(int32_t id, const String8& name, const String8& descriptor);
+
+    inline int32_t getId() const { return mId; }
+    inline const String8 getName() const { return mName; }
+    inline const String8 getDescriptor() const { return mDescriptor; }
+    inline uint32_t getSources() const { return mSources; }
+
+    const MotionRange* getMotionRange(int32_t axis, uint32_t source) const;
+
+    void addSource(uint32_t source);
+    void addMotionRange(int32_t axis, uint32_t source,
+            float min, float max, float flat, float fuzz);
+    void addMotionRange(const MotionRange& range);
+
+    inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
+    inline int32_t getKeyboardType() const { return mKeyboardType; }
+
+    inline void setKeyCharacterMap(const sp<KeyCharacterMap>& value) {
+        mKeyCharacterMap = value;
+    }
+
+    inline sp<KeyCharacterMap> getKeyCharacterMap() const {
+        return mKeyCharacterMap;
+    }
+
+    inline const Vector<MotionRange>& getMotionRanges() const {
+        return mMotionRanges;
+    }
+
+private:
+    int32_t mId;
+    String8 mName;
+    String8 mDescriptor;
+    uint32_t mSources;
+    int32_t mKeyboardType;
+    sp<KeyCharacterMap> mKeyCharacterMap;
+
+    Vector<MotionRange> mMotionRanges;
+};
+
+/* Types of input device configuration files. */
+enum InputDeviceConfigurationFileType {
+    INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION = 0,     /* .idc file */
+    INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT = 1,        /* .kl file */
+    INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP = 2, /* .kcm file */
+};
+
+/*
+ * Gets the path of an input device configuration file, if one is available.
+ * Considers both system provided and user installed configuration files.
+ *
+ * The device identifier is used to construct several default configuration file
+ * names to try based on the device name, vendor, product, and version.
+ *
+ * Returns an empty string if not found.
+ */
+extern String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
+        const InputDeviceIdentifier& deviceIdentifier,
+        InputDeviceConfigurationFileType type);
+
+/*
+ * Gets the path of an input device configuration file, if one is available.
+ * Considers both system provided and user installed configuration files.
+ *
+ * The name is case-sensitive and is used to construct the filename to resolve.
+ * All characters except 'a'-'z', 'A'-'Z', '0'-'9', '-', and '_' are replaced by underscores.
+ *
+ * Returns an empty string if not found.
+ */
+extern String8 getInputDeviceConfigurationFilePathByName(
+        const String8& name, InputDeviceConfigurationFileType type);
+
+} // namespace android
+
+#endif // _ANDROIDFW_INPUT_DEVICE_H
diff --git a/include/androidfw/KeyCharacterMap.h b/include/androidfw/KeyCharacterMap.h
index 679dd2c..3cc1cb2 100644
--- a/include/androidfw/KeyCharacterMap.h
+++ b/include/androidfw/KeyCharacterMap.h
@@ -19,12 +19,17 @@
 
 #include <stdint.h>
 
+#if HAVE_ANDROID_OS
+#include <binder/IBinder.h>
+#endif
+
 #include <androidfw/Input.h>
 #include <utils/Errors.h>
 #include <utils/KeyedVector.h>
 #include <utils/Tokenizer.h>
 #include <utils/String8.h>
 #include <utils/Unicode.h>
+#include <utils/RefBase.h>
 
 namespace android {
 
@@ -32,8 +37,10 @@
  * Describes a mapping from Android key codes to characters.
  * Also specifies other functions of the keyboard such as the keyboard type
  * and key modifier semantics.
+ *
+ * This object is immutable after it has been loaded.
  */
-class KeyCharacterMap {
+class KeyCharacterMap : public RefBase {
 public:
     enum KeyboardType {
         KEYBOARD_TYPE_UNKNOWN = 0,
@@ -50,9 +57,11 @@
         int32_t metaState;
     };
 
-    ~KeyCharacterMap();
+    /* Loads a key character map from a file. */
+    static status_t load(const String8& filename, sp<KeyCharacterMap>* outMap);
 
-    static status_t load(const String8& filename, KeyCharacterMap** outMap);
+    /* Returns an empty key character map. */
+    static sp<KeyCharacterMap> empty();
 
     /* Gets the keyboard type. */
     int32_t getKeyboardType() const;
@@ -92,6 +101,17 @@
     bool getEvents(int32_t deviceId, const char16_t* chars, size_t numChars,
             Vector<KeyEvent>& outEvents) const;
 
+#if HAVE_ANDROID_OS
+    /* Reads a key map from a parcel. */
+    static sp<KeyCharacterMap> readFromParcel(Parcel* parcel);
+
+    /* Writes a key map to a parcel. */
+    void writeToParcel(Parcel* parcel) const;
+#endif
+
+protected:
+    virtual ~KeyCharacterMap();
+
 private:
     struct Behavior {
         Behavior();
@@ -162,6 +182,8 @@
         status_t parseCharacterLiteral(char16_t* outCharacter);
     };
 
+    static sp<KeyCharacterMap> sEmpty;
+
     KeyedVector<int32_t, Key*> mKeys;
     int mType;
 
diff --git a/include/androidfw/KeyLayoutMap.h b/include/androidfw/KeyLayoutMap.h
index 5a6f550..5408680 100644
--- a/include/androidfw/KeyLayoutMap.h
+++ b/include/androidfw/KeyLayoutMap.h
@@ -21,6 +21,7 @@
 #include <utils/Errors.h>
 #include <utils/KeyedVector.h>
 #include <utils/Tokenizer.h>
+#include <utils/RefBase.h>
 
 namespace android {
 
@@ -56,18 +57,21 @@
 
 /**
  * Describes a mapping from keyboard scan codes and joystick axes to Android key codes and axes.
+ *
+ * This object is immutable after it has been loaded.
  */
-class KeyLayoutMap {
+class KeyLayoutMap : public RefBase {
 public:
-    ~KeyLayoutMap();
-
-    static status_t load(const String8& filename, KeyLayoutMap** outMap);
+    static status_t load(const String8& filename, sp<KeyLayoutMap>* outMap);
 
     status_t mapKey(int32_t scanCode, int32_t* keyCode, uint32_t* flags) const;
     status_t findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const;
 
     status_t mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const;
 
+protected:
+    virtual ~KeyLayoutMap();
+
 private:
     struct Key {
         int32_t keyCode;
diff --git a/include/androidfw/Keyboard.h b/include/androidfw/Keyboard.h
index ae65198..6537a8f 100644
--- a/include/androidfw/Keyboard.h
+++ b/include/androidfw/Keyboard.h
@@ -18,6 +18,7 @@
 #define _ANDROIDFW_KEYBOARD_H
 
 #include <androidfw/Input.h>
+#include <androidfw/InputDevice.h>
 #include <utils/Errors.h>
 #include <utils/String8.h>
 #include <utils/PropertyMap.h>
@@ -42,10 +43,10 @@
 class KeyMap {
 public:
     String8 keyLayoutFile;
-    KeyLayoutMap* keyLayoutMap;
+    sp<KeyLayoutMap> keyLayoutMap;
 
     String8 keyCharacterMapFile;
-    KeyCharacterMap* keyCharacterMap;
+    sp<KeyCharacterMap> keyCharacterMap;
 
     KeyMap();
     ~KeyMap();
diff --git a/include/androidfw/VirtualKeyMap.h b/include/androidfw/VirtualKeyMap.h
index 66340e3..dd3ad1e9 100644
--- a/include/androidfw/VirtualKeyMap.h
+++ b/include/androidfw/VirtualKeyMap.h
@@ -43,6 +43,8 @@
 /**
  * Describes a collection of virtual keys on a touch screen in terms of
  * virtual scan codes and hit rectangles.
+ *
+ * This object is immutable after it has been loaded.
  */
 class VirtualKeyMap {
 public:
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
index a3f92cb..95c77d4 100644
--- a/libs/androidfw/Android.mk
+++ b/libs/androidfw/Android.mk
@@ -29,6 +29,7 @@
 # formerly in libui
 commonUiSources:= \
     Input.cpp \
+    InputDevice.cpp \
     Keyboard.cpp \
     KeyCharacterMap.cpp \
     KeyLayoutMap.cpp \
diff --git a/libs/androidfw/Input.cpp b/libs/androidfw/Input.cpp
index 2e4b26f..1617a3f 100644
--- a/libs/androidfw/Input.cpp
+++ b/libs/androidfw/Input.cpp
@@ -1,8 +1,19 @@
-//
-// Copyright 2010 The Android Open Source Project
-//
-// Provides a pipe-based transport for native events in the NDK.
-//
+/*
+ * Copyright (C) 2010 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.
+ */
+
 #define LOG_TAG "Input"
 
 //#define LOG_NDEBUG 0
@@ -39,106 +50,6 @@
 
 namespace android {
 
-static const char* CONFIGURATION_FILE_DIR[] = {
-        "idc/",
-        "keylayout/",
-        "keychars/",
-};
-
-static const char* CONFIGURATION_FILE_EXTENSION[] = {
-        ".idc",
-        ".kl",
-        ".kcm",
-};
-
-static bool isValidNameChar(char ch) {
-    return isascii(ch) && (isdigit(ch) || isalpha(ch) || ch == '-' || ch == '_');
-}
-
-static void appendInputDeviceConfigurationFileRelativePath(String8& path,
-        const String8& name, InputDeviceConfigurationFileType type) {
-    path.append(CONFIGURATION_FILE_DIR[type]);
-    for (size_t i = 0; i < name.length(); i++) {
-        char ch = name[i];
-        if (!isValidNameChar(ch)) {
-            ch = '_';
-        }
-        path.append(&ch, 1);
-    }
-    path.append(CONFIGURATION_FILE_EXTENSION[type]);
-}
-
-String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
-        const InputDeviceIdentifier& deviceIdentifier,
-        InputDeviceConfigurationFileType type) {
-    if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
-        if (deviceIdentifier.version != 0) {
-            // Try vendor product version.
-            String8 versionPath(getInputDeviceConfigurationFilePathByName(
-                    String8::format("Vendor_%04x_Product_%04x_Version_%04x",
-                            deviceIdentifier.vendor, deviceIdentifier.product,
-                            deviceIdentifier.version),
-                    type));
-            if (!versionPath.isEmpty()) {
-                return versionPath;
-            }
-        }
-
-        // Try vendor product.
-        String8 productPath(getInputDeviceConfigurationFilePathByName(
-                String8::format("Vendor_%04x_Product_%04x",
-                        deviceIdentifier.vendor, deviceIdentifier.product),
-                type));
-        if (!productPath.isEmpty()) {
-            return productPath;
-        }
-    }
-
-    // Try device name.
-    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);
-}
-
-String8 getInputDeviceConfigurationFilePathByName(
-        const String8& name, InputDeviceConfigurationFileType type) {
-    // Search system repository.
-    String8 path;
-    path.setTo(getenv("ANDROID_ROOT"));
-    path.append("/usr/");
-    appendInputDeviceConfigurationFileRelativePath(path, name, type);
-#if DEBUG_PROBE
-    ALOGD("Probing for system provided input device configuration file: path='%s'", path.string());
-#endif
-    if (!access(path.string(), R_OK)) {
-#if DEBUG_PROBE
-        ALOGD("Found");
-#endif
-        return path;
-    }
-
-    // Search user repository.
-    // TODO Should only look here if not in safe mode.
-    path.setTo(getenv("ANDROID_DATA"));
-    path.append("/system/devices/");
-    appendInputDeviceConfigurationFileRelativePath(path, name, type);
-#if DEBUG_PROBE
-    ALOGD("Probing for system user input device configuration file: path='%s'", path.string());
-#endif
-    if (!access(path.string(), R_OK)) {
-#if DEBUG_PROBE
-        ALOGD("Found");
-#endif
-        return path;
-    }
-
-    // Not found.
-#if DEBUG_PROBE
-    ALOGD("Probe failed to find input device configuration file: name='%s', type=%d",
-            name.string(), type);
-#endif
-    return String8();
-}
-
-
 // --- InputEvent ---
 
 void InputEvent::initialize(int32_t deviceId, int32_t source) {
@@ -1222,57 +1133,4 @@
     }
 }
 
-
-// --- InputDeviceInfo ---
-
-InputDeviceInfo::InputDeviceInfo() {
-    initialize(-1, String8("uninitialized device info"), String8("unknown"));
-}
-
-InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) :
-        mId(other.mId), mName(other.mName), mDescriptor(other.mDescriptor),
-        mSources(other.mSources),
-        mKeyboardType(other.mKeyboardType),
-        mKeyCharacterMapFile(other.mKeyCharacterMapFile),
-        mMotionRanges(other.mMotionRanges) {
-}
-
-InputDeviceInfo::~InputDeviceInfo() {
-}
-
-void InputDeviceInfo::initialize(int32_t id, const String8& name, const String8& descriptor) {
-    mId = id;
-    mName = name;
-    mDescriptor = descriptor;
-    mSources = 0;
-    mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
-    mMotionRanges.clear();
-}
-
-const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange(
-        int32_t axis, uint32_t source) const {
-    size_t numRanges = mMotionRanges.size();
-    for (size_t i = 0; i < numRanges; i++) {
-        const MotionRange& range = mMotionRanges.itemAt(i);
-        if (range.axis == axis && range.source == source) {
-            return &range;
-        }
-    }
-    return NULL;
-}
-
-void InputDeviceInfo::addSource(uint32_t source) {
-    mSources |= source;
-}
-
-void InputDeviceInfo::addMotionRange(int32_t axis, uint32_t source, float min, float max,
-        float flat, float fuzz) {
-    MotionRange range = { axis, source, min, max, flat, fuzz };
-    mMotionRanges.add(range);
-}
-
-void InputDeviceInfo::addMotionRange(const MotionRange& range) {
-    mMotionRanges.add(range);
-}
-
 } // namespace android
diff --git a/libs/androidfw/InputDevice.cpp b/libs/androidfw/InputDevice.cpp
new file mode 100644
index 0000000..698feb6
--- /dev/null
+++ b/libs/androidfw/InputDevice.cpp
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "InputDevice"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include <androidfw/InputDevice.h>
+
+namespace android {
+
+static const char* CONFIGURATION_FILE_DIR[] = {
+        "idc/",
+        "keylayout/",
+        "keychars/",
+};
+
+static const char* CONFIGURATION_FILE_EXTENSION[] = {
+        ".idc",
+        ".kl",
+        ".kcm",
+};
+
+static bool isValidNameChar(char ch) {
+    return isascii(ch) && (isdigit(ch) || isalpha(ch) || ch == '-' || ch == '_');
+}
+
+static void appendInputDeviceConfigurationFileRelativePath(String8& path,
+        const String8& name, InputDeviceConfigurationFileType type) {
+    path.append(CONFIGURATION_FILE_DIR[type]);
+    for (size_t i = 0; i < name.length(); i++) {
+        char ch = name[i];
+        if (!isValidNameChar(ch)) {
+            ch = '_';
+        }
+        path.append(&ch, 1);
+    }
+    path.append(CONFIGURATION_FILE_EXTENSION[type]);
+}
+
+String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
+        const InputDeviceIdentifier& deviceIdentifier,
+        InputDeviceConfigurationFileType type) {
+    if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
+        if (deviceIdentifier.version != 0) {
+            // Try vendor product version.
+            String8 versionPath(getInputDeviceConfigurationFilePathByName(
+                    String8::format("Vendor_%04x_Product_%04x_Version_%04x",
+                            deviceIdentifier.vendor, deviceIdentifier.product,
+                            deviceIdentifier.version),
+                    type));
+            if (!versionPath.isEmpty()) {
+                return versionPath;
+            }
+        }
+
+        // Try vendor product.
+        String8 productPath(getInputDeviceConfigurationFilePathByName(
+                String8::format("Vendor_%04x_Product_%04x",
+                        deviceIdentifier.vendor, deviceIdentifier.product),
+                type));
+        if (!productPath.isEmpty()) {
+            return productPath;
+        }
+    }
+
+    // Try device name.
+    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);
+}
+
+String8 getInputDeviceConfigurationFilePathByName(
+        const String8& name, InputDeviceConfigurationFileType type) {
+    // Search system repository.
+    String8 path;
+    path.setTo(getenv("ANDROID_ROOT"));
+    path.append("/usr/");
+    appendInputDeviceConfigurationFileRelativePath(path, name, type);
+#if DEBUG_PROBE
+    ALOGD("Probing for system provided input device configuration file: path='%s'", path.string());
+#endif
+    if (!access(path.string(), R_OK)) {
+#if DEBUG_PROBE
+        ALOGD("Found");
+#endif
+        return path;
+    }
+
+    // Search user repository.
+    // TODO Should only look here if not in safe mode.
+    path.setTo(getenv("ANDROID_DATA"));
+    path.append("/system/devices/");
+    appendInputDeviceConfigurationFileRelativePath(path, name, type);
+#if DEBUG_PROBE
+    ALOGD("Probing for system user input device configuration file: path='%s'", path.string());
+#endif
+    if (!access(path.string(), R_OK)) {
+#if DEBUG_PROBE
+        ALOGD("Found");
+#endif
+        return path;
+    }
+
+    // Not found.
+#if DEBUG_PROBE
+    ALOGD("Probe failed to find input device configuration file: name='%s', type=%d",
+            name.string(), type);
+#endif
+    return String8();
+}
+
+
+// --- InputDeviceInfo ---
+
+InputDeviceInfo::InputDeviceInfo() {
+    initialize(-1, String8("uninitialized device info"), String8("unknown"));
+}
+
+InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) :
+        mId(other.mId), mName(other.mName), mDescriptor(other.mDescriptor),
+        mSources(other.mSources),
+        mKeyboardType(other.mKeyboardType),
+        mKeyCharacterMap(other.mKeyCharacterMap),
+        mMotionRanges(other.mMotionRanges) {
+}
+
+InputDeviceInfo::~InputDeviceInfo() {
+}
+
+void InputDeviceInfo::initialize(int32_t id, const String8& name, const String8& descriptor) {
+    mId = id;
+    mName = name;
+    mDescriptor = descriptor;
+    mSources = 0;
+    mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
+    mMotionRanges.clear();
+}
+
+const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange(
+        int32_t axis, uint32_t source) const {
+    size_t numRanges = mMotionRanges.size();
+    for (size_t i = 0; i < numRanges; i++) {
+        const MotionRange& range = mMotionRanges.itemAt(i);
+        if (range.axis == axis && range.source == source) {
+            return &range;
+        }
+    }
+    return NULL;
+}
+
+void InputDeviceInfo::addSource(uint32_t source) {
+    mSources |= source;
+}
+
+void InputDeviceInfo::addMotionRange(int32_t axis, uint32_t source, float min, float max,
+        float flat, float fuzz) {
+    MotionRange range = { axis, source, min, max, flat, fuzz };
+    mMotionRanges.add(range);
+}
+
+void InputDeviceInfo::addMotionRange(const MotionRange& range) {
+    mMotionRanges.add(range);
+}
+
+} // namespace android
diff --git a/libs/androidfw/KeyCharacterMap.cpp b/libs/androidfw/KeyCharacterMap.cpp
index 6984084..9abbf38 100644
--- a/libs/androidfw/KeyCharacterMap.cpp
+++ b/libs/androidfw/KeyCharacterMap.cpp
@@ -21,6 +21,11 @@
 #include <android/keycodes.h>
 #include <androidfw/Keyboard.h>
 #include <androidfw/KeyCharacterMap.h>
+
+#if HAVE_ANDROID_OS
+#include <binder/Parcel.h>
+#endif
+
 #include <utils/Log.h>
 #include <utils/Errors.h>
 #include <utils/Tokenizer.h>
@@ -78,6 +83,8 @@
 
 // --- KeyCharacterMap ---
 
+sp<KeyCharacterMap> KeyCharacterMap::sEmpty = new KeyCharacterMap();
+
 KeyCharacterMap::KeyCharacterMap() :
     mType(KEYBOARD_TYPE_UNKNOWN) {
 }
@@ -89,23 +96,23 @@
     }
 }
 
-status_t KeyCharacterMap::load(const String8& filename, KeyCharacterMap** outMap) {
-    *outMap = NULL;
+status_t KeyCharacterMap::load(const String8& filename, sp<KeyCharacterMap>* outMap) {
+    outMap->clear();
 
     Tokenizer* tokenizer;
     status_t status = Tokenizer::open(filename, &tokenizer);
     if (status) {
         ALOGE("Error %d opening key character map file %s.", status, filename.string());
     } else {
-        KeyCharacterMap* map = new KeyCharacterMap();
-        if (!map) {
+        sp<KeyCharacterMap> map = new KeyCharacterMap();
+        if (!map.get()) {
             ALOGE("Error allocating key character map.");
             status = NO_MEMORY;
         } else {
 #if DEBUG_PARSER_PERFORMANCE
             nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
 #endif
-            Parser parser(map, tokenizer);
+            Parser parser(map.get(), tokenizer);
             status = parser.parse();
 #if DEBUG_PARSER_PERFORMANCE
             nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
@@ -113,9 +120,7 @@
                     tokenizer->getFilename().string(), tokenizer->getLineNumber(),
                     elapsedTime / 1000000.0);
 #endif
-            if (status) {
-                delete map;
-            } else {
+            if (!status) {
                 *outMap = map;
             }
         }
@@ -124,6 +129,10 @@
     return status;
 }
 
+sp<KeyCharacterMap> KeyCharacterMap::empty() {
+    return sEmpty;
+}
+
 int32_t KeyCharacterMap::getKeyboardType() const {
     return mType;
 }
@@ -419,6 +428,79 @@
     }
 }
 
+#if HAVE_ANDROID_OS
+sp<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
+    sp<KeyCharacterMap> map = new KeyCharacterMap();
+    map->mType = parcel->readInt32();
+    size_t numKeys = parcel->readInt32();
+    if (parcel->errorCheck()) {
+        return NULL;
+    }
+
+    for (size_t i = 0; i < numKeys; i++) {
+        int32_t keyCode = parcel->readInt32();
+        char16_t label = parcel->readInt32();
+        char16_t number = parcel->readInt32();
+        if (parcel->errorCheck()) {
+            return NULL;
+        }
+
+        Key* key = new Key();
+        key->label = label;
+        key->number = number;
+        map->mKeys.add(keyCode, key);
+
+        Behavior* lastBehavior = NULL;
+        while (parcel->readInt32()) {
+            int32_t metaState = parcel->readInt32();
+            char16_t character = parcel->readInt32();
+            int32_t fallbackKeyCode = parcel->readInt32();
+            if (parcel->errorCheck()) {
+                return NULL;
+            }
+
+            Behavior* behavior = new Behavior();
+            behavior->metaState = metaState;
+            behavior->character = character;
+            behavior->fallbackKeyCode = fallbackKeyCode;
+            if (lastBehavior) {
+                lastBehavior->next = behavior;
+            } else {
+                key->firstBehavior = behavior;
+            }
+            lastBehavior = behavior;
+        }
+
+        if (parcel->errorCheck()) {
+            return NULL;
+        }
+    }
+    return map;
+}
+
+void KeyCharacterMap::writeToParcel(Parcel* parcel) const {
+    parcel->writeInt32(mType);
+
+    size_t numKeys = mKeys.size();
+    parcel->writeInt32(numKeys);
+    for (size_t i = 0; i < numKeys; i++) {
+        int32_t keyCode = mKeys.keyAt(i);
+        const Key* key = mKeys.valueAt(i);
+        parcel->writeInt32(keyCode);
+        parcel->writeInt32(key->label);
+        parcel->writeInt32(key->number);
+        for (const Behavior* behavior = key->firstBehavior; behavior != NULL;
+                behavior = behavior->next) {
+            parcel->writeInt32(1);
+            parcel->writeInt32(behavior->metaState);
+            parcel->writeInt32(behavior->character);
+            parcel->writeInt32(behavior->fallbackKeyCode);
+        }
+        parcel->writeInt32(0);
+    }
+}
+#endif
+
 
 // --- KeyCharacterMap::Key ---
 
diff --git a/libs/androidfw/KeyLayoutMap.cpp b/libs/androidfw/KeyLayoutMap.cpp
index 15d81ee..1809412 100644
--- a/libs/androidfw/KeyLayoutMap.cpp
+++ b/libs/androidfw/KeyLayoutMap.cpp
@@ -47,23 +47,23 @@
 KeyLayoutMap::~KeyLayoutMap() {
 }
 
-status_t KeyLayoutMap::load(const String8& filename, KeyLayoutMap** outMap) {
-    *outMap = NULL;
+status_t KeyLayoutMap::load(const String8& filename, sp<KeyLayoutMap>* outMap) {
+    outMap->clear();
 
     Tokenizer* tokenizer;
     status_t status = Tokenizer::open(filename, &tokenizer);
     if (status) {
         ALOGE("Error %d opening key layout map file %s.", status, filename.string());
     } else {
-        KeyLayoutMap* map = new KeyLayoutMap();
-        if (!map) {
+        sp<KeyLayoutMap> map = new KeyLayoutMap();
+        if (!map.get()) {
             ALOGE("Error allocating key layout map.");
             status = NO_MEMORY;
         } else {
 #if DEBUG_PARSER_PERFORMANCE
             nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
 #endif
-            Parser parser(map, tokenizer);
+            Parser parser(map.get(), tokenizer);
             status = parser.parse();
 #if DEBUG_PARSER_PERFORMANCE
             nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
@@ -71,9 +71,7 @@
                     tokenizer->getFilename().string(), tokenizer->getLineNumber(),
                     elapsedTime / 1000000.0);
 #endif
-            if (status) {
-                delete map;
-            } else {
+            if (!status) {
                 *outMap = map;
             }
         }
diff --git a/libs/androidfw/Keyboard.cpp b/libs/androidfw/Keyboard.cpp
index e97a5eb..a84a8c7 100644
--- a/libs/androidfw/Keyboard.cpp
+++ b/libs/androidfw/Keyboard.cpp
@@ -24,6 +24,7 @@
 #include <androidfw/KeycodeLabels.h>
 #include <androidfw/KeyLayoutMap.h>
 #include <androidfw/KeyCharacterMap.h>
+#include <androidfw/InputDevice.h>
 #include <utils/Errors.h>
 #include <utils/Log.h>
 #include <cutils/properties.h>
@@ -32,13 +33,10 @@
 
 // --- KeyMap ---
 
-KeyMap::KeyMap() :
-        keyLayoutMap(NULL), keyCharacterMap(NULL) {
+KeyMap::KeyMap() {
 }
 
 KeyMap::~KeyMap() {
-    delete keyLayoutMap;
-    delete keyCharacterMap;
 }
 
 status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
@@ -114,14 +112,12 @@
         return NAME_NOT_FOUND;
     }
 
-    KeyLayoutMap* map;
-    status_t status = KeyLayoutMap::load(path, &map);
+    status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
     if (status) {
         return status;
     }
 
     keyLayoutFile.setTo(path);
-    keyLayoutMap = map;
     return OK;
 }
 
@@ -133,14 +129,12 @@
         return NAME_NOT_FOUND;
     }
 
-    KeyCharacterMap* map;
-    status_t status = KeyCharacterMap::load(path, &map);
+    status_t status = KeyCharacterMap::load(path, &keyCharacterMap);
     if (status) {
         return status;
     }
 
     keyCharacterMapFile.setTo(path);
-    keyCharacterMap = map;
     return OK;
 }
 
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 6eb2990..accec64 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -32,7 +32,6 @@
 using android::InputEvent;
 using android::KeyEvent;
 using android::MotionEvent;
-using android::InputDeviceInfo;
 using android::sp;
 using android::Vector;
 
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_input_methods_panel.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_input_methods_panel.xml
index f6ed804..b712dba 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar_input_methods_panel.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar_input_methods_panel.xml
@@ -21,7 +21,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
-    android:paddingBottom="28dip"
+    android:paddingBottom="7dip"
     android:orientation="vertical"
     android:visibility="gone">
     <View
@@ -38,7 +38,7 @@
             android:layout_height="wrap_content"
             android:layout_marginLeft="20dip"
             android:orientation="vertical"
-            android:background="@*android:drawable/dialog_full_holo_dark">
+            android:background="@drawable/notify_panel_clock_bg">
             <!-- Hard keyboard switch -->
             <LinearLayout
                 android:id="@+id/hard_keyboard_section"
@@ -51,7 +51,7 @@
                     android:orientation="horizontal">
                     <TextView
                         android:id="@+id/use_physical_keyboard_label"
-                        android:layout_width="wrap_content"
+                        android:layout_width="0dip"
                         android:layout_height="wrap_content"
                         android:layout_weight="1"
                         android:minHeight="?android:attr/listPreferredItemHeight"
@@ -82,7 +82,7 @@
             <!-- Input method list -->
             <ScrollView
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_height="0dip"
                 android:overScrollMode="ifContentScrolls"
                 android:layout_marginTop="3dip"
                 android:layout_weight="1"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fc81f8e..6dbe9d3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -125,7 +125,7 @@
     <string name="status_bar_input_method_settings_configure_input_methods">Set up input methods</string>
 
     <!-- Label of a toggle switch to disable use of the physical keyboard in favor of the IME. [CHAR LIMIT=25] -->
-    <string name="status_bar_use_physical_keyboard">Use physical keyboard</string>
+    <string name="status_bar_use_physical_keyboard">Physical keyboard</string>
 
     <!-- Prompt for the USB device permission dialog [CHAR LIMIT=80] -->
     <string name="usb_device_permission_prompt">Allow the app <xliff:g id="application">%1$s</xliff:g> to access the USB device?</string>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 0c8208f..a00fab3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -273,7 +273,8 @@
                 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
                 flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
                 InputDevice.SOURCE_KEYBOARD);
-        InputManager.injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+        InputManager.getInstance().injectInputEvent(ev,
+                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
index f793af9..2171329 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
@@ -201,7 +201,6 @@
         if (closeKeyboard) {
             mImm.hideSoftInputFromWindow(getWindowToken(), 0);
         }
-        updateHardKeyboardEnabled();
     }
 
     private void startActivity(Intent intent) {
@@ -329,6 +328,7 @@
             mHardKeyboardSection.setVisibility(View.VISIBLE);
             if (mHardKeyboardSwitch.isChecked() != mHardKeyboardEnabled) {
                 mHardKeyboardSwitch.setChecked(mHardKeyboardEnabled);
+                updateHardKeyboardEnabled();
             }
         } else {
             mHardKeyboardSection.setVisibility(View.GONE);
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index 7060ae2..744f2ad 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -93,6 +93,32 @@
     return out;
 }
 
+static void setDescriptor(InputDeviceIdentifier& identifier) {
+    // Compute a device descriptor that uniquely identifies the device.
+    // The descriptor is assumed to be a stable identifier.  Its value should not
+    // change between reboots, reconnections, firmware updates or new releases of Android.
+    // Ideally, we also want the descriptor to be short and relatively opaque.
+    String8 rawDescriptor;
+    rawDescriptor.appendFormat(":%04x:%04x:", identifier.vendor, identifier.product);
+    if (!identifier.uniqueId.isEmpty()) {
+        rawDescriptor.append("uniqueId:");
+        rawDescriptor.append(identifier.uniqueId);
+    } if (identifier.vendor == 0 && identifier.product == 0) {
+        // If we don't know the vendor and product id, then the device is probably
+        // built-in so we need to rely on other information to uniquely identify
+        // the input device.  Usually we try to avoid relying on the device name or
+        // location but for built-in input device, they are unlikely to ever change.
+        if (!identifier.name.isEmpty()) {
+            rawDescriptor.append("name:");
+            rawDescriptor.append(identifier.name);
+        } else if (!identifier.location.isEmpty()) {
+            rawDescriptor.append("location:");
+            rawDescriptor.append(identifier.location);
+        }
+    }
+    identifier.descriptor = sha1(rawDescriptor);
+}
+
 // --- Global Functions ---
 
 uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses) {
@@ -164,7 +190,7 @@
 const int EventHub::EPOLL_MAX_EVENTS;
 
 EventHub::EventHub(void) :
-        mBuiltInKeyboardId(-1), mNextDeviceId(1),
+        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1),
         mOpeningDevices(0), mClosingDevices(0),
         mNeedToSendFinishedDeviceScan(false),
         mNeedToReopenDevices(false), mNeedToScanDevices(true),
@@ -256,7 +282,7 @@
         AutoMutex _l(mLock);
 
         Device* device = getDeviceLocked(deviceId);
-        if (device && test_bit(axis, device->absBitmask)) {
+        if (device && !device->isVirtual() && test_bit(axis, device->absBitmask)) {
             struct input_absinfo info;
             if(ioctl(device->fd, EVIOCGABS(axis), &info)) {
                 ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
@@ -307,7 +333,7 @@
         AutoMutex _l(mLock);
 
         Device* device = getDeviceLocked(deviceId);
-        if (device && test_bit(scanCode, device->keyBitmask)) {
+        if (device && !device->isVirtual() && test_bit(scanCode, device->keyBitmask)) {
             uint8_t keyState[sizeof_bit_array(KEY_MAX + 1)];
             memset(keyState, 0, sizeof(keyState));
             if (ioctl(device->fd, EVIOCGKEY(sizeof(keyState)), keyState) >= 0) {
@@ -322,7 +348,7 @@
     AutoMutex _l(mLock);
 
     Device* device = getDeviceLocked(deviceId);
-    if (device && device->keyMap.haveKeyLayout()) {
+    if (device && !device->isVirtual() && device->keyMap.haveKeyLayout()) {
         Vector<int32_t> scanCodes;
         device->keyMap.keyLayoutMap->findScanCodesForKey(keyCode, &scanCodes);
         if (scanCodes.size() != 0) {
@@ -347,7 +373,7 @@
         AutoMutex _l(mLock);
 
         Device* device = getDeviceLocked(deviceId);
-        if (device && test_bit(sw, device->swBitmask)) {
+        if (device && !device->isVirtual() && test_bit(sw, device->swBitmask)) {
             uint8_t swState[sizeof_bit_array(SW_MAX + 1)];
             memset(swState, 0, sizeof(swState));
             if (ioctl(device->fd, EVIOCGSW(sizeof(swState)), swState) >= 0) {
@@ -365,7 +391,7 @@
         AutoMutex _l(mLock);
 
         Device* device = getDeviceLocked(deviceId);
-        if (device && test_bit(axis, device->absBitmask)) {
+        if (device && !device->isVirtual() && test_bit(axis, device->absBitmask)) {
             struct input_absinfo info;
             if(ioctl(device->fd, EVIOCGABS(axis), &info)) {
                 ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
@@ -421,7 +447,7 @@
         }
     }
     
-    if (mBuiltInKeyboardId != -1) {
+    if (mBuiltInKeyboardId != NO_BUILT_IN_KEYBOARD) {
         device = getDeviceLocked(mBuiltInKeyboardId);
         
         if (device && device->keyMap.haveKeyLayout()) {
@@ -449,7 +475,7 @@
         }
     }
 
-    if (mBuiltInKeyboardId != -1) {
+    if (mBuiltInKeyboardId != NO_BUILT_IN_KEYBOARD) {
         device = getDeviceLocked(mBuiltInKeyboardId);
 
         if (device && device->keyMap.haveKeyLayout()) {
@@ -494,7 +520,7 @@
 void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
     AutoMutex _l(mLock);
     Device* device = getDeviceLocked(deviceId);
-    if (device && led >= 0 && led <= LED_MAX) {
+    if (device && !device->isVirtual() && led >= 0 && led <= LED_MAX) {
         struct input_event ev;
         ev.time.tv_sec = 0;
         ev.time.tv_usec = 0;
@@ -520,17 +546,17 @@
     }
 }
 
-String8 EventHub::getKeyCharacterMapFile(int32_t deviceId) const {
+sp<KeyCharacterMap> EventHub::getKeyCharacterMap(int32_t deviceId) const {
     AutoMutex _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device) {
-        return device->keyMap.keyCharacterMapFile;
+        return device->keyMap.keyCharacterMap;
     }
-    return String8();
+    return NULL;
 }
 
 EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const {
-    if (deviceId == 0) {
+    if (deviceId == BUILT_IN_KEYBOARD_ID) {
         deviceId = mBuiltInKeyboardId;
     }
     ssize_t index = mDevices.indexOfKey(deviceId);
@@ -578,7 +604,7 @@
                  device->id, device->path.string());
             mClosingDevices = device->next;
             event->when = now;
-            event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
+            event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id;
             event->type = DEVICE_REMOVED;
             event += 1;
             delete device;
@@ -813,6 +839,9 @@
     if(res < 0) {
         ALOGE("scan dir failed for %s\n", DEVICE_PATH);
     }
+    if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) {
+        createVirtualKeyboardLocked();
+    }
 }
 
 // ----------------------------------------------------------------------------
@@ -908,29 +937,8 @@
         identifier.uniqueId.setTo(buffer);
     }
 
-    // Compute a device descriptor that uniquely identifies the device.
-    // The descriptor is assumed to be a stable identifier.  Its value should not
-    // change between reboots, reconnections, firmware updates or new releases of Android.
-    // Ideally, we also want the descriptor to be short and relatively opaque.
-    String8 rawDescriptor;
-    rawDescriptor.appendFormat(":%04x:%04x:", identifier.vendor, identifier.product);
-    if (!identifier.uniqueId.isEmpty()) {
-        rawDescriptor.append("uniqueId:");
-        rawDescriptor.append(identifier.uniqueId);
-    } if (identifier.vendor == 0 && identifier.product == 0) {
-        // If we don't know the vendor and product id, then the device is probably
-        // built-in so we need to rely on other information to uniquely identify
-        // the input device.  Usually we try to avoid relying on the device name or
-        // location but for built-in input device, they are unlikely to ever change.
-        if (!identifier.name.isEmpty()) {
-            rawDescriptor.append("name:");
-            rawDescriptor.append(identifier.name);
-        } else if (!identifier.location.isEmpty()) {
-            rawDescriptor.append("location:");
-            rawDescriptor.append(identifier.location);
-        }
-    }
-    identifier.descriptor = sha1(rawDescriptor);
+    // Fill in the descriptor.
+    setDescriptor(identifier);
 
     // Make file descriptor non-blocking for use with poll().
     if (fcntl(fd, F_SETFL, O_NONBLOCK)) {
@@ -1048,7 +1056,7 @@
     if (device->classes & INPUT_DEVICE_CLASS_KEYBOARD) {
         // Register the keyboard as a built-in keyboard if it is eligible.
         if (!keyMapStatus
-                && mBuiltInKeyboardId == -1
+                && mBuiltInKeyboardId == NO_BUILT_IN_KEYBOARD
                 && isEligibleBuiltInKeyboard(device->identifier,
                         device->configuration, &device->keyMap)) {
             mBuiltInKeyboardId = device->id;
@@ -1133,11 +1141,29 @@
          toString(mBuiltInKeyboardId == deviceId),
          toString(usingSuspendBlockIoctl), toString(usingClockIoctl));
 
-    mDevices.add(deviceId, device);
+    addDeviceLocked(device);
+    return 0;
+}
 
+void EventHub::createVirtualKeyboardLocked() {
+    InputDeviceIdentifier identifier;
+    identifier.name = "Virtual";
+    identifier.uniqueId = "<virtual>";
+    setDescriptor(identifier);
+
+    Device* device = new Device(-1, VIRTUAL_KEYBOARD_ID, String8("<virtual>"), identifier);
+    device->classes = INPUT_DEVICE_CLASS_KEYBOARD
+            | INPUT_DEVICE_CLASS_ALPHAKEY
+            | INPUT_DEVICE_CLASS_DPAD
+            | INPUT_DEVICE_CLASS_VIRTUAL;
+    loadKeyMapLocked(device);
+    addDeviceLocked(device);
+}
+
+void EventHub::addDeviceLocked(Device* device) {
+    mDevices.add(device->id, device);
     device->next = mOpeningDevices;
     mOpeningDevices = device;
-    return 0;
 }
 
 void EventHub::loadConfigurationLocked(Device* device) {
@@ -1224,11 +1250,13 @@
     if (device->id == mBuiltInKeyboardId) {
         ALOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this",
                 device->path.string(), mBuiltInKeyboardId);
-        mBuiltInKeyboardId = -1;
+        mBuiltInKeyboardId = NO_BUILT_IN_KEYBOARD;
     }
 
-    if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, device->fd, NULL)) {
-        ALOGW("Could not remove device fd from epoll instance.  errno=%d", errno);
+    if (!device->isVirtual()) {
+        if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, device->fd, NULL)) {
+            ALOGW("Could not remove device fd from epoll instance.  errno=%d", errno);
+        }
     }
 
     mDevices.removeItem(device->id);
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index bd21a3d..c35df109 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -19,6 +19,7 @@
 #define _RUNTIME_EVENT_HUB_H
 
 #include <androidfw/Input.h>
+#include <androidfw/InputDevice.h>
 #include <androidfw/Keyboard.h>
 #include <androidfw/KeyLayoutMap.h>
 #include <androidfw/KeyCharacterMap.h>
@@ -43,6 +44,13 @@
 
 namespace android {
 
+enum {
+    // Device id of a special "virtual" keyboard that is always present.
+    VIRTUAL_KEYBOARD_ID = -1,
+    // Device id of the "built-in" keyboard if there is one.
+    BUILT_IN_KEYBOARD_ID = 0,
+};
+
 /*
  * A raw event as retrieved from the EventHub.
  */
@@ -107,6 +115,9 @@
     /* The input device is a joystick (implies gamepad, has joystick absolute axes). */
     INPUT_DEVICE_CLASS_JOYSTICK      = 0x00000100,
 
+    /* The input device is virtual (not a real device, not part of UI configuration). */
+    INPUT_DEVICE_CLASS_VIRTUAL       = 0x40000000,
+
     /* The input device is external (not built-in). */
     INPUT_DEVICE_CLASS_EXTERNAL      = 0x80000000,
 };
@@ -208,7 +219,7 @@
     virtual void getVirtualKeyDefinitions(int32_t deviceId,
             Vector<VirtualKeyDefinition>& outVirtualKeys) const = 0;
 
-    virtual String8 getKeyCharacterMapFile(int32_t deviceId) const = 0;
+    virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const = 0;
 
     /* Requests the EventHub to reopen all input devices on the next call to getEvents(). */
     virtual void requestReopenDevices() = 0;
@@ -266,7 +277,7 @@
     virtual void getVirtualKeyDefinitions(int32_t deviceId,
             Vector<VirtualKeyDefinition>& outVirtualKeys) const;
 
-    virtual String8 getKeyCharacterMapFile(int32_t deviceId) const;
+    virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const;
 
     virtual void requestReopenDevices();
 
@@ -282,7 +293,7 @@
     struct Device {
         Device* next;
 
-        int fd;
+        int fd; // may be -1 if device is virtual
         const int32_t id;
         const String8 path;
         const InputDeviceIdentifier identifier;
@@ -305,11 +316,15 @@
         ~Device();
 
         void close();
+
+        inline bool isVirtual() const { return fd < 0; }
     };
 
     status_t openDeviceLocked(const char *devicePath);
-    status_t closeDeviceByPathLocked(const char *devicePath);
+    void createVirtualKeyboardLocked();
+    void addDeviceLocked(Device* device);
 
+    status_t closeDeviceByPathLocked(const char *devicePath);
     void closeDeviceLocked(Device* device);
     void closeAllDevicesLocked();
 
@@ -331,8 +346,13 @@
     // Protect all internal state.
     mutable Mutex mLock;
 
-    // The actual id of the built-in keyboard, or -1 if none.
+    // The actual id of the built-in keyboard, or NO_BUILT_IN_KEYBOARD if none.
     // EventHub remaps the built-in keyboard to id 0 externally as required by the API.
+    enum {
+        // Must not conflict with any other assigned device ids, including
+        // the virtual keyboard id (-1).
+        NO_BUILT_IN_KEYBOARD = -2,
+    };
     int32_t mBuiltInKeyboardId;
 
     int32_t mNextDeviceId;
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index ddd870d..42512d8 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -523,19 +523,21 @@
     InputDeviceInfo deviceInfo;
     for (size_t i = 0; i < mDevices.size(); i++) {
         InputDevice* device = mDevices.valueAt(i);
-        device->getDeviceInfo(& deviceInfo);
-        uint32_t sources = deviceInfo.getSources();
+        if (!(device->getClasses() & INPUT_DEVICE_CLASS_VIRTUAL)) {
+            device->getDeviceInfo(& deviceInfo);
+            uint32_t sources = deviceInfo.getSources();
 
-        if ((sources & AINPUT_SOURCE_TOUCHSCREEN) == AINPUT_SOURCE_TOUCHSCREEN) {
-            touchScreenConfig = InputConfiguration::TOUCHSCREEN_FINGER;
-        }
-        if ((sources & AINPUT_SOURCE_TRACKBALL) == AINPUT_SOURCE_TRACKBALL) {
-            navigationConfig = InputConfiguration::NAVIGATION_TRACKBALL;
-        } else if ((sources & AINPUT_SOURCE_DPAD) == AINPUT_SOURCE_DPAD) {
-            navigationConfig = InputConfiguration::NAVIGATION_DPAD;
-        }
-        if (deviceInfo.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
-            keyboardConfig = InputConfiguration::KEYBOARD_QWERTY;
+            if ((sources & AINPUT_SOURCE_TOUCHSCREEN) == AINPUT_SOURCE_TOUCHSCREEN) {
+                touchScreenConfig = InputConfiguration::TOUCHSCREEN_FINGER;
+            }
+            if ((sources & AINPUT_SOURCE_TRACKBALL) == AINPUT_SOURCE_TRACKBALL) {
+                navigationConfig = InputConfiguration::NAVIGATION_TRACKBALL;
+            } else if ((sources & AINPUT_SOURCE_DPAD) == AINPUT_SOURCE_DPAD) {
+                navigationConfig = InputConfiguration::NAVIGATION_DPAD;
+            }
+            if (deviceInfo.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
+                keyboardConfig = InputConfiguration::KEYBOARD_QWERTY;
+            }
         }
     }
 
@@ -1789,7 +1791,7 @@
     InputMapper::populateDeviceInfo(info);
 
     info->setKeyboardType(mKeyboardType);
-    info->setKeyCharacterMapFile(getEventHub()->getKeyCharacterMapFile(getDeviceId()));
+    info->setKeyCharacterMap(getEventHub()->getKeyCharacterMap(getDeviceId()));
 }
 
 void KeyboardInputMapper::dump(String8& dump) {
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 2cccf9f..057ad18 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -610,8 +610,8 @@
         }
     }
 
-    virtual String8 getKeyCharacterMapFile(int32_t deviceId) const {
-        return String8();
+    virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const {
+        return NULL;
     }
 
     virtual bool isExternal(int32_t deviceId) const {
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 96ee2f0..ca7241c 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -27,6 +27,7 @@
 import com.android.internal.view.IInputMethodSession;
 import com.android.internal.view.InputBindResult;
 import com.android.server.EventLogTags;
+import com.android.server.wm.WindowManagerService;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -91,7 +92,10 @@
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
 import android.widget.ArrayAdapter;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
 import android.widget.RadioButton;
+import android.widget.Switch;
 import android.widget.TextView;
 
 import java.io.File;
@@ -134,6 +138,8 @@
     static final int MSG_UNBIND_METHOD = 3000;
     static final int MSG_BIND_METHOD = 3010;
 
+    static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
+
     static final long TIME_TO_RECONNECT = 10*1000;
 
     static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
@@ -156,6 +162,8 @@
     final HandlerCaller mCaller;
     private final InputMethodFileManager mFileManager;
     private final InputMethodAndSubtypeListManager mImListManager;
+    private final HardKeyboardListener mHardKeyboardListener;
+    private final WindowManagerService mWindowManagerService;
 
     final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
 
@@ -360,6 +368,7 @@
 
     private AlertDialog.Builder mDialogBuilder;
     private AlertDialog mSwitchingDialog;
+    private View mSwitchingDialogTitleView;
     private InputMethodInfo[] mIms;
     private int[] mSubtypeIds;
 
@@ -528,7 +537,31 @@
         }
     }
 
-    public InputMethodManagerService(Context context) {
+    private class HardKeyboardListener
+            implements WindowManagerService.OnHardKeyboardStatusChangeListener {
+        @Override
+        public void onHardKeyboardStatusChange(boolean available, boolean enabled) {
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0, enabled ? 1 : 0));
+        }
+
+        public void handleHardKeyboardStatusChange(boolean available, boolean enabled) {
+            if (DEBUG) {
+                Slog.w(TAG, "HardKeyboardStatusChanged: available = " + available + ", enabled = "
+                        + enabled);
+            }
+            synchronized(mMethodMap) {
+                if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
+                        && mSwitchingDialog.isShowing()) {
+                    mSwitchingDialogTitleView.findViewById(
+                            com.android.internal.R.id.hard_keyboard_section).setVisibility(
+                                    available ? View.VISIBLE : View.GONE);
+                }
+            }
+        }
+    }
+
+    public InputMethodManagerService(Context context, WindowManagerService windowManager) {
         mContext = context;
         mRes = context.getResources();
         mHandler = new Handler(this);
@@ -540,6 +573,8 @@
                 handleMessage(msg);
             }
         });
+        mWindowManagerService = windowManager;
+        mHardKeyboardListener = new HardKeyboardListener();
 
         mImeSwitcherNotification = new Notification();
         mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default;
@@ -633,6 +668,10 @@
                 updateImeWindowStatusLocked();
                 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
                         com.android.internal.R.bool.show_ongoing_ime_switcher);
+                if (mShowOngoingImeSwitcherForPhones) {
+                    mWindowManagerService.setOnHardKeyboardStatusChangeListener(
+                            mHardKeyboardListener);
+                }
                 try {
                     startInputInnerLocked();
                 } catch (RuntimeException e) {
@@ -1994,6 +2033,12 @@
                     Slog.w(TAG, "Client died receiving input method " + args.arg2);
                 }
                 return true;
+
+            // --------------------------------------------------------------
+            case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
+                mHardKeyboardListener.handleHardKeyboardStatusChange(
+                        msg.arg1 == 1, msg.arg2 == 1);
+                return true;
         }
         return false;
     }
@@ -2204,12 +2249,10 @@
                     }
                 }
             }
-
             final TypedArray a = context.obtainStyledAttributes(null,
                     com.android.internal.R.styleable.DialogPreference,
                     com.android.internal.R.attr.alertDialogStyle, 0);
             mDialogBuilder = new AlertDialog.Builder(context)
-                    .setTitle(com.android.internal.R.string.select_input_method)
                     .setOnCancelListener(new OnCancelListener() {
                         @Override
                         public void onCancel(DialogInterface dialog) {
@@ -2219,6 +2262,29 @@
                     .setIcon(a.getDrawable(
                             com.android.internal.R.styleable.DialogPreference_dialogTitle));
             a.recycle();
+            final LayoutInflater inflater =
+                    (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            final View tv = inflater.inflate(
+                    com.android.internal.R.layout.input_method_switch_dialog_title, null);
+            mDialogBuilder.setCustomTitle(tv);
+
+            // Setup layout for a toggle switch of the hardware keyboard
+            mSwitchingDialogTitleView = tv;
+            mSwitchingDialogTitleView.findViewById(
+                    com.android.internal.R.id.hard_keyboard_section).setVisibility(
+                            mWindowManagerService.isHardKeyboardAvailable() ?
+                                    View.VISIBLE : View.GONE);
+            final Switch hardKeySwitch =  ((Switch)mSwitchingDialogTitleView.findViewById(
+                    com.android.internal.R.id.hard_keyboard_switch));
+            hardKeySwitch.setChecked(mWindowManagerService.isHardKeyboardEnabled());
+            hardKeySwitch.setOnCheckedChangeListener(
+                    new OnCheckedChangeListener() {
+                        @Override
+                        public void onCheckedChanged(
+                                CompoundButton buttonView, boolean isChecked) {
+                            mWindowManagerService.setHardKeyboardEnabled(isChecked);
+                        }
+                    });
 
             final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(context,
                     com.android.internal.R.layout.simple_list_item_2_single_choice, imList,
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index bebce7e..2d2a881 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -1795,7 +1795,7 @@
             final boolean stateChanged = mPowerState != newState;
 
             if (stateChanged && reason == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT) {
-                if (mPolicy.isScreenSaverEnabled()) {
+                if (mPolicy != null && mPolicy.isScreenSaverEnabled()) {
                     if (mSpew) {
                         Slog.d(TAG, "setPowerState: running screen saver instead of turning off screen");
                     }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 68bbb571..241d04ff 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -276,7 +276,7 @@
         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
             try {
                 Slog.i(TAG, "Input Method Service");
-                imm = new InputMethodManagerService(context);
+                imm = new InputMethodManagerService(context, wm);
                 ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
             } catch (Throwable e) {
                 reportWtf("starting Input Manager Service", e);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 42c42c9..78b441a 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -13444,7 +13444,7 @@
             // an earlier hidden adjustment that isn't really for us... if
             // so, use the new hidden adjustment.
             if (!recursed && app.hidden) {
-                app.curAdj = app.curRawAdj = hiddenAdj;
+                app.curAdj = app.curRawAdj = app.nonStoppingAdj = hiddenAdj;
             }
             return app.curRawAdj;
         }
@@ -13468,7 +13468,7 @@
             // below foreground, so it is not worth doing work for it.
             app.adjType = "fixed";
             app.adjSeq = mAdjSeq;
-            app.curRawAdj = app.maxAdj;
+            app.curRawAdj = app.nonStoppingAdj = app.maxAdj;
             app.foregroundActivities = false;
             app.keeping = true;
             app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
@@ -13545,6 +13545,8 @@
             app.adjType = "bg-empty";
         }
 
+        boolean hasStoppingActivities = false;
+
         // Examine all activities if not already foreground.
         if (!app.foregroundActivities && activitiesSize > 0) {
             for (int j = 0; j < activitiesSize; j++) {
@@ -13559,15 +13561,20 @@
                     app.hidden = false;
                     app.foregroundActivities = true;
                     break;
-                } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED
-                        || r.state == ActivityState.STOPPING) {
-                    // Only upgrade adjustment.
+                } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) {
                     if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
                         adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                        app.adjType = "stopping";
+                        app.adjType = "pausing";
                     }
                     app.hidden = false;
                     app.foregroundActivities = true;
+                } else if (r.state == ActivityState.STOPPING) {
+                    // We will apply the actual adjustment later, because
+                    // we want to allow this process to immediately go through
+                    // any memory trimming that is in effect.
+                    app.hidden = false;
+                    app.foregroundActivities = true;
+                    hasStoppingActivities = true;
                 }
             }
         }
@@ -13625,7 +13632,7 @@
         // this gives us a baseline and makes sure we don't get into an
         // infinite recursion.
         app.adjSeq = mAdjSeq;
-        app.curRawAdj = adj;
+        app.curRawAdj = app.nonStoppingAdj = adj;
 
         if (mBackupTarget != null && app == mBackupTarget.app) {
             // If possible we want to avoid killing apps while they're being backed up
@@ -13882,6 +13889,28 @@
             }
         }
 
+        if (adj == ProcessList.SERVICE_ADJ) {
+            if (doingAll) {
+                app.serviceb = mNewNumServiceProcs > (mNumServiceProcs/3);
+                mNewNumServiceProcs++;
+            }
+            if (app.serviceb) {
+                adj = ProcessList.SERVICE_B_ADJ;
+            }
+        } else {
+            app.serviceb = false;
+        }
+
+        app.nonStoppingAdj = adj;
+
+        if (hasStoppingActivities) {
+            // Only upgrade adjustment.
+            if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                app.adjType = "stopping";
+            }
+        }
+
         app.curRawAdj = adj;
         
         //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid +
@@ -13915,18 +13944,6 @@
             }
         }
 
-        if (adj == ProcessList.SERVICE_ADJ) {
-            if (doingAll) {
-                app.serviceb = mNewNumServiceProcs > (mNumServiceProcs/3);
-                mNewNumServiceProcs++;
-            }
-            if (app.serviceb) {
-                adj = ProcessList.SERVICE_B_ADJ;
-            }
-        } else {
-            app.serviceb = false;
-        }
-
         app.curAdj = adj;
         app.curSchedGroup = schedGroup;
 
@@ -14138,7 +14155,7 @@
                 }
                 // If a process has held a wake lock for more
                 // than 50% of the time during this period,
-                // that sounds pad.  Kill!
+                // that sounds bad.  Kill!
                 if (doWakeKills && realtimeSince > 0
                         && ((wtimeUsed*100)/realtimeSince) >= 50) {
                     synchronized (stats) {
@@ -14186,23 +14203,6 @@
         computeOomAdjLocked(app, hiddenAdj, TOP_APP, false, doingAll);
 
         if (app.curRawAdj != app.setRawAdj) {
-            if (false) {
-                // Removing for now.  Forcing GCs is not so useful anymore
-                // with Dalvik, and the new memory level hint facility is
-                // better for what we need to do these days.
-                if (app.curRawAdj > ProcessList.FOREGROUND_APP_ADJ
-                        && app.setRawAdj <= ProcessList.FOREGROUND_APP_ADJ) {
-                    // If this app is transitioning from foreground to
-                    // non-foreground, have it do a gc.
-                    scheduleAppGcLocked(app);
-                } else if (app.curRawAdj >= ProcessList.HIDDEN_APP_MIN_ADJ
-                        && app.setRawAdj < ProcessList.HIDDEN_APP_MIN_ADJ) {
-                    // Likewise do a gc when an app is moving in to the
-                    // background (such as a service stopping).
-                    scheduleAppGcLocked(app);
-                }
-            }
-
             if (wasKeeping && !app.keeping) {
                 // This app is no longer something we want to keep.  Note
                 // its current wake lock time to later know to kill it if
@@ -14319,6 +14319,7 @@
         if (factor < 1) factor = 1;
         int step = 0;
         int numHidden = 0;
+        int numTrimming = 0;
         
         // First update the OOM adjustment for each of the
         // application processes based on their current state.
@@ -14363,6 +14364,11 @@
                     app.killedBackground = true;
                     Process.killProcessQuiet(app.pid);
                 }
+                if (app.nonStoppingAdj >= ProcessList.HOME_APP_ADJ
+                        && app.nonStoppingAdj != ProcessList.SERVICE_B_ADJ
+                        && !app.killedBackground) {
+                    numTrimming++;
+                }
             }
         }
 
@@ -14376,7 +14382,7 @@
         // memory they want.
         if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/2)) {
             final int N = mLruProcesses.size();
-            factor = numHidden/3;
+            factor = numTrimming/3;
             int minFactor = 2;
             if (mHomeProcess != null) minFactor++;
             if (mPreviousProcess != null) minFactor++;
@@ -14393,8 +14399,8 @@
             int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
             for (i=0; i<N; i++) {
                 ProcessRecord app = mLruProcesses.get(i);
-                if (app.curAdj >= ProcessList.HOME_APP_ADJ
-                        && app.curAdj != ProcessList.SERVICE_B_ADJ
+                if (app.nonStoppingAdj >= ProcessList.HOME_APP_ADJ
+                        && app.nonStoppingAdj != ProcessList.SERVICE_B_ADJ
                         && !app.killedBackground) {
                     if (app.trimMemoryLevel < curLevel && app.thread != null) {
                         try {
@@ -14426,7 +14432,7 @@
                                 break;
                         }
                     }
-                } else if (app.curAdj == ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+                } else if (app.nonStoppingAdj == ProcessList.HEAVY_WEIGHT_APP_ADJ) {
                     if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
                             && app.thread != null) {
                         try {
@@ -14437,7 +14443,7 @@
                     }
                     app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
                 } else {
-                    if ((app.curAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi)
+                    if ((app.nonStoppingAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi)
                             && app.pendingUiClean) {
                         // If this application is now in the background and it
                         // had done UI, then give it the special trim level to
@@ -14464,7 +14470,7 @@
             final int N = mLruProcesses.size();
             for (i=0; i<N; i++) {
                 ProcessRecord app = mLruProcesses.get(i);
-                if ((app.curAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi)
+                if ((app.nonStoppingAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi)
                         && app.pendingUiClean) {
                     if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
                             && app.thread != null) {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index a01ed25..6596e1f 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -97,6 +97,11 @@
     // next activity.
     static final int PAUSE_TIMEOUT = 500;
 
+    // How long we wait for the activity to tell us it has stopped before
+    // giving up.  This is a good amount of time because we really need this
+    // from the application in order to get its saved state.
+    static final int STOP_TIMEOUT = 10*1000;
+
     // How long we can hold the sleep wake lock before giving up.
     static final int SLEEP_TIMEOUT = 5*1000;
 
@@ -280,6 +285,7 @@
     static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 5;
     static final int RESUME_TOP_ACTIVITY_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 6;
     static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 7;
+    static final int STOP_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 8;
     
     final Handler mHandler = new Handler() {
         //public Handler() {
@@ -364,6 +370,17 @@
                         resumeTopActivityLocked(null);
                     }
                 } break;
+                case STOP_TIMEOUT_MSG: {
+                    ActivityRecord r = (ActivityRecord)msg.obj;
+                    // We don't at this point know if the activity is fullscreen,
+                    // so we need to be conservative and assume it isn't.
+                    Slog.w(TAG, "Activity stop timeout for " + r);
+                    synchronized (mService) {
+                        if (r.isInHistory()) {
+                            activityStoppedLocked(r, null, null, null);
+                        }
+                    }
+                } break;
             }
         }
     };
@@ -1000,31 +1017,38 @@
     final void activityStoppedLocked(ActivityRecord r, Bundle icicle, Bitmap thumbnail,
             CharSequence description) {
         if (DEBUG_SAVED_STATE) Slog.i(TAG, "Saving icicle of " + r + ": " + icicle);
-        r.icicle = icicle;
-        r.haveState = true;
-        r.updateThumbnail(thumbnail, description);
-        r.stopped = true;
-        if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + " (stop complete)");
-        r.state = ActivityState.STOPPED;
-        if (!r.finishing) {
-            if (r.configDestroy) {
-                destroyActivityLocked(r, true, false, "stop-config");
-                resumeTopActivityLocked(null);
-            } else {
-                // Now that this process has stopped, we may want to consider
-                // it to be the previous app to try to keep around in case
-                // the user wants to return to it.
-                ProcessRecord fgApp = null;
-                if (mResumedActivity != null) {
-                    fgApp = mResumedActivity.app;
-                } else if (mPausingActivity != null) {
-                    fgApp = mPausingActivity.app;
-                }
-                if (r.app != null && fgApp != null && r.app != fgApp
-                        && r.lastVisibleTime > mService.mPreviousProcessVisibleTime
-                        && r.app != mService.mHomeProcess) {
-                    mService.mPreviousProcess = r.app;
-                    mService.mPreviousProcessVisibleTime = r.lastVisibleTime;
+        if (icicle != null) {
+            // If icicle is null, this is happening due to a timeout, so we
+            // haven't really saved the state.
+            r.icicle = icicle;
+            r.haveState = true;
+            r.updateThumbnail(thumbnail, description);
+        }
+        if (!r.stopped) {
+            if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + " (stop complete)");
+            mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
+            r.stopped = true;
+            r.state = ActivityState.STOPPED;
+            if (!r.finishing) {
+                if (r.configDestroy) {
+                    destroyActivityLocked(r, true, false, "stop-config");
+                    resumeTopActivityLocked(null);
+                } else {
+                    // Now that this process has stopped, we may want to consider
+                    // it to be the previous app to try to keep around in case
+                    // the user wants to return to it.
+                    ProcessRecord fgApp = null;
+                    if (mResumedActivity != null) {
+                        fgApp = mResumedActivity.app;
+                    } else if (mPausingActivity != null) {
+                        fgApp = mPausingActivity.app;
+                    }
+                    if (r.app != null && fgApp != null && r.app != fgApp
+                            && r.lastVisibleTime > mService.mPreviousProcessVisibleTime
+                            && r.app != mService.mHomeProcess) {
+                        mService.mPreviousProcess = r.app;
+                        mService.mPreviousProcessVisibleTime = r.lastVisibleTime;
+                    }
                 }
             }
         }
@@ -3228,6 +3252,9 @@
                 if (mService.isSleeping()) {
                     r.setSleeping(true);
                 }
+                Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG);
+                msg.obj = r;
+                mHandler.sendMessageDelayed(msg, STOP_TIMEOUT);
             } catch (Exception e) {
                 // Maybe just ignore exceptions here...  if the process
                 // has crashed, our death notification will clean things
@@ -3694,6 +3721,7 @@
 
         // Get rid of any pending idle timeouts.
         mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
+        mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
         mHandler.removeMessages(IDLE_TIMEOUT_MSG, r);
         mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
         r.finishLaunchTickingLocked();
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index b64261d..4529ecc 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -63,6 +63,7 @@
     int hiddenAdj;              // If hidden, this is the adjustment to use
     int curRawAdj;              // Current OOM unlimited adjustment for this process
     int setRawAdj;              // Last set OOM unlimited adjustment for this process
+    int nonStoppingAdj;         // Adjustment not counting any stopping activities
     int curAdj;                 // Current OOM adjustment for this process
     int setAdj;                 // Last set OOM adjustment for this process
     int curSchedGroup;          // Currently desired scheduling class
@@ -199,6 +200,7 @@
                 pw.print(" hidden="); pw.print(hiddenAdj);
                 pw.print(" curRaw="); pw.print(curRawAdj);
                 pw.print(" setRaw="); pw.print(setRawAdj);
+                pw.print(" nonStopping="); pw.print(nonStoppingAdj);
                 pw.print(" cur="); pw.print(curAdj);
                 pw.print(" set="); pw.println(setAdj);
         pw.print(prefix); pw.print("curSchedGroup="); pw.print(curSchedGroup);
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index b8cc65e..2f25df1 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -18,37 +18,45 @@
 
 import com.android.internal.util.XmlUtils;
 import com.android.server.Watchdog;
-import com.android.server.input.InputFilter.Host;
-import com.android.server.wm.WindowManagerService;
 
 import org.xmlpull.v1.XmlPullParser;
 
+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.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.hardware.input.IInputManager;
 import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardLayout;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.MessageQueue;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
+import android.util.Log;
 import android.util.Slog;
 import android.util.Xml;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
+import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.PointerIcon;
 import android.view.Surface;
 import android.view.ViewConfiguration;
-import android.view.WindowManager;
 import android.view.WindowManagerPolicy;
+import android.view.KeyCharacterMap.UnavailableException;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -57,6 +65,8 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 
 /*
  * Wraps the C++ InputManager and provides its callbacks.
@@ -135,6 +145,10 @@
     /** The key is down but is a virtual key press that is being emulated by the system. */
     public static final int KEY_STATE_VIRTUAL = 2;
 
+    // Used to simulate a persistent data store for keyboard layouts.
+    // TODO: Replace with the real thing.
+    private final HashMap<String, String> mFakeRegistry = new HashMap<String, String>();
+
     // State for the currently installed input filter.
     final Object mInputFilterLock = new Object();
     InputFilter mInputFilter;
@@ -246,7 +260,7 @@
      * key codes.
      * @return True if the lookup was successful, false otherwise.
      */
-    @Override
+    @Override // Binder call
     public boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists) {
         if (keyCodes == null) {
             throw new IllegalArgumentException("keyCodes must not be null.");
@@ -337,7 +351,7 @@
         }
     }
 
-    @Override
+    @Override // Binder call
     public boolean injectInputEvent(InputEvent event, int mode) {
         if (event == null) {
             throw new IllegalArgumentException("event must not be null");
@@ -380,7 +394,7 @@
      * @param id The device id.
      * @return The input device or null if not found.
      */
-    @Override
+    @Override // Binder call
     public InputDevice getInputDevice(int deviceId) {
         return nativeGetInputDevice(mPtr, deviceId);
     }
@@ -389,11 +403,187 @@
      * Gets the ids of all input devices in the system.
      * @return The input device ids.
      */
-    @Override
+    @Override // Binder call
     public int[] getInputDeviceIds() {
         return nativeGetInputDeviceIds(mPtr);
     }
-    
+
+    @Override // Binder call
+    public KeyboardLayout[] getKeyboardLayouts() {
+        ArrayList<KeyboardLayout> list = new ArrayList<KeyboardLayout>();
+
+        final PackageManager pm = mContext.getPackageManager();
+        Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS);
+        for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent,
+                PackageManager.GET_META_DATA)) {
+            loadKeyboardLayouts(pm, resolveInfo.activityInfo, list, null);
+        }
+        return list.toArray(new KeyboardLayout[list.size()]);
+    }
+
+    @Override // Binder call
+    public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+
+        KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(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 null;
+        }
+    }
+
+    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(InputManager.META_DATA_KEYBOARD_LAYOUTS);
+        if (configResId == 0) {
+            Log.w(TAG, "Missing meta-data '" + InputManager.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 = KeyboardLayoutDescriptor.format(
+                                        receiver.packageName, receiver.name, name);
+                                KeyboardLayout c = new KeyboardLayout(descriptor, label);
+                                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;
+        }
+        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;
+    }
+
+    @Override // Binder call
+    public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+
+        return mFakeRegistry.get(inputDeviceDescriptor);
+    }
+
+    @Override // Binder call
+    public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+                "setKeyboardLayoutForInputDevice()")) {
+            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+        }
+
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+
+        mFakeRegistry.put(inputDeviceDescriptor, keyboardLayoutDescriptor);
+    }
+
+    /**
+     * 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 = InputManager.parseKeyboardLayoutDescriptor(mDescriptor);
+            if (d == null) {
+                Log.e(InputManager.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(InputManager.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(InputManager.TAG, "Could not load key character map '" + mDescriptor
+                        + "' due to an error while parsing.", ex);
+                return null;
+            }
+        }
+        return mKeyCharacterMap;
+    }*/
+
     public void setInputWindows(InputWindowHandle[] windowHandles) {
         nativeSetInputWindows(mPtr, windowHandles);
     }
@@ -433,18 +623,17 @@
         return nativeTransferTouchFocus(mPtr, fromChannel, toChannel);
     }
 
-    /**
-     * Set the pointer speed.
-     * @param speed The pointer speed as a value between -7 (slowest) and 7 (fastest)
-     * where 0 is the default speed.
-     */
-    @Override
+    @Override // Binder call
     public void tryPointerSpeed(int speed) {
         if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED,
                 "tryPointerSpeed()")) {
             throw new SecurityException("Requires SET_POINTER_SPEED permission");
         }
 
+        if (speed < InputManager.MIN_POINTER_SPEED || speed > InputManager.MAX_POINTER_SPEED) {
+            throw new IllegalArgumentException("speed out of range");
+        }
+
         setPointerSpeedUnchecked(speed);
     }
 
@@ -740,4 +929,32 @@
             }
         }
     }
+
+    private static final class KeyboardLayoutDescriptor {
+        public String packageName;
+        public String receiverName;
+        public String keyboardLayoutName;
+
+        public static String format(String packageName,
+                String receiverName, String keyboardName) {
+            return packageName + "/" + receiverName + "/" + keyboardName;
+        }
+
+        public static KeyboardLayoutDescriptor parse(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;
+        }
+    }
 }
diff --git a/services/jni/com_android_server_input_InputManagerService.cpp b/services/jni/com_android_server_input_InputManagerService.cpp
index c137a78..85d6e11 100644
--- a/services/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/jni/com_android_server_input_InputManagerService.cpp
@@ -39,6 +39,7 @@
 #include <input/SpriteController.h>
 
 #include <android_os_MessageQueue.h>
+#include <android_view_InputDevice.h>
 #include <android_view_KeyEvent.h>
 #include <android_view_MotionEvent.h>
 #include <android_view_InputChannel.h>
@@ -88,20 +89,6 @@
 } gMotionEventClassInfo;
 
 static struct {
-    jclass clazz;
-
-    jmethodID ctor;
-    jmethodID addMotionRange;
-
-    jfieldID mId;
-    jfieldID mName;
-    jfieldID mDescriptor;
-    jfieldID mSources;
-    jfieldID mKeyboardType;
-    jfieldID mKeyCharacterMapFile;
-} gInputDeviceClassInfo;
-
-static struct {
     jfieldID touchscreen;
     jfieldID keyboard;
     jfieldID navigation;
@@ -1171,44 +1158,7 @@
         return NULL;
     }
 
-    jobject deviceObj = env->NewObject(gInputDeviceClassInfo.clazz, gInputDeviceClassInfo.ctor);
-    if (!deviceObj) {
-        return NULL;
-    }
-
-    jstring deviceNameObj = env->NewStringUTF(deviceInfo.getName().string());
-    if (!deviceNameObj) {
-        return NULL;
-    }
-
-    jstring deviceDescriptorObj = env->NewStringUTF(deviceInfo.getDescriptor().string());
-    if (!deviceDescriptorObj) {
-        return NULL;
-    }
-
-    jstring fileStr = env->NewStringUTF(deviceInfo.getKeyCharacterMapFile());
-    if (!fileStr) {
-        return NULL;
-    }
-
-    env->SetIntField(deviceObj, gInputDeviceClassInfo.mId, deviceInfo.getId());
-    env->SetObjectField(deviceObj, gInputDeviceClassInfo.mName, deviceNameObj);
-    env->SetObjectField(deviceObj, gInputDeviceClassInfo.mDescriptor, deviceDescriptorObj);
-    env->SetIntField(deviceObj, gInputDeviceClassInfo.mSources, deviceInfo.getSources());
-    env->SetIntField(deviceObj, gInputDeviceClassInfo.mKeyboardType, deviceInfo.getKeyboardType());
-    env->SetObjectField(deviceObj, gInputDeviceClassInfo.mKeyCharacterMapFile, fileStr);
-
-    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(deviceObj, gInputDeviceClassInfo.addMotionRange,
-                range.axis, range.source, range.min, range.max, range.flat, range.fuzz);
-        if (env->ExceptionCheck()) {
-            return NULL;
-        }
-    }
-
-    return deviceObj;
+    return android_view_InputDevice_create(env, deviceInfo);
 }
 
 static jintArray nativeGetInputDeviceIds(JNIEnv* env,
@@ -1437,35 +1387,6 @@
     FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent");
     gMotionEventClassInfo.clazz = jclass(env->NewGlobalRef(gMotionEventClassInfo.clazz));
 
-    // InputDevice
-
-    FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
-    gInputDeviceClassInfo.clazz = jclass(env->NewGlobalRef(gInputDeviceClassInfo.clazz));
-
-    GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz,
-            "<init>", "()V");
-
-    GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz,
-            "addMotionRange", "(IIFFFF)V");
-
-    GET_FIELD_ID(gInputDeviceClassInfo.mId, gInputDeviceClassInfo.clazz,
-            "mId", "I");
-
-    GET_FIELD_ID(gInputDeviceClassInfo.mName, gInputDeviceClassInfo.clazz,
-            "mName", "Ljava/lang/String;");
-
-    GET_FIELD_ID(gInputDeviceClassInfo.mDescriptor, gInputDeviceClassInfo.clazz,
-            "mDescriptor", "Ljava/lang/String;");
-
-    GET_FIELD_ID(gInputDeviceClassInfo.mSources, gInputDeviceClassInfo.clazz,
-            "mSources", "I");
-
-    GET_FIELD_ID(gInputDeviceClassInfo.mKeyboardType, gInputDeviceClassInfo.clazz,
-            "mKeyboardType", "I");
-
-    GET_FIELD_ID(gInputDeviceClassInfo.mKeyCharacterMapFile, gInputDeviceClassInfo.clazz,
-            "mKeyCharacterMapFile", "Ljava/lang/String;");
-
     // Configuration
 
     FIND_CLASS(clazz, "android/content/res/Configuration");
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 689aa8e..cbd591f 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -345,6 +345,7 @@
     LABEL_ATTR = 0x01010001,
     ICON_ATTR = 0x01010002,
     NAME_ATTR = 0x01010003,
+    DEBUGGABLE_ATTR = 0x0101000f,
     VERSION_CODE_ATTR = 0x0101021b,
     VERSION_NAME_ATTR = 0x0101021c,
     SCREEN_ORIENTATION_ATTR = 0x0101001e,
@@ -830,6 +831,15 @@
                         if (testOnly != 0) {
                             printf("testOnly='%d'\n", testOnly);
                         }
+
+                        int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
+                            goto bail;
+                        }
+                        if (debuggable != 0) {
+                            printf("application-debuggable\n");
+                        }
                     } else if (tag == "uses-sdk") {
                         int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
                         if (error != "") {
diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp
index 3cc2467..563225e 100644
--- a/tools/validatekeymaps/Main.cpp
+++ b/tools/validatekeymaps/Main.cpp
@@ -78,7 +78,7 @@
         return false;
 
     case FILETYPE_KEYLAYOUT: {
-        KeyLayoutMap* map;
+        sp<KeyLayoutMap> map;
         status_t status = KeyLayoutMap::load(String8(filename), &map);
         if (status) {
             fprintf(stderr, "Error %d parsing key layout file.\n\n", status);
@@ -88,7 +88,7 @@
     }
 
     case FILETYPE_KEYCHARACTERMAP: {
-        KeyCharacterMap* map;
+        sp<KeyCharacterMap> map;
         status_t status = KeyCharacterMap::load(String8(filename), &map);
         if (status) {
             fprintf(stderr, "Error %d parsing key character map file.\n\n", status);
@@ -104,6 +104,7 @@
             fprintf(stderr, "Error %d parsing input device configuration file.\n\n", status);
             return false;
         }
+        delete map;
         break;
     }
 
@@ -114,6 +115,7 @@
             fprintf(stderr, "Error %d parsing virtual key definition file.\n\n", status);
             return false;
         }
+        delete map;
         break;
     }
     }